ProductionInboundService.cs 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. using Admin.NET.Plugin.AiDOP.FinishedWarehouse.Dto;
  2. namespace Admin.NET.Plugin.AiDOP.FinishedWarehouse;
  3. /// <summary>
  4. /// S7 生产入库单列表 只读 list 服务。
  5. ///
  6. /// 数据源(2026-07-30 起):DOP 数据中台标准层 mdp_std_production_receipt(扁平,一行=一条 NbrDetail 明细)。
  7. /// 该 std 由 ProductionReceiptMdpSyncService 从 aidopdev.NbrMaster/NbrDetail Type='WOI'(生产入库)同步标准化而来,且已双源(dopdemorq)。
  8. /// S7 成品入库与 S5 生产收货读同一批 WOI 数据(过滤逐字相同)→ CTO 拍板复用该 std、不另建管线/实体(S5-list 与 S7-list 展示同一 WOI 单据的两个视角)。
  9. ///
  10. /// 口径映射(std 列 → 列表列):入库单号=nbr;行号=line;入库日期=receipt_date;状态=status;入库类型=remark;
  11. /// 生产线=prod_line;工单号=work_ord;ERP工单号=erp_work_ord;物料编码=item_num;名称=item_name;规格=item_spec;
  12. /// 入库库位=location_to;批次=lot_serial;入库数量=qty_rec;单位=um;相关单号=ord_nbr。
  13. ///
  14. /// 本服务仅 SELECT:无新增/编辑/删除/入库写入/库存事务/状态流转。租户口径对齐 ProductionReceiptService:
  15. /// 仅当 input.TenantId>0 才按 m.tenant_id 过滤(receipt std tenant 由同步入站上下文赋值,默认租户可能空态)。
  16. /// </summary>
  17. [ApiDescriptionSettings(Order = 310, Description = "生产入库单列表")]
  18. [Route("api/S7ProductionInbound")]
  19. [NonUnify]
  20. public class ProductionInboundService : IDynamicApiController, ITransient
  21. {
  22. /// <summary>主/系统租户哨兵:超管未选择目标租户时其 JWT TenantId 即此值,拒绝作为业务租户。</summary>
  23. private const long MainTenantId = 1300000000001L;
  24. private readonly ISqlSugarClient _db;
  25. private readonly UserManager _userManager;
  26. public ProductionInboundService(ISqlSugarClient db, UserManager userManager)
  27. {
  28. _db = db;
  29. _userManager = userManager;
  30. }
  31. /// <summary>严格可信租户解析:只来自认证后 JWT;无 Token(&lt;=0) 或超管未选主租户 → 拒绝;不读前端 tenantId、无默认回退。</summary>
  32. private long ResolveTenantOrThrow()
  33. {
  34. var tid = _userManager.TenantId;
  35. if (tid <= 0)
  36. throw Oops.Oh("无法确定当前租户,请重新登录或选择目标租户");
  37. if (_userManager.SuperAdmin && tid == MainTenantId)
  38. throw Oops.Oh("超级管理员操作前必须选择目标租户");
  39. return tid;
  40. }
  41. /// <summary>
  42. /// 生产入库单列表(只读分页查询,扁平:一行=一条明细;读数据中台标准层 mdp_std_production_receipt)。
  43. /// </summary>
  44. [DisplayName("生产入库单列表")]
  45. [HttpGet("list")]
  46. public async Task<object> GetList([FromQuery] ProductionInboundListInput input)
  47. {
  48. var page = input.Page <= 0 ? 1 : input.Page;
  49. var pageSize = input.PageSize <= 0 ? 10 : input.PageSize;
  50. var offset = (page - 1) * pageSize;
  51. // 强制租户过滤:租户只来自认证后的可信上下文(严格 JWT),忽略前端传入的 input.TenantId,杜绝跨租户可见。
  52. var tid = ResolveTenantOrThrow();
  53. var where = new List<string> { "1=1" };
  54. var pars = new List<SugarParameter>();
  55. where.Add("m.tenant_id = @TenantId");
  56. pars.Add(new SugarParameter("@TenantId", tid));
  57. if (!string.IsNullOrWhiteSpace(input.InboundNo))
  58. {
  59. where.Add("m.nbr LIKE @InboundNo");
  60. pars.Add(new SugarParameter("@InboundNo", $"%{input.InboundNo.Trim()}%"));
  61. }
  62. if (!string.IsNullOrWhiteSpace(input.WorkOrderNo))
  63. {
  64. where.Add("m.work_ord LIKE @WorkOrderNo");
  65. pars.Add(new SugarParameter("@WorkOrderNo", $"%{input.WorkOrderNo.Trim()}%"));
  66. }
  67. if (!string.IsNullOrWhiteSpace(input.ErpWorkOrderNo))
  68. {
  69. where.Add("m.erp_work_ord LIKE @ErpWorkOrderNo");
  70. pars.Add(new SugarParameter("@ErpWorkOrderNo", $"%{input.ErpWorkOrderNo.Trim()}%"));
  71. }
  72. if (!string.IsNullOrWhiteSpace(input.Batch))
  73. {
  74. where.Add("m.lot_serial LIKE @Batch");
  75. pars.Add(new SugarParameter("@Batch", $"%{input.Batch.Trim()}%"));
  76. }
  77. if (!string.IsNullOrWhiteSpace(input.InboundDateStart))
  78. {
  79. where.Add("m.receipt_date >= @InboundDateStart");
  80. pars.Add(new SugarParameter("@InboundDateStart", $"{input.InboundDateStart.Trim()} 00:00:00"));
  81. }
  82. if (!string.IsNullOrWhiteSpace(input.InboundDateEnd))
  83. {
  84. where.Add("m.receipt_date <= @InboundDateEnd");
  85. pars.Add(new SugarParameter("@InboundDateEnd", $"{input.InboundDateEnd.Trim()} 23:59:59"));
  86. }
  87. var whereSql = string.Join(" AND ", where);
  88. var total = await _db.Ado.GetIntAsync(
  89. $"SELECT COUNT(1) FROM mdp_std_production_receipt m WHERE {whereSql}", pars);
  90. var list = await _db.Ado.SqlQueryAsync<ProductionInboundListRow>(
  91. $"""
  92. SELECT
  93. m.nbr AS InboundNo,
  94. m.line AS LineNo,
  95. m.receipt_date AS InboundDate,
  96. m.status AS Status,
  97. m.remark AS InboundType,
  98. m.prod_line AS ProductionLine,
  99. m.work_ord AS WorkOrderNo,
  100. m.erp_work_ord AS ErpWorkOrderNo,
  101. m.item_num AS MaterialCode,
  102. m.item_name AS MaterialName,
  103. m.item_spec AS Spec,
  104. m.location_to AS InboundLocation,
  105. m.lot_serial AS Batch,
  106. m.qty_rec AS Qty,
  107. m.um AS Unit,
  108. m.ord_nbr AS RelatedBillNo,
  109. m.create_time AS CreateTime
  110. FROM mdp_std_production_receipt m
  111. WHERE {whereSql}
  112. ORDER BY m.nbr DESC, m.line
  113. LIMIT {pageSize} OFFSET {offset}
  114. """,
  115. pars);
  116. return new { total, page, pageSize, list };
  117. }
  118. }