WorkOrderProgressDashboardService.cs 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. namespace Admin.NET.Plugin.AiDOP.Production;
  2. /// <summary>
  3. /// 工单执行进度看板(列表)📊
  4. /// 路由前缀:<c>/api/Production/work-order-progress/...</c>
  5. ///
  6. /// 【租户安全边界】租户一律经 <see cref="AidopTenantScope.ResolveOrThrow"/> 从认证后 JWT 解析,
  7. /// 无有效租户即拒绝,不读前端 tenantId、无默认回退(原实现:宽松解析 <c>AidopTenantHelper.Resolve</c>,
  8. /// 可被 <c>?tenantId=</c> query 覆盖并有默认租户兜底)。
  9. /// </summary>
  10. [ApiDescriptionSettings(Order = 266, Description = "工单执行进度看板")]
  11. [Route("api/Production")]
  12. [NonUnify]
  13. public class WorkOrderProgressDashboardService : IDynamicApiController, ITransient
  14. {
  15. private readonly ISqlSugarClient _db;
  16. private readonly UserManager _userManager;
  17. public WorkOrderProgressDashboardService(ISqlSugarClient db, UserManager userManager)
  18. {
  19. _db = db;
  20. _userManager = userManager;
  21. }
  22. /// <summary>工单执行进度看板分页列表</summary>
  23. [DisplayName("工单执行进度看板列表")]
  24. [HttpGet("work-order-progress/list")]
  25. public async Task<object> GetList([FromQuery] WorkOrderProgressDashboardListInput input)
  26. {
  27. const string C = "utf8mb4_general_ci";
  28. var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
  29. var pars = new List<SugarParameter> { new("@TenantId", tenantId) };
  30. var innerWhere = new List<string>
  31. {
  32. "IFNULL(wm.Status,'') <> ''",
  33. "wm.tenant_id = @TenantId"
  34. };
  35. if (!string.IsNullOrWhiteSpace(input.WorkOrd))
  36. {
  37. innerWhere.Add($"(wm.WorkOrd COLLATE {C}) LIKE @WorkOrd");
  38. pars.Add(new SugarParameter("@WorkOrd", $"%{input.WorkOrd.Trim()}%"));
  39. }
  40. if (!string.IsNullOrWhiteSpace(input.ItemNum))
  41. {
  42. innerWhere.Add($"(wm.ItemNum COLLATE {C}) = @ItemNum");
  43. pars.Add(new SugarParameter("@ItemNum", input.ItemNum.Trim()));
  44. }
  45. if (!string.IsNullOrWhiteSpace(input.OrdDateFrom))
  46. {
  47. innerWhere.Add("DATE(wm.OrdDate) >= @OrdDateFrom");
  48. pars.Add(new SugarParameter("@OrdDateFrom", input.OrdDateFrom.Trim()));
  49. }
  50. var baseSql = BuildListBaseSql(string.Join(" AND ", innerWhere), C);
  51. var offset = (input.Page - 1) * input.PageSize;
  52. var total = await _db.Ado.GetIntAsync(
  53. $"SELECT COUNT(*) FROM ({baseSql}) AS t", pars);
  54. var list = await _db.Ado.SqlQueryAsync<WorkOrderProgressDashboardListRow>(
  55. $"SELECT * FROM ({baseSql}) AS t ORDER BY t.OrdDate DESC, t.Id DESC LIMIT {input.PageSize} OFFSET {offset}",
  56. pars);
  57. return new { total, page = input.Page, pageSize = input.PageSize, list };
  58. }
  59. /// <summary>看板侧工序行匹配:工单号 + 主表租户。</summary>
  60. private static string KanbanRoutingMatch(string C) =>
  61. $"r.WorkOrd COLLATE {C} = wm.WorkOrd COLLATE {C} AND NULLIF(r.tenant_id, 0) = wm.tenant_id";
  62. private static string BuildListBaseSql(string innerWhere, string C) => $"""
  63. SELECT
  64. wm.RecID AS Id,
  65. wm.WorkOrd AS WorkOrd,
  66. wm.Typed AS Typed,
  67. wm.`Domain` AS `Domain`,
  68. defaultLine.Workshop AS Workshop,
  69. wm.OrdDate AS OrdDate,
  70. wm.DueDate AS DueDate,
  71. wm.ItemNum AS ItemNum,
  72. im.Descr AS Descr,
  73. im.Descr1 AS Descr1,
  74. IFNULL(wm.QtyOrded, 0) AS QtyOrded,
  75. IFNULL(wm.QtyCompleted, 0) AS QtyCompleted,
  76. IFNULL(wm.QtyOrded, 0) - IFNULL(wm.QtyCompleted, 0) AS NoCompleted,
  77. mo.MaterialSituation AS MaterialSituation,
  78. LOWER(wm.Status) AS Status,
  79. CASE
  80. WHEN IFNULL(wm.QtyOrded, 0) = 0 THEN 0
  81. ELSE ROUND(100.0 * IFNULL(wm.QtyCompleted, 0) / wm.QtyOrded, 4)
  82. END AS Progress,
  83. {WorkOrderExecutionProgressSql.Columns("wm", "curOp", "lastOp", "opAgg")}
  84. FROM WorkOrdMaster wm
  85. LEFT JOIN ItemMaster im
  86. ON wm.ItemNum COLLATE {C} = im.ItemNum COLLATE {C}
  87. AND wm.`Domain` COLLATE {C} = im.`Domain` COLLATE {C}
  88. AND (im.tenant_id = wm.tenant_id OR wm.tenant_id IS NULL)
  89. LEFT JOIN mes_morder mo
  90. ON wm.WorkOrd COLLATE {C} = mo.morder_no COLLATE {C}
  91. AND wm.`Domain` COLLATE {C} = mo.factory_id COLLATE {C}
  92. AND mo.tenant_id = wm.tenant_id
  93. {WorkOrderExecutionProgressSql.Laterals(KanbanRoutingMatch(C))}
  94. LEFT JOIN LATERAL (
  95. SELECT psd0.`Line` AS `Line`
  96. FROM PeriodSequenceDet psd0
  97. WHERE psd0.ItemNum COLLATE {C} = wm.ItemNum COLLATE {C}
  98. AND psd0.`Domain` COLLATE {C} = wm.`Domain` COLLATE {C}
  99. LIMIT 1
  100. ) AS psd ON TRUE
  101. LEFT JOIN LATERAL (
  102. SELECT lm.Workshop AS Workshop
  103. FROM LineMaster lm
  104. WHERE lm.`Line` COLLATE {C} = psd.`Line` COLLATE {C}
  105. AND lm.`Domain` COLLATE {C} = wm.`Domain` COLLATE {C}
  106. LIMIT 1
  107. ) AS defaultLine ON TRUE
  108. WHERE {innerWhere}
  109. """;
  110. }