ExecutableDailyPlanService.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. namespace Admin.NET.Plugin.AiDOP.Production;
  2. /// <summary>
  3. /// 可执行生产日计划(PeriodSequenceDet + 排产/工单等)
  4. /// 路由前缀:/api/Production/daily-plan/...
  5. /// </summary>
  6. [ApiDescriptionSettings(Order = 266, Description = "可执行生产日计划")]
  7. [Route("api/Production")]
  8. [AllowAnonymous]
  9. [NonUnify]
  10. public class ExecutableDailyPlanService : IDynamicApiController, ITransient
  11. {
  12. private readonly ISqlSugarClient _db;
  13. private readonly UserManager _userManager;
  14. public ExecutableDailyPlanService(ISqlSugarClient db, UserManager userManager)
  15. {
  16. _db = db;
  17. _userManager = userManager;
  18. }
  19. /// <summary>分页列表 GET /api/Production/daily-plan/list</summary>
  20. [DisplayName("可执行生产日计划列表")]
  21. [HttpGet("daily-plan/list")]
  22. public async Task<object> GetList([FromQuery] ExecutableDailyPlanListInput input)
  23. {
  24. var pars = new List<SugarParameter>();
  25. var innerWhere = new List<string>
  26. {
  27. "p.OrdQty > 0",
  28. "p.IsActive = 1",
  29. "DATE(p.PlanDate) >= CURDATE()",
  30. "LOWER(IFNULL(w.Status,'')) <> 'c'"
  31. };
  32. if (!string.IsNullOrWhiteSpace(input.Domain))
  33. {
  34. innerWhere.Add("p.`Domain` = @Domain");
  35. pars.Add(new SugarParameter("@Domain", input.Domain.Trim()));
  36. }
  37. if (!string.IsNullOrWhiteSpace(input.WorkOrds))
  38. {
  39. innerWhere.Add("(p.WorkOrds) LIKE @WorkOrds");
  40. pars.Add(new SugarParameter("@WorkOrds", $"%{input.WorkOrds.Trim()}%"));
  41. }
  42. if (!string.IsNullOrWhiteSpace(input.ItemNum))
  43. {
  44. innerWhere.Add("(p.ItemNum) LIKE @ItemNum");
  45. pars.Add(new SugarParameter("@ItemNum", $"%{input.ItemNum.Trim()}%"));
  46. }
  47. if (!string.IsNullOrWhiteSpace(input.Batch))
  48. {
  49. innerWhere.Add("(w.Batch) LIKE @Batch");
  50. pars.Add(new SugarParameter("@Batch", $"%{input.Batch.Trim()}%"));
  51. }
  52. if (!string.IsNullOrWhiteSpace(input.WoStatus))
  53. {
  54. innerWhere.Add("LOWER(IFNULL(w.Status,'')) = @WoStatus");
  55. pars.Add(new SugarParameter("@WoStatus", input.WoStatus.Trim().ToLowerInvariant()));
  56. }
  57. if (!string.IsNullOrWhiteSpace(input.IsLabor))
  58. {
  59. if (string.Equals(input.IsLabor, "yes", StringComparison.OrdinalIgnoreCase))
  60. innerWhere.Add("IFNULL(p.Status,'') <> ''");
  61. else if (string.Equals(input.IsLabor, "no", StringComparison.OrdinalIgnoreCase))
  62. innerWhere.Add("IFNULL(p.Status,'') = ''");
  63. }
  64. if (!string.IsNullOrWhiteSpace(input.WorkCtr))
  65. {
  66. innerWhere.Add("(sm.WorkCtr) = @WorkCtr");
  67. pars.Add(new SugarParameter("@WorkCtr", input.WorkCtr.Trim()));
  68. }
  69. if (!string.IsNullOrWhiteSpace(input.OccupyEquipmentType))
  70. {
  71. innerWhere.Add("(IFNULL(sm.OccupyInternalEquipmentCode,'') LIKE @Occ OR IFNULL(sm.MoldTypeCode,'') LIKE @Occ)");
  72. var occ = $"%{input.OccupyEquipmentType.Trim()}%";
  73. pars.Add(new SugarParameter("@Occ", occ));
  74. }
  75. if (!string.IsNullOrWhiteSpace(input.OpStdOp))
  76. {
  77. innerWhere.Add("(CONCAT(CAST(sm.Op AS CHAR),' ',IFNULL(sm.OpDescr,'')) LIKE @OpStdOp");
  78. pars.Add(new SugarParameter("@OpStdOp", $"%{input.OpStdOp.Trim()}%"));
  79. }
  80. if (!string.IsNullOrWhiteSpace(input.WorkStartFrom))
  81. {
  82. innerWhere.Add("sm.WorkStartTime >= @WorkStartFrom");
  83. pars.Add(new SugarParameter("@WorkStartFrom", input.WorkStartFrom.Trim()));
  84. }
  85. var baseSql = BuildListBaseSql(string.Join(" AND ", innerWhere));
  86. var orderClause = ResolveListOrder(input.OrderBy, input.Sort);
  87. var offset = (input.Page - 1) * input.PageSize;
  88. var total = await _db.Ado.GetIntAsync(
  89. $"SELECT COUNT(*) FROM ({baseSql}) AS t", pars);
  90. var list = await _db.Ado.SqlQueryAsync<ExecutableDailyPlanListRow>(
  91. $"SELECT * FROM ({baseSql}) AS t ORDER BY {orderClause} LIMIT {input.PageSize} OFFSET {offset}", pars);
  92. return new { total, page = input.Page, pageSize = input.PageSize, list };
  93. }
  94. /// <summary>下达 POST /api/Production/daily-plan/release</summary>
  95. [DisplayName("日计划下达")]
  96. [HttpPost("daily-plan/release")]
  97. public async Task<object> Release([FromBody] ExecutableDailyPlanReleaseInput input)
  98. {
  99. var domain = input.Domain.Trim();
  100. var ids = input.Ids.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
  101. .Select(s => long.TryParse(s, out var id) ? id : (long?)null)
  102. .Where(x => x.HasValue)
  103. .Select(x => x!.Value)
  104. .ToArray();
  105. if (ids.Length == 0)
  106. throw Oops.Oh("请选择有效的行主键");
  107. var account = _userManager.Account ?? "system";
  108. var now = DateTime.Now;
  109. const string releasedFlag = "Y";
  110. var n = await _db.Ado.ExecuteCommandAsync(
  111. $"""
  112. UPDATE PeriodSequenceDet
  113. SET `Status` = @Flag,
  114. UpdateUser = @UpdateUser,
  115. UpdateTime = @UpdateTime
  116. WHERE RecID IN ({string.Join(",", ids)})
  117. AND `Domain` = @Domain
  118. """,
  119. new List<SugarParameter>
  120. {
  121. new("@Flag", releasedFlag),
  122. new("@UpdateUser", account),
  123. new("@UpdateTime", now),
  124. new("@Domain", domain)
  125. });
  126. return new { message = "下达成功", affected = n };
  127. }
  128. /// <summary>工单状态 PATCH POST /api/Production/daily-plan/patch-wo-status</summary>
  129. [DisplayName("可执行日计划-工单状态")]
  130. [HttpPost("daily-plan/patch-wo-status")]
  131. public async Task<object> PatchWoStatus([FromBody] ExecutableDailyPlanWoStatusInput input)
  132. {
  133. var st = input.Status.Trim().ToLowerInvariant();
  134. if (st is not ("w" or "r" or "c" or "p"))
  135. throw Oops.Oh("状态不合法");
  136. var n = await _db.Ado.ExecuteCommandAsync(
  137. $"""
  138. UPDATE WorkOrdMaster
  139. SET `Status` = @St,
  140. UpdateTime = @Now,
  141. UpdateUser = @User
  142. WHERE WorkOrd = @WorkOrd AND `Domain` = @Domain
  143. """,
  144. new List<SugarParameter>
  145. {
  146. new("@St", st),
  147. new("@Now", DateTime.Now),
  148. new("@User", _userManager.Account ?? "system"),
  149. new("@WorkOrd", input.WorkOrd.Trim()),
  150. new("@Domain", input.Domain.Trim())
  151. });
  152. if (n == 0)
  153. throw Oops.Oh("工单不存在");
  154. return new { message = "状态已更新" };
  155. }
  156. private static string BuildListBaseSql(string innerWhere) => $"""
  157. SELECT
  158. IF(p.CompQty > 0, p.CompQty, IF(IFNULL(comPD.CompQty, 0) > 0, comPD.CompQty, 0)) AS CompQty,
  159. IF(p.RejectQty > 0, p.RejectQty, IF(IFNULL(comPD.RejectQty, 0) > 0, comPD.RejectQty, 0)) AS RejectQty,
  160. sm.WorkStartTime AS WorkStartTime,
  161. sm.DeviceAllocationCount AS DeviceAllocationCount,
  162. p.RecID AS Id,
  163. sm.WorkEndTime AS WorkEndTime,
  164. pd.InternalEquipmentCode AS Line,
  165. line.line_describe AS LineDescribe,
  166. p.WorkOrds AS WorkOrds,
  167. p.ItemNum AS ItemNum,
  168. i.Descr AS Descr,
  169. i.PkgCode AS Languages,
  170. w.LotSerial AS LotSerial,
  171. CASE WHEN IFNULL(p.Status,'') = '' THEN '否' ELSE '是' END AS IsLabor,
  172. IFNULL(r.MachBdnRate, pd.Rate) AS Rate,
  173. p.Op AS Op,
  174. p.PlanDate AS PlanDate,
  175. p.OrdQty AS OrdQty,
  176. pd.RunCrew AS RunCrew,
  177. LOWER(w.Status) AS Status,
  178. w.Batch AS Batch,
  179. p.Status AS PStatus,
  180. m.MaterialSituation AS MaterialSituation,
  181. CASE WHEN DATE_FORMAT(p.PlanDate,'%Y-%m-%d') = DATE_FORMAT(CURDATE(),'%Y-%m-%d') THEN 1 ELSE 0 END AS IsUpdateQty,
  182. CASE WHEN DATE_FORMAT(p.PlanDate,'%Y-%m-%d') > DATE_FORMAT(CURDATE(),'%Y-%m-%d') THEN 1 ELSE 0 END AS IsUpdateTime,
  183. sm.SetupTime AS SetupTime,
  184. sm.SetupTime * 60 AS SetupTimeInMinutes,
  185. pd.Site AS Site,
  186. CONCAT(IFNULL(sm.WorkCtr,''), ' ', IFNULL(wm.Descr,'')) AS Descr0,
  187. w.DueDate AS DueDate,
  188. pd.OpType AS OpType,
  189. CASE WHEN pd.OpType = 'M'
  190. THEN TIMESTAMPDIFF(MINUTE, sm.WorkStartTime, sm.WorkEndTime) / 60.0 * IFNULL(sm.DeviceAllocationCount, 0)
  191. ELSE 0 END AS DeviceWorkHours,
  192. CASE WHEN IFNULL(pd.OpType,'') <> 'M'
  193. THEN TIMESTAMPDIFF(MINUTE, sm.WorkStartTime, sm.WorkEndTime) / 60.0 * IFNULL(sm.DeviceAllocationCount, 0)
  194. ELSE TIMESTAMPDIFF(MINUTE, sm.WorkStartTime, sm.WorkEndTime) / 60.0 * IFNULL(sm.DeviceAllocationCount, 0) * IFNULL(pd.StandardStaffCount, 0) END AS ManWorkHours,
  195. CASE WHEN sm.WorkEndTime > w.DueDate THEN 'WorkOrds:#FF7F50'
  196. WHEN m.MaterialSituation = '仓库不齐套' AND LOWER(IFNULL(w.Status,'')) = 'r' THEN 'WorkOrds:#FCDD18'
  197. ELSE 'WorkOrds:#FFFFFF;' END AS Background,
  198. CONCAT(CAST(sm.Op AS CHAR), ' ', IFNULL(sm.OpDescr,'')) AS OpStdOp,
  199. IF(IFNULL(r.RunCrew, 0) > 0, r.RunCrew, pd.StandardStaffCount) AS StandardStaffCount,
  200. i.Drawing AS Drawing,
  201. w.QtyOrded AS QtyOrded,
  202. w.ReleaseDate AS ReleaseDate,
  203. (
  204. SELECT GROUP_CONCAT(DISTINCT el.MachineName ORDER BY el.MachineName SEPARATOR '/')
  205. FROM EquipmentList el
  206. WHERE pd.InternalEquipmentCode IS NOT NULL
  207. AND (
  208. el.InternalEquipmentCode = pd.InternalEquipmentCode
  209. OR FIND_IN_SET(el.InternalEquipmentCode, REPLACE(REPLACE(IFNULL(pd.InternalEquipmentCode,''), '+', ','), '/', ',')) > 0
  210. )
  211. ) AS MachineName,
  212. IF(w.Rev IS NULL OR w.Rev = '', i.Rev, w.Rev) AS Rev,
  213. w.Eff AS Eff,
  214. p.`Domain` AS Domain
  215. FROM PeriodSequenceDet p
  216. LEFT JOIN LineMaster line ON p.Line = line.Line AND p.`Domain` = line.`Domain`
  217. LEFT JOIN ItemMaster i ON p.ItemNum = i.ItemNum AND p.`Domain` = i.`Domain`
  218. LEFT JOIN WorkOrdMaster w ON p.WorkOrds = w.WorkOrd AND p.`Domain` = w.`Domain`
  219. LEFT JOIN WorkOrdRouting r ON p.Op = r.OP AND p.ItemNum = r.ItemNum AND p.WorkOrds = r.WorkOrd
  220. LEFT JOIN mes_morder m ON w.WorkOrd = m.morder_no AND w.`Domain` = m.factory_id
  221. LEFT JOIN ProdLineDetail pd ON p.`Domain` = pd.`Domain` AND p.ItemNum = pd.Part AND p.Line = pd.Line AND p.Op = pd.Op
  222. LEFT JOIN ScheduleResultOpMaster sm ON sm.`Domain` = p.`Domain` AND p.UDate2 = sm.WorkActivateTime AND p.ItemNum = sm.ItemNum AND sm.WorkOrd = p.WorkOrds AND sm.Op = p.Op
  223. LEFT JOIN WorkCtrMaster wm ON wm.WorkCtr = sm.WorkCtr AND wm.`Domain` = sm.`Domain`
  224. LEFT JOIN (
  225. SELECT SUM(CompQty) AS CompQty, SUM(RejectQty) AS RejectQty, PlanDate, `Period` AS UInt2, Op, WorkOrds, ItemNum
  226. FROM PeriodSequenceDet
  227. WHERE CompQty > 0
  228. GROUP BY PlanDate, `Period`, Op, WorkOrds, ItemNum
  229. ) comPD ON DATE_FORMAT(p.PlanDate,'%Y-%m-%d') = DATE_FORMAT(comPD.PlanDate,'%Y-%m-%d')
  230. AND p.`Period` = comPD.UInt2 AND p.Op = comPD.Op AND p.WorkOrds = comPD.WorkOrds AND p.ItemNum = comPD.ItemNum
  231. WHERE {innerWhere}
  232. """;
  233. private static string ResolveListOrder(string? orderBy, string? sort)
  234. {
  235. var desc = string.Equals(sort?.Trim(), "desc", StringComparison.OrdinalIgnoreCase);
  236. var dir = desc ? "DESC" : "ASC";
  237. var key = orderBy?.Trim().ToLowerInvariant();
  238. var col = key switch
  239. {
  240. "workords" => "t.WorkOrds",
  241. "itemnum" => "t.ItemNum",
  242. "plandate" => "t.PlanDate",
  243. "status" => "t.Status",
  244. "workstarttime" => "t.WorkStartTime",
  245. "lotserial" => "t.LotSerial",
  246. _ => "t.PlanDate"
  247. };
  248. return $"{col} {dir}, t.Id DESC";
  249. }
  250. private sealed class ExecutableDailyPlanListRow
  251. {
  252. public decimal? CompQty { get; set; }
  253. public decimal? RejectQty { get; set; }
  254. public DateTime? WorkStartTime { get; set; }
  255. public int? DeviceAllocationCount { get; set; }
  256. public long Id { get; set; }
  257. public DateTime? WorkEndTime { get; set; }
  258. public string? Line { get; set; }
  259. public string? LineDescribe { get; set; }
  260. public string? WorkOrds { get; set; }
  261. public string? ItemNum { get; set; }
  262. public string? Descr { get; set; }
  263. public string? Languages { get; set; }
  264. public string? LotSerial { get; set; }
  265. public string? IsLabor { get; set; }
  266. public decimal? Rate { get; set; }
  267. public int? Op { get; set; }
  268. public DateTime? PlanDate { get; set; }
  269. public decimal? OrdQty { get; set; }
  270. public decimal? RunCrew { get; set; }
  271. public string? Status { get; set; }
  272. public string? Batch { get; set; }
  273. public string? PStatus { get; set; }
  274. public string? MaterialSituation { get; set; }
  275. public int IsUpdateQty { get; set; }
  276. public int IsUpdateTime { get; set; }
  277. public decimal? SetupTime { get; set; }
  278. public decimal? SetupTimeInMinutes { get; set; }
  279. public string? Site { get; set; }
  280. public string? Descr0 { get; set; }
  281. public DateTime? DueDate { get; set; }
  282. public string? OpType { get; set; }
  283. public decimal? DeviceWorkHours { get; set; }
  284. public decimal? ManWorkHours { get; set; }
  285. public string? Background { get; set; }
  286. public string? OpStdOp { get; set; }
  287. public decimal? StandardStaffCount { get; set; }
  288. public string? Drawing { get; set; }
  289. public decimal? QtyOrded { get; set; }
  290. public DateTime? ReleaseDate { get; set; }
  291. public string? MachineName { get; set; }
  292. public string? Rev { get; set; }
  293. public DateTime? Eff { get; set; }
  294. public string? Domain { get; set; }
  295. }
  296. }