PendingInspectionService.cs 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. using Admin.NET.Plugin.AiDOP.MaterialWarehouse.Dto;
  2. namespace Admin.NET.Plugin.AiDOP.MaterialWarehouse;
  3. /// <summary>
  4. /// S5 暂收在检列表 只读 list 服务。
  5. ///
  6. /// 数据源:aidopdev.MissedPrint(Status='I' 暂收待检、Qty>0、Location 空),忠实复刻旧系统存储过程
  7. /// pr_WMS_GetMissedTempQty:LEFT JOIN qms_qcp_inspecapplyn/qms_qcp_insappnentry 取检验状态,按
  8. /// Supply/PurOrd/PurLine/ShipperNbr/ItemNum/LotSerial 聚合(MAX Shelf/SUM Qty/MAX CheckNo/MAX FINSPECTSTATUS),
  9. /// 再 LEFT JOIN ItemMaster(描述/单位)、SuppMaster(简称),clfs='3' 追加“-挑选使用”。
  10. /// Domain 由 GeneralizedCodeMaster(SystemConfig/CompanyCode) 解析,默认 8010。
  11. ///
  12. /// 本服务仅 SELECT:无新增/编辑/删除/库存事务/状态流转;MissedPrint 无 tenant_id,不做租户过滤。
  13. /// </summary>
  14. [ApiDescriptionSettings(Order = 301, Description = "暂收在检列表")]
  15. [Route("api/S5PendingInspection")]
  16. [NonUnify]
  17. public class PendingInspectionService : IDynamicApiController, ITransient
  18. {
  19. /// <summary>主/系统租户哨兵:超管未选择目标租户时其 JWT TenantId 即此值,拒绝作为业务租户。</summary>
  20. private const long MainTenantId = 1300000000001L;
  21. private readonly ISqlSugarClient _db;
  22. private readonly UserManager _userManager;
  23. public PendingInspectionService(ISqlSugarClient db, UserManager userManager)
  24. {
  25. _db = db;
  26. _userManager = userManager;
  27. }
  28. /// <summary>严格可信租户解析:只来自认证后 JWT;无 Token(&lt;=0) 或超管未选主租户 → 拒绝;不读前端 tenantId、无默认回退。</summary>
  29. private long ResolveTenantOrThrow()
  30. {
  31. var tid = _userManager.TenantId;
  32. if (tid <= 0)
  33. throw Oops.Oh("无法确定当前租户,请重新登录或选择目标租户");
  34. if (_userManager.SuperAdmin && tid == MainTenantId)
  35. throw Oops.Oh("超级管理员操作前必须选择目标租户");
  36. return tid;
  37. }
  38. /// <summary>解析公司域(GeneralizedCodeMaster SystemConfig/CompanyCode,默认 8010)。</summary>
  39. private async Task<string> ResolveDomainAsync()
  40. {
  41. var list = await _db.Ado.SqlQueryAsync<string>(
  42. "SELECT Domain FROM GeneralizedCodeMaster WHERE FldName='SystemConfig' AND Val='CompanyCode' ORDER BY RecID LIMIT 1");
  43. var domain = list.FirstOrDefault();
  44. return string.IsNullOrWhiteSpace(domain) ? "8010" : domain;
  45. }
  46. /// <summary>
  47. /// 暂收在检列表(只读分页查询)。
  48. /// </summary>
  49. [DisplayName("暂收在检列表")]
  50. [HttpGet("list")]
  51. public async Task<object> GetList([FromQuery] PendingInspectionListInput input)
  52. {
  53. var page = input.Page <= 0 ? 1 : input.Page;
  54. var pageSize = input.PageSize <= 0 ? 10 : input.PageSize;
  55. var offset = (page - 1) * pageSize;
  56. var domain = await ResolveDomainAsync();
  57. var where = new List<string>
  58. {
  59. "m.Domain = @Domain",
  60. "m.Status = 'I'",
  61. "m.Qty > 0",
  62. "IFNULL(m.Location,'') = ''",
  63. };
  64. // 同库分租户:主表 MissedPrint 无 tenant_id(只能靠 Domain 公司域隔离,无法做租户行级过滤);
  65. // LEFT JOIN 的 qms 申请/明细表已补 tenant_id,租户过滤置于 JOIN ON(避免退化为 INNER JOIN 丢失 MissedPrint 锚行)。
  66. // 租户改用严格可信解析(仅认证后 JWT,忽略前端 tenantId);配合类级已去 [AllowAnonymous],杜绝匿名跨租户读。
  67. // 残留边界:MissedPrint 锚行本身仍按 Domain(非租户)可见,此为旧系统 pr_WMS_GetMissedTempQty 无租户列的既有约束。
  68. var tenantId = ResolveTenantOrThrow();
  69. var pars = new List<SugarParameter> { new("@Domain", domain), new("@TenantId", tenantId) };
  70. if (!string.IsNullOrWhiteSpace(input.DemandNo))
  71. {
  72. where.Add("m.PurOrd LIKE @DemandNo");
  73. pars.Add(new SugarParameter("@DemandNo", $"%{input.DemandNo.Trim()}%"));
  74. }
  75. if (!string.IsNullOrWhiteSpace(input.Shelf))
  76. {
  77. where.Add("m.Shelf LIKE @Shelf");
  78. pars.Add(new SugarParameter("@Shelf", $"%{input.Shelf.Trim()}%"));
  79. }
  80. if (!string.IsNullOrWhiteSpace(input.BatchNo))
  81. {
  82. where.Add("m.LotSerial LIKE @BatchNo");
  83. pars.Add(new SugarParameter("@BatchNo", $"%{input.BatchNo.Trim()}%"));
  84. }
  85. if (!string.IsNullOrWhiteSpace(input.MaterialCode))
  86. {
  87. where.Add("m.ItemNum LIKE @MaterialCode");
  88. pars.Add(new SugarParameter("@MaterialCode", $"%{input.MaterialCode.Trim()}%"));
  89. }
  90. if (!string.IsNullOrWhiteSpace(input.InspectStatus))
  91. {
  92. where.Add("d.FINSPECTSTATUS = @InspectStatus");
  93. pars.Add(new SugarParameter("@InspectStatus", input.InspectStatus.Trim()));
  94. }
  95. if (!string.IsNullOrWhiteSpace(input.Supplier))
  96. {
  97. where.Add("m.Supply = @Supplier");
  98. pars.Add(new SugarParameter("@Supplier", input.Supplier.Trim()));
  99. }
  100. var whereSql = string.Join(" AND ", where);
  101. // 复刻存储过程 @tbLocDet 聚合层
  102. var groupedInner =
  103. $"""
  104. SELECT m.Supply, m.PurOrd, m.PurLine, m.ItemNum, m.ShipperNbr, m.LotSerial,
  105. MAX(m.Shelf) AS Shelf, SUM(m.Qty) AS Qty, MAX(m.CheckNo) AS CheckNo,
  106. MAX(IFNULL(d.FINSPECTSTATUS,'')) AS FINSPECTSTATUS
  107. FROM MissedPrint m
  108. LEFT JOIN qms_qcp_inspecapplyn q ON m.CheckNo = q.FBILLNO AND q.tenant_id = @TenantId
  109. LEFT JOIN qms_qcp_insappnentry d ON q.id = d.glid AND d.tenant_id = @TenantId
  110. WHERE {whereSql}
  111. GROUP BY m.Supply, m.PurOrd, m.PurLine, m.ShipperNbr, m.ItemNum, m.LotSerial
  112. """;
  113. var total = await _db.Ado.GetIntAsync($"SELECT COUNT(1) FROM ({groupedInner}) t", pars);
  114. var grandList = await _db.Ado.SqlQueryAsync<decimal>($"SELECT IFNULL(SUM(t.Qty),0) FROM ({groupedInner}) t", pars);
  115. var grandTotalQty = grandList.FirstOrDefault();
  116. var list = await _db.Ado.SqlQueryAsync<PendingInspectionListRow>(
  117. $"""
  118. SELECT
  119. t.PurOrd AS DemandNo,
  120. t.PurLine AS DemandLineNo,
  121. t.ItemNum AS MaterialCode,
  122. i.Descr AS MaterialName,
  123. t.LotSerial AS BatchNo,
  124. t.Qty AS Qty,
  125. i.UM AS Unit,
  126. t.Shelf AS PendingShelf,
  127. t.ShipperNbr AS DeliveryNo,
  128. t.CheckNo AS InspectNo,
  129. CONCAT(t.FINSPECTSTATUS,
  130. CASE WHEN (SELECT MAX(ib.clfs) FROM qms_qcp_inspbill ib WHERE ib.lydjbh = t.CheckNo AND ib.tenant_id = @TenantId) = '3'
  131. THEN '-挑选使用' ELSE '' END) AS InspectStatus,
  132. t.Supply AS Supplier,
  133. s.SortName AS SupplierShortName
  134. FROM ({groupedInner}) t
  135. LEFT JOIN ItemMaster i ON i.Domain = @Domain AND t.ItemNum = i.ItemNum
  136. LEFT JOIN SuppMaster s ON s.Domain = @Domain AND t.Supply = s.Supp
  137. ORDER BY t.Supply, t.PurOrd, t.PurLine
  138. LIMIT {pageSize} OFFSET {offset}
  139. """,
  140. pars);
  141. return new { total, page, pageSize, list, grandTotalQty };
  142. }
  143. /// <summary>
  144. /// 检验状态下拉选项(qms_qcp_insappnentry.FINSPECTSTATUS distinct,只读)。
  145. /// </summary>
  146. [DisplayName("暂收在检检验状态选项")]
  147. [HttpGet("status-options")]
  148. public async Task<object> GetStatusOptions()
  149. {
  150. // 同库分租户:qms_qcp_insappnentry 已补 tenant_id,选项按严格可信租户(仅认证后 JWT,忽略前端 tenantId)收敛。
  151. var tenantId = ResolveTenantOrThrow();
  152. var list = await _db.Ado.SqlQueryAsync<PendingInspectionStatusOption>(
  153. """
  154. SELECT DISTINCT FINSPECTSTATUS AS Val
  155. FROM qms_qcp_insappnentry
  156. WHERE IFNULL(FINSPECTSTATUS,'') <> '' AND tenant_id = @TenantId
  157. ORDER BY FINSPECTSTATUS
  158. """,
  159. new List<SugarParameter> { new("@TenantId", tenantId) });
  160. return list;
  161. }
  162. /// <summary>
  163. /// 供应商下拉选项(暂收在检集合内 distinct Supply + SuppMaster.SortName,只读)。
  164. /// </summary>
  165. [DisplayName("暂收在检供应商选项")]
  166. [HttpGet("supplier-options")]
  167. public async Task<object> GetSupplierOptions()
  168. {
  169. // 残留边界(注明):本选项来自 MissedPrint(无 tenant_id)+ SuppMaster(无 tenant_id),无 tenant-safe 父键可挂载租户过滤,
  170. // 只能按 Domain 公司域收敛,无法做租户行级过滤(既有约束)。类级已去 [AllowAnonymous] → 需认证后方可访问,杜绝匿名读。
  171. var domain = await ResolveDomainAsync();
  172. var list = await _db.Ado.SqlQueryAsync<PendingInspectionSupplierOption>(
  173. """
  174. SELECT DISTINCT m.Supply AS Val, s.SortName AS Label
  175. FROM MissedPrint m
  176. LEFT JOIN SuppMaster s ON s.Domain = @Domain AND m.Supply = s.Supp
  177. WHERE m.Domain = @Domain AND m.Status = 'I' AND m.Qty > 0 AND IFNULL(m.Location,'') = ''
  178. AND IFNULL(m.Supply,'') <> ''
  179. ORDER BY m.Supply
  180. """,
  181. new List<SugarParameter> { new("@Domain", domain) });
  182. return list;
  183. }
  184. }