OutsourceIssueService.cs 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. using Admin.NET.Plugin.AiDOP.MaterialWarehouse.Dto;
  2. namespace Admin.NET.Plugin.AiDOP.MaterialWarehouse;
  3. /// <summary>
  4. /// S5 委外发料单 只读 list/detail 服务。
  5. ///
  6. /// 数据源:DOP 数据中台标准层 mdp_std_outsource_issue(头)/ mdp_std_outsource_issue_detail(明细)。
  7. /// 由 OutsourceIssueMdpSyncService 从 aidopdev.NbrMaster/NbrDetail Type='CA' 同步标准化而来。
  8. ///
  9. /// 本服务仅 SELECT:无新增/编辑/删除/写入;明细只暴露已确认 9 列(发料数量/已发数/批次号 3 候选列后置)。
  10. /// 当前上游未产 CA 单据,标准层为空,list 返回空数组、detail 返回 null(前端空态)。
  11. /// </summary>
  12. [ApiDescriptionSettings(Order = 304, Description = "委外发料单")]
  13. [Route("api/OutsourceIssue")]
  14. [AllowAnonymous]
  15. [NonUnify]
  16. public class OutsourceIssueService : IDynamicApiController, ITransient
  17. {
  18. private readonly ISqlSugarClient _db;
  19. public OutsourceIssueService(ISqlSugarClient db)
  20. {
  21. _db = db;
  22. }
  23. /// <summary>
  24. /// 委外发料单列表(只读分页查询)。
  25. /// </summary>
  26. [DisplayName("委外发料单列表")]
  27. [HttpGet("list")]
  28. public async Task<object> GetList([FromQuery] OutsourceIssueListInput input)
  29. {
  30. var page = input.Page <= 0 ? 1 : input.Page;
  31. var pageSize = input.PageSize <= 0 ? 10 : input.PageSize;
  32. var offset = (page - 1) * pageSize;
  33. var where = new List<string> { "1=1" };
  34. var pars = new List<SugarParameter>();
  35. if (input.TenantId is > 0)
  36. {
  37. where.Add("m.tenant_id = @TenantId");
  38. pars.Add(new SugarParameter("@TenantId", input.TenantId));
  39. }
  40. if (!string.IsNullOrWhiteSpace(input.BillNo))
  41. {
  42. where.Add("m.bill_no LIKE @BillNo");
  43. pars.Add(new SugarParameter("@BillNo", $"%{input.BillNo.Trim()}%"));
  44. }
  45. if (!string.IsNullOrWhiteSpace(input.OutsourceNo))
  46. {
  47. where.Add("m.outsource_no LIKE @OutsourceNo");
  48. pars.Add(new SugarParameter("@OutsourceNo", $"%{input.OutsourceNo.Trim()}%"));
  49. }
  50. if (!string.IsNullOrWhiteSpace(input.IssueDateStart))
  51. {
  52. where.Add("m.issue_date >= @IssueDateStart");
  53. pars.Add(new SugarParameter("@IssueDateStart", $"{input.IssueDateStart.Trim()} 00:00:00"));
  54. }
  55. if (!string.IsNullOrWhiteSpace(input.IssueDateEnd))
  56. {
  57. where.Add("m.issue_date <= @IssueDateEnd");
  58. pars.Add(new SugarParameter("@IssueDateEnd", $"{input.IssueDateEnd.Trim()} 23:59:59"));
  59. }
  60. var whereSql = string.Join(" AND ", where);
  61. var total = await _db.Ado.GetIntAsync(
  62. $"SELECT COUNT(1) FROM mdp_std_outsource_issue m WHERE {whereSql}", pars);
  63. var list = await _db.Ado.SqlQueryAsync<OutsourceIssueListRow>(
  64. $"""
  65. SELECT
  66. m.id AS Id,
  67. m.bill_no AS Nbr,
  68. m.issue_date AS Date,
  69. m.status_desc AS StatusDescr,
  70. m.outsource_no AS Address,
  71. m.work_order AS WorkOrd,
  72. TRIM(CONCAT(IFNULL(m.department_code,''), ' ', IFNULL(m.department_name,''))) AS DepartmentDescr,
  73. m.issuer AS User1,
  74. m.remark AS Remark,
  75. m.create_user AS CreateUser,
  76. m.source_create_time AS CreateTime
  77. FROM mdp_std_outsource_issue m
  78. WHERE {whereSql}
  79. ORDER BY {BuildOrderBy(input.SortField, input.SortOrder)}
  80. LIMIT {pageSize} OFFSET {offset}
  81. """,
  82. pars);
  83. return new { total, page, pageSize, list };
  84. }
  85. /// <summary>
  86. /// 委外发料单详情(只读:头 + 明细)。查不到返回 null。
  87. /// </summary>
  88. [DisplayName("委外发料单详情")]
  89. [HttpGet("detail")]
  90. public async Task<OutsourceIssueDetailDto?> GetDetail([FromQuery] long id, [FromQuery] long? tenantId)
  91. {
  92. if (id <= 0) return null;
  93. var headPars = new List<SugarParameter> { new("@Id", id) };
  94. var headWhere = "m.id = @Id";
  95. if (tenantId is > 0)
  96. {
  97. headWhere += " AND m.tenant_id = @TenantId";
  98. headPars.Add(new SugarParameter("@TenantId", tenantId));
  99. }
  100. var head = await _db.Ado.SqlQuerySingleAsync<OutsourceIssueDetailDto>(
  101. $"""
  102. SELECT
  103. m.id AS Id,
  104. m.bill_no AS Nbr,
  105. m.issue_date AS Date,
  106. m.outsource_no AS Address,
  107. m.work_order AS WorkOrd,
  108. TRIM(CONCAT(IFNULL(m.department_code,''), ' ', IFNULL(m.department_name,''))) AS DepartmentDescr,
  109. m.issuer AS User1,
  110. m.status_desc AS StatusDescr,
  111. m.remark AS Remark,
  112. m.create_user AS CreateUser,
  113. m.source_create_time AS CreateTime
  114. FROM mdp_std_outsource_issue m
  115. WHERE {headWhere}
  116. LIMIT 1
  117. """,
  118. headPars);
  119. if (head == null || head.Id <= 0) return null;
  120. head.Lines = await _db.Ado.SqlQueryAsync<OutsourceIssueDetailLineDto>(
  121. """
  122. SELECT
  123. d.id AS Id,
  124. d.line AS Line,
  125. d.item_num AS ItemNum,
  126. d.item_name AS ItemName,
  127. d.um AS Um,
  128. d.qty_ord AS QtyOrd,
  129. d.location_from AS LocationFrom,
  130. d.location_to AS LocationTo,
  131. d.status AS Status,
  132. d.remark AS Remark
  133. FROM mdp_std_outsource_issue_detail d
  134. WHERE d.std_head_id = @HeadId
  135. ORDER BY d.line ASC, d.id ASC
  136. """,
  137. new List<SugarParameter> { new("@HeadId", head.Id) });
  138. return head;
  139. }
  140. /// <summary>
  141. /// 排序白名单:仅允许按已展示列排序,杜绝 SQL 注入。
  142. /// </summary>
  143. private static string BuildOrderBy(string? sortField, string? sortOrder)
  144. {
  145. var column = sortField switch
  146. {
  147. "billNo" => "m.bill_no",
  148. "issueDate" => "m.issue_date",
  149. "sourceCreateTime" => "m.source_create_time",
  150. _ => "m.issue_date",
  151. };
  152. var direction = string.Equals(sortOrder, "asc", StringComparison.OrdinalIgnoreCase) ? "ASC" : "DESC";
  153. return $"{column} {direction}, m.id DESC";
  154. }
  155. }