| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404 |
- namespace Admin.NET.Plugin.AiDOP.Order;
- /// <summary>
- /// 订单发货服务 🚚
- /// 路由前缀:/api/Order/asnshipper/...
- /// </summary>
- [ApiDescriptionSettings(Order = 280, Description = "订单发货")]
- [Route("api/Order")]
- [AllowAnonymous]
- [NonUnify]
- public class AsnShipperService : IDynamicApiController, ITransient
- {
- private readonly ISqlSugarClient _db;
- private readonly SqlSugarRepository<AsnShipperMaster> _masterRep;
- private readonly SqlSugarRepository<AsnShipperDetail> _detailRep;
- private readonly UserManager _userManager;
- public AsnShipperService(
- ISqlSugarClient db,
- SqlSugarRepository<AsnShipperMaster> masterRep,
- SqlSugarRepository<AsnShipperDetail> detailRep,
- UserManager userManager)
- {
- _db = db;
- _masterRep = masterRep;
- _detailRep = detailRep;
- _userManager = userManager;
- }
- // ══════════════════════════════════════════════════════════════
- // 列表 GET /api/Order/asnshipper/list
- // ══════════════════════════════════════════════════════════════
- /// <summary>获取订单发货分页列表 🚚</summary>
- [DisplayName("获取订单发货列表")]
- [HttpGet("asnshipper/list")]
- public async Task<object> GetAsnShipperList([FromQuery] AsnShipperListInput input)
- {
- var pars = new List<SugarParameter>();
- var conditions = new List<string>
- {
- "a.shtype = 'SH'",
- "a.Typed <> 'S'",
- "a.IsActive = 1"
- };
- if (!string.IsNullOrWhiteSpace(input.Id))
- {
- conditions.Add("a.Id LIKE @Id");
- pars.Add(new SugarParameter("@Id", $"%{input.Id.Trim()}%"));
- }
- if (!string.IsNullOrWhiteSpace(input.OrdNbr))
- {
- conditions.Add("a.OrdNbr LIKE @OrdNbr");
- pars.Add(new SugarParameter("@OrdNbr", $"%{input.OrdNbr.Trim()}%"));
- }
- if (!string.IsNullOrWhiteSpace(input.Department))
- {
- conditions.Add("(b.Department LIKE @Dept OR b.Descr LIKE @Dept)");
- pars.Add(new SugarParameter("@Dept", $"%{input.Department.Trim()}%"));
- }
- if (!string.IsNullOrWhiteSpace(input.ShipDateFrom))
- {
- conditions.Add("a.ShipDate >= @ShipDateFrom");
- pars.Add(new SugarParameter("@ShipDateFrom", input.ShipDateFrom.Trim()));
- }
- var whereClause = "WHERE " + string.Join(" AND ", conditions);
- var baseSql = $"""
- SELECT
- a.RecID AS Id,
- a.Id AS Id1,
- a.OrdNbr,
- TRIM(CONCAT(IFNULL(b.Department,''), ' ', IFNULL(b.Descr,''))) AS DepartmentName,
- a.ShipDate,
- a.Status,
- a.Remark
- FROM ASNBOLShipperMaster a
- LEFT JOIN DepartmentMaster b
- ON a.Department = b.Department AND a.Domain = b.Domain
- {whereClause}
- """;
- var offset = (input.Page - 1) * input.PageSize;
- var total = await _db.Ado.GetIntAsync(
- $"SELECT COUNT(*) FROM ({baseSql}) AS t", pars);
- var list = await _db.Ado.SqlQueryAsync<AsnShipperListRow>(
- $"SELECT * FROM ({baseSql}) AS t ORDER BY t.Id DESC LIMIT {input.PageSize} OFFSET {offset}",
- pars);
- return new { total, page = input.Page, pageSize = input.PageSize, list };
- }
- // ══════════════════════════════════════════════════════════════
- // 详情 GET /api/Order/asnshipper/{id}
- // ══════════════════════════════════════════════════════════════
- /// <summary>获取订单发货详情(含明细)🚚</summary>
- [DisplayName("获取订单发货详情")]
- [HttpGet("asnshipper/{id}")]
- public async Task<object> GetAsnShipperDetail(int id)
- {
- var master = await _masterRep.GetFirstAsync(u => u.RecID == id)
- ?? throw Oops.Oh("发货单不存在");
- // 主键关联:ASNBOLShipperDetail.ASNBOLShipperRecID = 主表 RecID
- var details = await _detailRep.GetListAsync(u => u.AsnShipperRecID == id);
- // 兼容历史数据:若外键未写入或曾用错误方式取 RecID,则按业务单号 Id + 销售单号 OrdNbr 兜底
- if (details.Count == 0 && !string.IsNullOrWhiteSpace(master.Id) && !string.IsNullOrWhiteSpace(master.OrdNbr))
- {
- details = await _detailRep.GetListAsync(u =>
- u.Id == master.Id && u.OrdNbr == master.OrdNbr);
- }
- return new
- {
- recID = master.RecID,
- id = master.Id,
- ordNbr = master.OrdNbr,
- soldTo = master.SoldTo,
- department = master.Department,
- shipDate = master.ShipDate?.ToString("yyyy-MM-dd"),
- status = master.Status,
- remark = master.Remark,
- details = details.OrderBy(d => d.Line).Select(d => new
- {
- recID = d.RecID,
- line = d.Line,
- ordNbr = d.OrdNbr,
- ordLine = d.OrdLine,
- containerItem = d.ContainerItem,
- descr = d.Descr,
- um = d.UM,
- location = d.Location,
- lotSerial = d.LotSerial,
- qtyToShip = d.QtyToShip,
- pickingQty = d.PickingQty,
- realQty = d.RealQty,
- status = d.Status,
- remark = d.Remark,
- })
- };
- }
- // ══════════════════════════════════════════════════════════════
- // 保存(新增/编辑)POST /api/Order/asnshipper/save
- // ══════════════════════════════════════════════════════════════
- /// <summary>保存订单发货(新增或编辑)🚚</summary>
- [DisplayName("保存订单发货")]
- [ApiDescriptionSettings(Name = "SaveAsnShipper"), HttpPost("asnshipper/save")]
- public async Task<object> SaveAsnShipper([FromBody] AsnShipperSaveInput input)
- {
- var now = DateTime.Now;
- var user = _userManager.Account ?? "system";
- if (input.RecID is null or 0)
- {
- // ── 新增:参照 SysJobService.AddJobDetail ──
- var entity = new AsnShipperMaster
- {
- Id = input.Id,
- OrdNbr = input.OrdNbr,
- SoldTo = input.SoldTo,
- Department = input.Department,
- ShipDate = string.IsNullOrWhiteSpace(input.ShipDate) ? null : DateTime.Parse(input.ShipDate),
- Status = input.Status,
- Remark = input.Remark,
- ShType = "SH",
- Typed = string.Empty,
- IsActive = 1,
- IsConfirm = 0,
- CreateUser = user,
- CreateTime = now,
- };
- var inserted = await _masterRep.AsInsertable(entity).ExecuteReturnEntityAsync();
- var newId = inserted.RecID;
- await SaveDetailsAsync(newId, input.Id ?? string.Empty, input.Details, user, now, isNew: true);
- return new { id = newId, message = "新增成功" };
- }
- else
- {
- // ── 编辑:参照 SysJobService.UpdateJobDetail ──
- var entity = await _masterRep.GetFirstAsync(u => u.RecID == input.RecID.Value)
- ?? throw Oops.Oh("发货单不存在");
- entity.Id = input.Id;
- entity.OrdNbr = input.OrdNbr;
- entity.SoldTo = input.SoldTo;
- entity.Department = input.Department;
- entity.ShipDate = string.IsNullOrWhiteSpace(input.ShipDate) ? null : DateTime.Parse(input.ShipDate);
- entity.Status = input.Status;
- entity.Remark = input.Remark;
- entity.UpdateUser = user;
- entity.UpdateTime = now;
- await _masterRep.UpdateAsync(entity);
- await SaveDetailsAsync(input.RecID.Value, input.Id ?? string.Empty, input.Details, user, now, isNew: false);
- return new { id = input.RecID, message = "编辑成功" };
- }
- }
- /// <summary>
- /// 明细三路合并(参照 SeOrderService.SaveEntriesAsync):
- /// ① DB有且入参有 → 更新
- /// ② DB无入参有 → 新增
- /// ③ DB有入参无 → 删除
- /// </summary>
- private async Task SaveDetailsAsync(int masterRecId, string shipperId,
- List<AsnShipperDetailInput> details, string user, DateTime now, bool isNew)
- {
- var dbDetails = isNew ? new List<AsnShipperDetail>()
- : await _detailRep.GetListAsync(u => u.AsnShipperRecID == masterRecId);
- var dbById = dbDetails.ToDictionary(u => u.RecID);
- var inputIds = new HashSet<int>(details.Where(d => d.RecID is > 0).Select(d => d.RecID!.Value));
- for (var i = 0; i < details.Count; i++)
- {
- var d = details[i];
- var lineNo = d.Line ?? (i + 1);
- if (d.RecID > 0 && dbById.TryGetValue(d.RecID!.Value, out var existing))
- {
- // ① 更新
- existing.Line = lineNo;
- existing.OrdNbr = d.OrdNbr;
- existing.OrdLine = d.OrdLine;
- existing.ContainerItem = d.ContainerItem;
- existing.Descr = d.Descr;
- existing.UM = d.UM;
- existing.Location = d.Location;
- existing.LotSerial = d.LotSerial;
- existing.QtyToShip = d.QtyToShip;
- existing.PickingQty = d.PickingQty;
- existing.RealQty = d.RealQty;
- existing.Status = d.Status;
- existing.Remark = d.Remark;
- existing.UpdateUser = user;
- existing.UpdateTime = now;
- await _detailRep.UpdateAsync(existing);
- }
- else
- {
- // ② 新增
- var detail = new AsnShipperDetail
- {
- Id = shipperId,
- AsnShipperRecID = masterRecId,
- Line = lineNo,
- OrdNbr = d.OrdNbr,
- OrdLine = d.OrdLine,
- ContainerItem = d.ContainerItem,
- Descr = d.Descr,
- UM = d.UM,
- Location = d.Location,
- LotSerial = d.LotSerial,
- QtyToShip = d.QtyToShip,
- PickingQty = d.PickingQty,
- RealQty = d.RealQty,
- Status = d.Status,
- Remark = d.Remark,
- ShType = "SH",
- IsActive = 1,
- CreateUser = user,
- CreateTime = now,
- };
- await _detailRep.InsertAsync(detail);
- }
- }
- // ③ 删除:DB有、入参无
- foreach (var toDelete in dbDetails.Where(u => !inputIds.Contains(u.RecID)))
- {
- await _detailRep.DeleteAsync(u => u.RecID == toDelete.RecID);
- }
- }
- // ══════════════════════════════════════════════════════════════
- // 删除 POST /api/Order/asnshipper/delete
- // ══════════════════════════════════════════════════════════════
- /// <summary>删除发货单(状态非关闭才可删除)🚚</summary>
- [DisplayName("删除订单发货")]
- [ApiDescriptionSettings(Name = "DeleteAsnShipper"), HttpPost("asnshipper/delete")]
- public async Task<object> DeleteAsnShipper([FromBody] AsnShipperDeleteInput input)
- {
- // 参照 SysJobService.DeleteJobDetail:先查再删
- var entity = await _masterRep.GetFirstAsync(u => u.RecID == input.RecID)
- ?? throw Oops.Oh("发货单不存在");
- if (entity.Status == "C")
- throw Oops.Oh("已关闭的发货单不允许删除");
- await _detailRep.DeleteAsync(u => u.AsnShipperRecID == input.RecID);
- await _masterRep.DeleteAsync(u => u.RecID == input.RecID);
- return new { message = "删除成功" };
- }
- // ══════════════════════════════════════════════════════════════
- // 下拉数据源 GET /api/Order/asnshipper/options/salesords
- // ══════════════════════════════════════════════════════════════
- /// <summary>获取销售单下拉列表 🚚</summary>
- [DisplayName("获取销售单下拉")]
- [HttpGet("asnshipper/options/salesords")]
- public async Task<object> GetSalesOrdOptions([FromQuery] string? keyword)
- {
- var pars = new List<SugarParameter>();
- var where = "s.IsDeleted = 0";
- if (!string.IsNullOrWhiteSpace(keyword))
- {
- where += " AND (s.bill_no LIKE @kw OR s.custom_name LIKE @kw)";
- pars.Add(new SugarParameter("@kw", $"%{keyword.Trim()}%"));
- }
- // MySQL:字符串拼接用 CONCAT(对应 SQL Server 的 bill_no+'_'+custom_name)
- var sql = $"""
- SELECT s.bill_no AS Value,
- CONCAT(s.bill_no, '_', IFNULL(s.custom_name, '')) AS Label
- FROM crm_seorder s
- WHERE {where}
- ORDER BY s.bill_no DESC
- LIMIT 100
- """;
- var list = await _db.Ado.SqlQueryAsync<SimpleKvRow>(sql, pars);
- return list;
- }
- // ══════════════════════════════════════════════════════════════
- // 下拉数据源 GET /api/Order/asnshipper/options/customers
- // ══════════════════════════════════════════════════════════════
- /// <summary>获取客户下拉列表 🚚</summary>
- [DisplayName("获取客户下拉")]
- [HttpGet("asnshipper/options/customers")]
- public async Task<object> GetCustomerOptions([FromQuery] string? keyword)
- {
- var pars = new List<SugarParameter>();
- var where = "1=1";
- if (!string.IsNullOrWhiteSpace(keyword))
- {
- where += " AND (Cust LIKE @kw OR SortName LIKE @kw)";
- pars.Add(new SugarParameter("@kw", $"%{keyword.Trim()}%"));
- }
- var sql = $"""
- SELECT Cust AS Value,
- TRIM(CONCAT(Cust, ' ', IFNULL(SortName,''))) AS Label
- FROM CustMaster
- WHERE {where}
- ORDER BY Cust
- LIMIT 100
- """;
- var list = await _db.Ado.SqlQueryAsync<SimpleKvRow>(sql, pars);
- return list;
- }
- // ══════════════════════════════════════════════════════════════
- // 下拉数据源 GET /api/Order/asnshipper/options/departments
- // ══════════════════════════════════════════════════════════════
- /// <summary>获取部门下拉列表 🚚</summary>
- [DisplayName("获取部门下拉")]
- [HttpGet("asnshipper/options/departments")]
- public async Task<object> GetDepartmentOptions([FromQuery] string? keyword)
- {
- var pars = new List<SugarParameter>();
- var where = "1=1";
- if (!string.IsNullOrWhiteSpace(keyword))
- {
- where += " AND (Department LIKE @kw OR Descr LIKE @kw)";
- pars.Add(new SugarParameter("@kw", $"%{keyword.Trim()}%"));
- }
- var sql = $"""
- SELECT Department AS Value,
- TRIM(CONCAT(Department, ' ', IFNULL(Descr,''))) AS Label
- FROM DepartmentMaster
- WHERE {where}
- ORDER BY Department
- LIMIT 100
- """;
- var list = await _db.Ado.SqlQueryAsync<SimpleKvRow>(sql, pars);
- return list;
- }
- // ──────────────── 内部查询结果映射类 ────────────────
- private sealed class AsnShipperListRow
- {
- public int Id { get; set; }
- public string? Id1 { get; set; }
- public string? OrdNbr { get; set; }
- public string? DepartmentName { get; set; }
- public DateTime? ShipDate { get; set; }
- public string? Status { get; set; }
- public string? Remark { get; set; }
- }
- private sealed class SimpleKvRow
- {
- public string? Value { get; set; }
- public string? Label { get; set; }
- }
- }
|