PurchaseReceiptService.cs 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. using Admin.NET.Plugin.AiDOP.MaterialWarehouse.Dto;
  2. namespace Admin.NET.Plugin.AiDOP.MaterialWarehouse;
  3. /// <summary>
  4. /// S5 采购收货单 只读 list 服务。
  5. ///
  6. /// 数据源:DOP 数据中台标准层 mdp_std_purchase_receipt(明细粒度 domain#receiver#line)。
  7. /// 由 PurchaseReceiptMdpSyncService 从 aidopdev.PurOrdRctDetail/PurOrdRctMaster RctType='rc' 同步标准化而来。
  8. ///
  9. /// 本服务仅 SELECT:无新增/编辑/删除/收货确认/入库写入/库存事务/状态流转;无 detail(扁平列表)。
  10. /// </summary>
  11. [ApiDescriptionSettings(Order = 305, Description = "采购收货单")]
  12. [Route("api/PurchaseReceipt")]
  13. [NonUnify]
  14. public class PurchaseReceiptService : IDynamicApiController, ITransient
  15. {
  16. /// <summary>主/系统租户哨兵:超管未选择目标租户时其 JWT TenantId 即此值,拒绝作为业务租户。</summary>
  17. private const long MainTenantId = 1300000000001L;
  18. private readonly ISqlSugarClient _db;
  19. private readonly UserManager _userManager;
  20. public PurchaseReceiptService(ISqlSugarClient db, UserManager userManager)
  21. {
  22. _db = db;
  23. _userManager = userManager;
  24. }
  25. /// <summary>严格可信租户解析:只来自认证后 JWT;无 Token(&lt;=0) 或超管未选主租户 → 拒绝;不读前端 tenantId、无默认回退。</summary>
  26. private long ResolveTenantOrThrow()
  27. {
  28. var tid = _userManager.TenantId;
  29. if (tid <= 0)
  30. throw Oops.Oh("无法确定当前租户,请重新登录或选择目标租户");
  31. if (_userManager.SuperAdmin && tid == MainTenantId)
  32. throw Oops.Oh("超级管理员操作前必须选择目标租户");
  33. return tid;
  34. }
  35. /// <summary>
  36. /// 采购收货单列表(只读分页查询)。
  37. /// </summary>
  38. [DisplayName("采购收货单列表")]
  39. [HttpGet("list")]
  40. public async Task<object> GetList([FromQuery] PurchaseReceiptListInput input)
  41. {
  42. var page = input.Page <= 0 ? 1 : input.Page;
  43. var pageSize = input.PageSize <= 0 ? 10 : input.PageSize;
  44. var offset = (page - 1) * pageSize;
  45. var where = new List<string> { "1=1" };
  46. var pars = new List<SugarParameter>();
  47. // 强制租户过滤:租户只来自认证后可信 JWT,忽略前端 input.TenantId,杜绝跨租户可见。
  48. var tid = ResolveTenantOrThrow();
  49. where.Add("m.tenant_id = @TenantId");
  50. pars.Add(new SugarParameter("@TenantId", tid));
  51. if (!string.IsNullOrWhiteSpace(input.Supp))
  52. {
  53. where.Add("(m.supp LIKE @Supp OR m.sort_name LIKE @Supp)");
  54. pars.Add(new SugarParameter("@Supp", $"%{input.Supp.Trim()}%"));
  55. }
  56. if (!string.IsNullOrWhiteSpace(input.ItemNum))
  57. {
  58. where.Add("m.item_num LIKE @ItemNum");
  59. pars.Add(new SugarParameter("@ItemNum", $"%{input.ItemNum.Trim()}%"));
  60. }
  61. if (!string.IsNullOrWhiteSpace(input.RctDateFrom))
  62. {
  63. where.Add("m.rct_date >= @RctDateFrom");
  64. pars.Add(new SugarParameter("@RctDateFrom", $"{input.RctDateFrom.Trim()} 00:00:00"));
  65. }
  66. if (!string.IsNullOrWhiteSpace(input.RctDateTo))
  67. {
  68. where.Add("m.rct_date <= @RctDateTo");
  69. pars.Add(new SugarParameter("@RctDateTo", $"{input.RctDateTo.Trim()} 23:59:59"));
  70. }
  71. if (!string.IsNullOrWhiteSpace(input.PurOrd))
  72. {
  73. where.Add("m.pur_ord LIKE @PurOrd");
  74. pars.Add(new SugarParameter("@PurOrd", $"%{input.PurOrd.Trim()}%"));
  75. }
  76. if (!string.IsNullOrWhiteSpace(input.Receiver))
  77. {
  78. where.Add("m.receiver LIKE @Receiver");
  79. pars.Add(new SugarParameter("@Receiver", $"%{input.Receiver.Trim()}%"));
  80. }
  81. if (!string.IsNullOrWhiteSpace(input.SalesJob))
  82. {
  83. where.Add("m.sales_job LIKE @SalesJob");
  84. pars.Add(new SugarParameter("@SalesJob", $"%{input.SalesJob.Trim()}%"));
  85. }
  86. if (!string.IsNullOrWhiteSpace(input.Req))
  87. {
  88. where.Add("m.req LIKE @Req");
  89. pars.Add(new SugarParameter("@Req", $"%{input.Req.Trim()}%"));
  90. }
  91. if (!string.IsNullOrWhiteSpace(input.DopReq))
  92. {
  93. where.Add("m.dop_req LIKE @DopReq");
  94. pars.Add(new SugarParameter("@DopReq", $"%{input.DopReq.Trim()}%"));
  95. }
  96. var whereSql = string.Join(" AND ", where);
  97. var total = await _db.Ado.GetIntAsync(
  98. $"SELECT COUNT(1) FROM mdp_std_purchase_receipt m WHERE {whereSql}", pars);
  99. var list = await _db.Ado.SqlQueryAsync<PurchaseReceiptListRow>(
  100. $"""
  101. SELECT
  102. m.id AS Id,
  103. m.receiver AS Receiver,
  104. m.line AS Line,
  105. m.rct_date AS RctDate,
  106. m.supp AS Supp,
  107. m.sort_name AS SortName,
  108. m.item_num AS ItemNum,
  109. m.item_name AS ItemName,
  110. m.item_spec AS ItemSpec,
  111. m.um AS Um,
  112. m.qty_ordered AS QtyOrdered,
  113. m.qty_received AS QtyReceived,
  114. m.lot_serial AS LotSerial,
  115. m.location AS Location,
  116. m.pur_ord AS PurOrd,
  117. m.sales_job AS SalesJob,
  118. m.address1 AS Address1,
  119. m.req AS Req,
  120. m.dop_req AS DopReq,
  121. m.ord_nbr AS OrdNbr
  122. FROM mdp_std_purchase_receipt m
  123. WHERE {whereSql}
  124. ORDER BY {BuildOrderBy(input.OrderBy, input.OrderDir)}
  125. LIMIT {pageSize} OFFSET {offset}
  126. """,
  127. pars);
  128. return new { total, page, pageSize, list };
  129. }
  130. /// <summary>
  131. /// 排序白名单:仅允许按已展示列排序,杜绝 SQL 注入。
  132. /// </summary>
  133. private static string BuildOrderBy(string? orderBy, string? orderDir)
  134. {
  135. var column = orderBy switch
  136. {
  137. "receiver" => "m.receiver",
  138. "rctDate" => "m.rct_date",
  139. "itemNum" => "m.item_num",
  140. "sortName" => "m.sort_name",
  141. "qtyOrdered" => "m.qty_ordered",
  142. "qtyReceived" => "m.qty_received",
  143. "purOrd" => "m.pur_ord",
  144. "salesJob" => "m.sales_job",
  145. _ => "m.rct_date",
  146. };
  147. var direction = string.Equals(orderDir, "asc", StringComparison.OrdinalIgnoreCase) ? "ASC" : "DESC";
  148. return $"{column} {direction}, m.id DESC";
  149. }
  150. }