ProductionIssueService.cs 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  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_production_issue(头级)。
  7. /// 由 ProductionIssueMdpSyncService 从 aidopdev.NbrMaster Type='SM' 同步标准化而来。
  8. ///
  9. /// 本服务仅 SELECT:无新增/编辑/删除/发料确认/库存事务/状态流转/明细。
  10. /// 领料单为头级列表(一单一行),不 JOIN 明细,无重复。
  11. /// </summary>
  12. [ApiDescriptionSettings(Order = 305, Description = "生产领料单")]
  13. [Route("api/ProductionIssue")]
  14. [NonUnify]
  15. public class ProductionIssueService : IDynamicApiController, ITransient
  16. {
  17. /// <summary>主/系统租户哨兵:超管未选择目标租户时其 JWT TenantId 即此值,拒绝作为业务租户。</summary>
  18. private const long MainTenantId = 1300000000001L;
  19. private readonly ISqlSugarClient _db;
  20. private readonly UserManager _userManager;
  21. public ProductionIssueService(ISqlSugarClient db, UserManager userManager)
  22. {
  23. _db = db;
  24. _userManager = userManager;
  25. }
  26. /// <summary>严格可信租户解析:只来自认证后 JWT;无 Token(&lt;=0) 或超管未选主租户 → 拒绝;不读前端 tenantId、无默认回退。</summary>
  27. private long ResolveTenantOrThrow()
  28. {
  29. var tid = _userManager.TenantId;
  30. if (tid <= 0)
  31. throw Oops.Oh("无法确定当前租户,请重新登录或选择目标租户");
  32. if (_userManager.SuperAdmin && tid == MainTenantId)
  33. throw Oops.Oh("超级管理员操作前必须选择目标租户");
  34. return tid;
  35. }
  36. /// <summary>
  37. /// 生产领料单列表(只读分页查询)。
  38. /// </summary>
  39. [DisplayName("生产领料单列表")]
  40. [HttpGet("list")]
  41. public async Task<object> GetList([FromQuery] ProductionIssueListInput input)
  42. {
  43. var page = input.Page <= 0 ? 1 : input.Page;
  44. var pageSize = input.PageSize <= 0 ? 10 : input.PageSize;
  45. var offset = (page - 1) * pageSize;
  46. var where = new List<string> { "1=1" };
  47. var pars = new List<SugarParameter>();
  48. // 强制租户过滤:租户只来自认证后可信 JWT,忽略前端 input.TenantId,杜绝跨租户可见。
  49. var tid = ResolveTenantOrThrow();
  50. where.Add("m.tenant_id = @TenantId");
  51. pars.Add(new SugarParameter("@TenantId", tid));
  52. if (!string.IsNullOrWhiteSpace(input.Nbr))
  53. {
  54. where.Add("m.nbr LIKE @Nbr");
  55. pars.Add(new SugarParameter("@Nbr", $"%{input.Nbr.Trim()}%"));
  56. }
  57. if (!string.IsNullOrWhiteSpace(input.WorkOrd))
  58. {
  59. where.Add("m.work_ord LIKE @WorkOrd");
  60. pars.Add(new SugarParameter("@WorkOrd", $"%{input.WorkOrd.Trim()}%"));
  61. }
  62. if (!string.IsNullOrWhiteSpace(input.Status))
  63. {
  64. where.Add("m.status = @Status");
  65. pars.Add(new SugarParameter("@Status", input.Status.Trim()));
  66. }
  67. if (!string.IsNullOrWhiteSpace(input.ProdLine))
  68. {
  69. where.Add("m.prod_line LIKE @ProdLine");
  70. pars.Add(new SugarParameter("@ProdLine", $"%{input.ProdLine.Trim()}%"));
  71. }
  72. if (!string.IsNullOrWhiteSpace(input.DateFrom))
  73. {
  74. where.Add("m.issue_date >= @DateFrom");
  75. pars.Add(new SugarParameter("@DateFrom", $"{input.DateFrom.Trim()} 00:00:00"));
  76. }
  77. if (!string.IsNullOrWhiteSpace(input.DateTo))
  78. {
  79. where.Add("m.issue_date <= @DateTo");
  80. pars.Add(new SugarParameter("@DateTo", $"{input.DateTo.Trim()} 23:59:59"));
  81. }
  82. var whereSql = string.Join(" AND ", where);
  83. var total = await _db.Ado.GetIntAsync(
  84. $"SELECT COUNT(1) FROM mdp_std_production_issue m WHERE {whereSql}", pars);
  85. var list = await _db.Ado.SqlQueryAsync<ProductionIssueListRow>(
  86. $"""
  87. SELECT
  88. m.id AS Id,
  89. m.nbr AS Nbr,
  90. m.issue_date AS IssueDate,
  91. m.status_desc AS StatusDesc,
  92. m.work_ord AS WorkOrd,
  93. m.department_desc AS DepartmentDesc,
  94. m.prod_line AS ProdLine,
  95. m.qty_ord AS QtyOrd,
  96. m.issue_user AS IssueUser,
  97. m.remark AS Remark,
  98. m.create_user AS CreateUser,
  99. m.source_create_time AS CreateTime
  100. FROM mdp_std_production_issue m
  101. WHERE {whereSql}
  102. ORDER BY {BuildOrderBy(input.OrderBy, input.OrderDir)}
  103. LIMIT {pageSize} OFFSET {offset}
  104. """,
  105. pars);
  106. return new { total, page, pageSize, list };
  107. }
  108. /// <summary>
  109. /// 排序白名单:仅允许按已展示列排序,杜绝 SQL 注入;非法字段回落默认。
  110. /// </summary>
  111. private static string BuildOrderBy(string? orderBy, string? orderDir)
  112. {
  113. var column = orderBy switch
  114. {
  115. "nbr" => "m.nbr",
  116. "issueDate" => "m.issue_date",
  117. "status" => "m.status",
  118. "workOrd" => "m.work_ord",
  119. "prodLine" => "m.prod_line",
  120. "createTime" => "m.source_create_time",
  121. _ => "m.issue_date",
  122. };
  123. var direction = string.Equals(orderDir, "asc", StringComparison.OrdinalIgnoreCase) ? "ASC" : "DESC";
  124. return $"{column} {direction}, m.id DESC";
  125. }
  126. }