IpqcInspectionService.cs 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. using Admin.NET.Plugin.AiDOP.Manufacturing.Dto;
  2. namespace Admin.NET.Plugin.AiDOP.Manufacturing;
  3. /// <summary>
  4. /// S6 过程检验单(IPQC)只读 list 服务。
  5. ///
  6. /// 数据源:DOP 数据中台标准层 mdp_std_ipqc_inspection(头)。
  7. /// 由 IpqcInspectionMdpSyncService 从归口后的 aidopdev.qms_gcjyd(过程检验单头)同步标准化而来。
  8. ///
  9. /// 本服务仅 SELECT:无新增/编辑/删除/写入。
  10. /// 当前上游尚未抽数,归口源表 qms_gcjyd 为空 → 标准层为空 → list 返回空数组(前端空态)。
  11. /// 明细(qms_gcjydzb)与详情不在本批范围,留待 IPQC-INSPECTION-DETAIL-READONLY-1。
  12. /// </summary>
  13. [ApiDescriptionSettings(Order = 306, Description = "过程检验单")]
  14. [Route("api/IpqcInspection")]
  15. [NonUnify]
  16. public class IpqcInspectionService : IDynamicApiController, ITransient
  17. {
  18. private readonly ISqlSugarClient _db;
  19. private readonly UserManager _userManager;
  20. public IpqcInspectionService(ISqlSugarClient db, UserManager userManager)
  21. {
  22. _db = db;
  23. _userManager = userManager;
  24. }
  25. /// <summary>严格可信租户解析,见 <see cref="Infrastructure.AidopTenantScope.ResolveOrThrow"/>。</summary>
  26. private long ResolveTenantOrThrow() => Infrastructure.AidopTenantScope.ResolveOrThrow(_userManager);
  27. /// <summary>
  28. /// 过程检验单列表(只读分页查询)。
  29. /// </summary>
  30. [DisplayName("过程检验单列表")]
  31. [HttpGet("list")]
  32. public async Task<object> GetList([FromQuery] IpqcInspectionListInput input)
  33. {
  34. var page = input.Page <= 0 ? 1 : input.Page;
  35. var pageSize = input.PageSize <= 0 ? 10 : input.PageSize;
  36. var offset = (page - 1) * pageSize;
  37. var where = new List<string> { "1=1" };
  38. var pars = new List<SugarParameter>();
  39. // 强制租户过滤:qms_gcjyd/qms_gcjydzb 全库共享无 tenant,标准层按源 tenant_id 落库。
  40. // 租户只来自认证后的可信上下文(严格 JWT),忽略前端传入的 input.TenantId,杜绝跨租户可见。
  41. var tid = ResolveTenantOrThrow();
  42. where.Add("m.tenant_id = @TenantId");
  43. pars.Add(new SugarParameter("@TenantId", tid));
  44. if (!string.IsNullOrWhiteSpace(input.BillNo))
  45. {
  46. where.Add("m.bill_no LIKE @BillNo");
  47. pars.Add(new SugarParameter("@BillNo", $"%{input.BillNo.Trim()}%"));
  48. }
  49. if (!string.IsNullOrWhiteSpace(input.ProductionBatchNo))
  50. {
  51. where.Add("m.production_batch_no LIKE @ProductionBatchNo");
  52. pars.Add(new SugarParameter("@ProductionBatchNo", $"%{input.ProductionBatchNo.Trim()}%"));
  53. }
  54. var whereSql = string.Join(" AND ", where);
  55. var total = await _db.Ado.GetIntAsync(
  56. $"SELECT COUNT(1) FROM mdp_std_ipqc_inspection m WHERE {whereSql}", pars);
  57. var list = await _db.Ado.SqlQueryAsync<IpqcInspectionListRow>(
  58. $"""
  59. SELECT
  60. m.id AS Id,
  61. m.bill_no AS BillNo,
  62. m.product_model AS ProductModel,
  63. m.production_batch_no AS ProductionBatchNo,
  64. m.production_work_order AS ProductionWorkOrder,
  65. m.result_judgement AS ResultJudgement,
  66. m.attachment AS Attachment,
  67. m.remark AS Remark,
  68. m.inspector AS Inspector,
  69. m.process_code AS ProcessCode,
  70. m.process_name AS ProcessName,
  71. m.production_person AS ProductionPerson,
  72. m.sample_qty AS SampleQty,
  73. m.form_no AS FormNo,
  74. m.version_no AS VersionNo,
  75. m.effective_date AS EffectiveDate,
  76. m.material_code AS MaterialCode,
  77. m.material_name AS MaterialName,
  78. m.inspec_standard_version AS InspecStandardVersion,
  79. m.inspec_standard_code AS InspecStandardCode,
  80. CAST(m.source_row_id AS SIGNED) AS SourceRowId,
  81. f.business_status AS BusinessStatus
  82. FROM mdp_std_ipqc_inspection m
  83. LEFT JOIN ado_s6_ipqc_inspection_flow_state f
  84. ON f.inspection_id = CAST(m.source_row_id AS SIGNED)
  85. AND f.tenant_id = m.tenant_id
  86. AND f.IsDelete = 0
  87. WHERE {whereSql}
  88. ORDER BY {BuildOrderBy(input.SortField, input.SortOrder)}
  89. LIMIT {pageSize} OFFSET {offset}
  90. """,
  91. pars);
  92. return new { total, page, pageSize, list };
  93. }
  94. /// <summary>
  95. /// 过程检验单详情(只读:头 + 明细)。查不到返回 null。
  96. /// </summary>
  97. [DisplayName("过程检验单详情")]
  98. [HttpGet("detail")]
  99. public async Task<IpqcInspectionDetailDto?> GetDetail([FromQuery] long id)
  100. {
  101. if (id <= 0) return null;
  102. // 强制租户过滤(同 GetList):租户只来自认证后的可信上下文,忽略前端 tenantId,杜绝跨租户读取详情。
  103. var tid = ResolveTenantOrThrow();
  104. var headPars = new List<SugarParameter> { new("@Id", id), new("@TenantId", tid) };
  105. var headWhere = "m.id = @Id AND m.tenant_id = @TenantId";
  106. var head = await _db.Ado.SqlQuerySingleAsync<IpqcInspectionDetailDto>(
  107. $"""
  108. SELECT
  109. m.id AS Id,
  110. m.bill_no AS BillNo,
  111. m.product_model AS ProductModel,
  112. m.production_batch_no AS ProductionBatchNo,
  113. m.production_work_order AS ProductionWorkOrder,
  114. m.result_judgement AS ResultJudgement,
  115. m.attachment AS Attachment,
  116. m.remark AS Remark,
  117. m.inspector AS Inspector,
  118. m.process_code AS ProcessCode,
  119. m.process_name AS ProcessName,
  120. m.production_person AS ProductionPerson,
  121. m.sample_qty AS SampleQty,
  122. m.form_no AS FormNo,
  123. m.version_no AS VersionNo,
  124. m.effective_date AS EffectiveDate,
  125. m.material_code AS MaterialCode,
  126. m.material_name AS MaterialName,
  127. m.inspec_standard_version AS InspecStandardVersion,
  128. m.inspec_standard_code AS InspecStandardCode,
  129. m.inspection_status AS InspectionStatus,
  130. CAST(m.source_row_id AS SIGNED) AS SourceRowId
  131. FROM mdp_std_ipqc_inspection m
  132. WHERE {headWhere}
  133. LIMIT 1
  134. """,
  135. headPars);
  136. if (head == null || head.Id <= 0) return null;
  137. head.Lines = await _db.Ado.SqlQueryAsync<IpqcInspectionDetailLineDto>(
  138. """
  139. SELECT
  140. d.id AS Id,
  141. d.inspection_item AS InspectionItem,
  142. d.inspection_basis AS InspectionBasis,
  143. d.technical_standard AS TechnicalStandard,
  144. d.inspection_method AS InspectionMethod,
  145. d.upper_limit AS UpperLimit,
  146. d.lower_limit AS LowerLimit,
  147. d.process_code AS ProcessCode,
  148. d.process_name AS ProcessName,
  149. d.measurement_instrument AS MeasurementInstrument,
  150. d.inspector AS Inspector,
  151. d.inspection_time AS InspectionTime,
  152. d.sample_qty AS SampleQty,
  153. d.inspection_qty AS InspectionQty,
  154. d.unqualified_qty AS UnqualifiedQty,
  155. d.result_judgement AS ResultJudgement,
  156. d.non_numeric_ok AS NonNumericOk,
  157. d.non_numeric_ng AS NonNumericNg,
  158. d.remark AS Remark,
  159. d.attachment AS Attachment
  160. FROM mdp_std_ipqc_inspection_detail d
  161. WHERE d.std_head_id = @HeadId AND d.tenant_id = @TenantId
  162. ORDER BY d.id ASC
  163. """,
  164. new List<SugarParameter> { new("@HeadId", head.Id), new("@TenantId", tid) });
  165. return head;
  166. }
  167. /// <summary>
  168. /// 排序白名单:仅允许按已展示列排序,杜绝 SQL 注入。
  169. /// </summary>
  170. private static string BuildOrderBy(string? sortField, string? sortOrder)
  171. {
  172. var column = sortField switch
  173. {
  174. "billNo" => "m.bill_no",
  175. "effectiveDate" => "m.effective_date",
  176. _ => "m.effective_date",
  177. };
  178. var direction = string.Equals(sortOrder, "asc", StringComparison.OrdinalIgnoreCase) ? "ASC" : "DESC";
  179. return $"{column} {direction}, m.id DESC";
  180. }
  181. }