IqcInspBillService.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. using Admin.NET.Plugin.AiDOP.MaterialWarehouse.Dto;
  2. namespace Admin.NET.Plugin.AiDOP.MaterialWarehouse;
  3. /// <summary>
  4. /// S5 来料检验单(qms_qcp_inspbill)只读 list 服务 —— 来料检验流程主入口。
  5. ///
  6. /// 数据源:aidopdev.qms_qcp_inspbill 单表直读(不 join 申请主表/明细/SuppMaster/ApprovalFlow/SQE)。
  7. /// 口径:检验单号=FBILLNO;来源单号=lydjbh;物料=FMATERIALCFG/wlmc/ggxh;供应商=gysmc(内嵌);
  8. /// 批次=pch;数量=FRINSQTY/jysl/dhsl/bhgsl;判定=pd(raw);处理方式=clfs(raw);状态=FBILLSTATUS(raw);
  9. /// 检验员=FINSPECTORID;检验起止=FINSPESTARTDATE/FINSPEENDDATE;创建=FCREATETIME。默认排序 FCREATETIME DESC, id DESC。
  10. ///
  11. /// tenant/组织:qms_qcp_inspbill 已补 tenant_id 列,按严格可信租户过滤(需认证,租户仅取认证后 JWT,忽略前端 tenantId);FORGID 当前全 NULL,不做 FORGID 过滤。
  12. /// 本服务仅 SELECT:无新增/编辑/删除/详情/判定录入/主管审核/SQE 处置/仓储通知/
  13. /// 库存事务/流程实例;不写 qms_qcp_inspbill、不改 FBIZTYPE、不碰 ApprovalFlow。pd/clfs/FBILLSTATUS 字典未闭合,raw 展示。
  14. /// </summary>
  15. [ApiDescriptionSettings(Order = 309, Description = "来料检验单列表")]
  16. [Route("api/S5IqcInspBill")]
  17. [NonUnify]
  18. public class IqcInspBillService : IDynamicApiController, ITransient
  19. {
  20. /// <summary>主/系统租户哨兵:超管未选择目标租户时其 JWT TenantId 即此值,拒绝作为业务租户。</summary>
  21. private const long MainTenantId = 1300000000001L;
  22. private readonly ISqlSugarClient _db;
  23. private readonly UserManager _userManager;
  24. public IqcInspBillService(ISqlSugarClient db, UserManager userManager)
  25. {
  26. _db = db;
  27. _userManager = userManager;
  28. }
  29. /// <summary>严格可信租户解析:只来自认证后 JWT;无 Token(&lt;=0) 或超管未选主租户 → 拒绝;不读前端 tenantId、无默认回退。</summary>
  30. private long ResolveTenantOrThrow()
  31. {
  32. var tid = _userManager.TenantId;
  33. if (tid <= 0)
  34. throw Oops.Oh("无法确定当前租户,请重新登录或选择目标租户");
  35. if (_userManager.SuperAdmin && tid == MainTenantId)
  36. throw Oops.Oh("超级管理员操作前必须选择目标租户");
  37. return tid;
  38. }
  39. /// <summary>排序字段白名单(前端 prop → 物理列),杜绝 orderBy 注入。</summary>
  40. private static readonly Dictionary<string, string> OrderByWhitelist = new(StringComparer.OrdinalIgnoreCase)
  41. {
  42. ["billNo"] = "ib.FBILLNO",
  43. ["sourceBillNo"] = "ib.lydjbh",
  44. ["materialCode"] = "ib.FMATERIALCFG",
  45. ["materialName"] = "ib.wlmc",
  46. ["supplierName"] = "ib.gysmc",
  47. ["lotNo"] = "ib.pch",
  48. ["billStatus"] = "ib.FBILLSTATUS",
  49. ["judgement"] = "ib.pd",
  50. ["inspectDate"] = "ib.FINSPESTARTDATE",
  51. ["createTime"] = "ib.FCREATETIME",
  52. };
  53. /// <summary>
  54. /// 来料检验单列表(只读分页查询)。
  55. /// </summary>
  56. [DisplayName("来料检验单列表")]
  57. [HttpGet("list")]
  58. public async Task<object> GetList([FromQuery] IqcInspBillListInput input)
  59. {
  60. var page = input.Page <= 0 ? 1 : input.Page;
  61. var pageSize = input.PageSize <= 0 ? 20 : (input.PageSize > 200 ? 200 : input.PageSize);
  62. var offset = (page - 1) * pageSize;
  63. // 同库分租户:qms_qcp_inspbill 已补 tenant_id 列,按严格可信租户(仅认证后 JWT,忽略前端 tenantId)过滤,杜绝匿名跨租户读。FORGID 全 NULL 不做组织过滤。
  64. var tenantId = ResolveTenantOrThrow();
  65. var where = new List<string> { "ib.tenant_id = @TenantId" };
  66. var pars = new List<SugarParameter> { new("@TenantId", tenantId) };
  67. void AddLike(string? v, string col, string p)
  68. {
  69. if (string.IsNullOrWhiteSpace(v)) return;
  70. where.Add($"{col} LIKE @{p}");
  71. pars.Add(new SugarParameter($"@{p}", $"%{v.Trim()}%"));
  72. }
  73. AddLike(input.BillNo, "ib.FBILLNO", "BillNo");
  74. AddLike(input.SourceBillNo, "ib.lydjbh", "SourceBillNo");
  75. AddLike(input.MaterialCode, "ib.FMATERIALCFG", "MaterialCode");
  76. AddLike(input.MaterialName, "ib.wlmc", "MaterialName");
  77. AddLike(input.SupplierName, "ib.gysmc", "SupplierName");
  78. AddLike(input.LotNo, "ib.pch", "LotNo");
  79. AddLike(input.Inspector, "ib.FINSPECTORID", "Inspector");
  80. if (!string.IsNullOrWhiteSpace(input.BillStatus))
  81. {
  82. where.Add("ib.FBILLSTATUS = @BillStatus");
  83. pars.Add(new SugarParameter("@BillStatus", input.BillStatus.Trim()));
  84. }
  85. if (input.Judgement.HasValue)
  86. {
  87. where.Add("ib.pd = @Judgement");
  88. pars.Add(new SugarParameter("@Judgement", input.Judgement.Value));
  89. }
  90. if (input.ProcessMethod.HasValue)
  91. {
  92. where.Add("ib.clfs = @ProcessMethod");
  93. pars.Add(new SugarParameter("@ProcessMethod", input.ProcessMethod.Value));
  94. }
  95. if (!string.IsNullOrWhiteSpace(input.InspectDateFrom))
  96. {
  97. where.Add("ib.FINSPESTARTDATE >= @InspectDateFrom");
  98. pars.Add(new SugarParameter("@InspectDateFrom", $"{input.InspectDateFrom.Trim()} 00:00:00"));
  99. }
  100. if (!string.IsNullOrWhiteSpace(input.InspectDateTo))
  101. {
  102. where.Add("ib.FINSPESTARTDATE <= @InspectDateTo");
  103. pars.Add(new SugarParameter("@InspectDateTo", $"{input.InspectDateTo.Trim()} 23:59:59"));
  104. }
  105. if (!string.IsNullOrWhiteSpace(input.CreateDateFrom))
  106. {
  107. where.Add("ib.FCREATETIME >= @CreateDateFrom");
  108. pars.Add(new SugarParameter("@CreateDateFrom", $"{input.CreateDateFrom.Trim()} 00:00:00"));
  109. }
  110. if (!string.IsNullOrWhiteSpace(input.CreateDateTo))
  111. {
  112. where.Add("ib.FCREATETIME <= @CreateDateTo");
  113. pars.Add(new SugarParameter("@CreateDateTo", $"{input.CreateDateTo.Trim()} 23:59:59"));
  114. }
  115. var whereSql = string.Join(" AND ", where);
  116. // 排序:白名单回落,方向 asc/desc 回落 desc;默认 FCREATETIME DESC, id DESC。
  117. string orderSql;
  118. if (!string.IsNullOrWhiteSpace(input.OrderBy) && OrderByWhitelist.TryGetValue(input.OrderBy.Trim(), out var col))
  119. {
  120. var dir = string.Equals(input.OrderDir?.Trim(), "asc", StringComparison.OrdinalIgnoreCase) ? "ASC" : "DESC";
  121. orderSql = $"{col} {dir}, ib.id DESC";
  122. }
  123. else
  124. {
  125. orderSql = "ib.FCREATETIME DESC, ib.id DESC";
  126. }
  127. var total = await _db.Ado.GetIntAsync(
  128. $"SELECT COUNT(1) FROM qms_qcp_inspbill ib WHERE {whereSql}", pars);
  129. var list = await _db.Ado.SqlQueryAsync<IqcInspBillListRow>(
  130. $"""
  131. SELECT
  132. ib.id AS Id,
  133. ib.FBILLNO AS BillNo,
  134. ib.lydjbh AS SourceBillNo,
  135. ib.FMATERIALCFG AS MaterialCode,
  136. ib.wlmc AS MaterialName,
  137. ib.ggxh AS Spec,
  138. ib.gysmc AS SupplierName,
  139. ib.pch AS LotNo,
  140. ib.FRINSQTY AS ReportQty,
  141. ib.jysl AS InspectQty,
  142. ib.dhsl AS QualifiedQty,
  143. ib.bhgsl AS UnqualifiedQty,
  144. ib.pd AS Judgement,
  145. ib.clfs AS ProcessMethod,
  146. ib.FBILLSTATUS AS BillStatus,
  147. ib.FINSPECTORID AS Inspector,
  148. ib.FINSPESTARTDATE AS InspectStartDate,
  149. ib.FINSPEENDDATE AS InspectEndDate,
  150. ib.FCREATETIME AS CreateTime,
  151. ib.FORGID AS OrgId
  152. FROM qms_qcp_inspbill ib
  153. WHERE {whereSql}
  154. ORDER BY {orderSql}
  155. LIMIT {pageSize} OFFSET {offset}
  156. """,
  157. pars);
  158. return new { total, page, pageSize, list };
  159. }
  160. /// <summary>
  161. /// 来料检验单详情(只读,按 id 单表直读 qms_qcp_inspbill)。
  162. ///
  163. /// 仅 SELECT 单行:不 join 申请表/明细/SuppMaster/ApprovalFlow/SQE,不查字典,不写任何表。
  164. /// id&lt;=0 或不存在返回 null(HTTP 200)。字典字段 pd/clfs/FBILLSTATUS/pcsq/blwtflid/FBIZTYPE 全 raw。
  165. /// 流程占位 BizType/BizId/BizCode 由本方法拼装,**不查 ApprovalFlowInstance、不生成流程实例**。
  166. /// qms_qcp_inspbill 已补 tenant_id:详情按 id + 当前租户过滤(与 list 口径一致),FORGID 全 NULL 不做组织过滤。
  167. /// </summary>
  168. [DisplayName("来料检验单详情")]
  169. [HttpGet("detail")]
  170. public async Task<object?> GetDetail([FromQuery] long id)
  171. {
  172. if (id <= 0) return null;
  173. // 同库分租户:详情按 id + 严格可信租户(仅认证后 JWT,忽略前端 tenantId)过滤,杜绝匿名跨租户越权取单(最高优先越权点)。
  174. var tenantId = ResolveTenantOrThrow();
  175. var pars = new List<SugarParameter> { new("@id", id), new("@TenantId", tenantId) };
  176. var list = await _db.Ado.SqlQueryAsync<IqcInspBillDetailRow>(
  177. """
  178. SELECT
  179. ib.id AS Id,
  180. ib.FBILLNO AS BillNo,
  181. ib.lydjbh AS SourceBillNo,
  182. ib.FBILLSTATUS AS BillStatus,
  183. ib.FBIZTYPE AS BizTypeRaw,
  184. ib.FORGID AS OrgId,
  185. ib.FCREATETIME AS CreateTime,
  186. ib.FCREATORID AS CreateUserId,
  187. ib.FMODIFYTIME AS UpdateTime,
  188. ib.FMODIFIERID AS UpdateUserId,
  189. ib.FMATERIALCFG AS MaterialCode,
  190. ib.wlmc AS MaterialName,
  191. ib.ggxh AS Spec,
  192. ib.gysmc AS SupplierName,
  193. ib.pch AS LotNo,
  194. ib.jgbh AS InspectionSpecNo,
  195. ib.jgbb AS InspectionSpecVersion,
  196. ib.jgsxrq AS InspectionSpecEffectiveDate,
  197. ib.FRINSQTY AS ReportQty,
  198. ib.jysl AS InspectQty,
  199. ib.SampleQty AS SampleQty,
  200. ib.TestQty AS TestQty,
  201. ib.dhsl AS QualifiedQty,
  202. ib.bhgsl AS UnqualifiedQty,
  203. ib.phsl AS DestructiveQty,
  204. ib.lysl AS RetainSampleQty,
  205. ib.FINSPECTORID AS Inspector,
  206. ib.jyr AS InspectorName,
  207. ib.FINSPESTARTDATE AS InspectStartDate,
  208. ib.FINSPEENDDATE AS InspectEndDate,
  209. ib.pd AS Judgement,
  210. ib.clfs AS ProcessMethod,
  211. ib.UnqualifiedDescription AS UnqualifiedDescription,
  212. ib.thyy AS ReturnReason,
  213. ib.blwtflid AS DefectCategoryId,
  214. ib.blwtflmc AS DefectCategoryName,
  215. ib.pcsq AS DeviationApply,
  216. ib.pcsm AS DeviationDescription,
  217. ib.leader AS Leader,
  218. ib.FAUDITORID AS AuditorId,
  219. ib.FAUDITDATE AS AuditDate
  220. FROM qms_qcp_inspbill ib
  221. WHERE ib.id = @id AND ib.tenant_id = @TenantId
  222. LIMIT 1
  223. """,
  224. pars);
  225. var row = list.FirstOrDefault();
  226. if (row == null) return null;
  227. // 流程占位:固定 BizType、BizId=id、BizCode=FBILLNO,供后续 C'/D'/E' 批次接入 ApprovalFlow 用。本批不查/不建流程实例。
  228. row.BizType = "S5_IQC_INSPBILL";
  229. row.BizId = row.Id;
  230. row.BizCode = row.BillNo;
  231. row.FlowHint = "流程将在后续 C'/D'/E' 批次接入";
  232. return row;
  233. }
  234. }