WorkOrderSchedulingService.cs 45 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019
  1. namespace Admin.NET.Plugin.AiDOP.Production;
  2. /// <summary>
  3. /// 工单工序排产服务 🏭
  4. /// 路由前缀:/api/Production/scheduling/...
  5. /// </summary>
  6. [ApiDescriptionSettings(Order = 265, Description = "工单工序排产")]
  7. [Route("api/Production")]
  8. [AllowAnonymous]
  9. [NonUnify]
  10. public class WorkOrderSchedulingService : IDynamicApiController, ITransient
  11. {
  12. private readonly ISqlSugarClient _db;
  13. private readonly UserManager _userManager;
  14. public WorkOrderSchedulingService(ISqlSugarClient db, UserManager userManager)
  15. {
  16. _db = db;
  17. _userManager = userManager;
  18. }
  19. /// <summary>
  20. /// 主表 WorkOrdMaster 按工单号 +「公司域或租户 id」匹配:<c>domain</c> 可为原 <c>Domain</c> 列,或与 <c>tenant_id</c> 相同的字符串(列表已做 COALESCE 回传)。
  21. /// </summary>
  22. private const string SqlWorkOrdMasterMatchDomainOrTenant = """
  23. WorkOrd = @WorkOrd AND (
  24. TRIM(IFNULL(`Domain`, '')) = TRIM(@Domain)
  25. OR (
  26. TRIM(IFNULL(`Domain`, '')) = ''
  27. AND TRIM(@Domain) <> ''
  28. AND CAST(IFNULL(tenant_id, 0) AS CHAR) = TRIM(@Domain)
  29. )
  30. )
  31. """;
  32. /// <summary>带表别名的 WorkOrdMaster 匹配(参数 <c>alias</c> 为表别名,如 a、w)。</summary>
  33. private static string SqlWorkOrdMasterMatchDomainOrTenantAliased(string alias) => $"""
  34. {alias}.WorkOrd = @WorkOrd AND (
  35. TRIM(IFNULL({alias}.`Domain`, '')) = TRIM(@Domain)
  36. OR (
  37. TRIM(IFNULL({alias}.`Domain`, '')) = ''
  38. AND TRIM(@Domain) <> ''
  39. AND CAST(IFNULL({alias}.tenant_id, 0) AS CHAR) = TRIM(@Domain)
  40. )
  41. )
  42. """;
  43. /// <summary>子表(工序/明细)按工单号 + 域或主表租户匹配。</summary>
  44. private static string SqlWorkOrdChildMatchDomainOrTenant(string childAlias, string masterAlias) => $"""
  45. {childAlias}.WorkOrd = @WorkOrd AND (
  46. TRIM(IFNULL({childAlias}.`Domain`, '')) = TRIM(@Domain)
  47. OR EXISTS (
  48. SELECT 1 FROM WorkOrdMaster {masterAlias}
  49. WHERE {masterAlias}.WorkOrd = {childAlias}.WorkOrd
  50. AND (
  51. TRIM(IFNULL({masterAlias}.`Domain`, '')) = TRIM(@Domain)
  52. OR (
  53. TRIM(IFNULL({masterAlias}.`Domain`, '')) = ''
  54. AND TRIM(@Domain) <> ''
  55. AND CAST(IFNULL({masterAlias}.tenant_id, 0) AS CHAR) = TRIM(@Domain)
  56. )
  57. )
  58. )
  59. )
  60. """;
  61. /// <summary>按主键 +「公司域或租户 id」匹配(状态更新等)。</summary>
  62. private const string SqlWorkOrdMasterMatchRecIdAndDomainOrTenant = """
  63. RecID = @Id AND (
  64. TRIM(IFNULL(`Domain`, '')) = TRIM(@Domain)
  65. OR (
  66. TRIM(IFNULL(`Domain`, '')) = ''
  67. AND TRIM(@Domain) <> ''
  68. AND CAST(IFNULL(tenant_id, 0) AS CHAR) = TRIM(@Domain)
  69. )
  70. )
  71. """;
  72. // ══════════════════════════════════════════════════════════════
  73. // 列表 GET /api/Production/scheduling/list
  74. // ══════════════════════════════════════════════════════════════
  75. /// <summary>工单工序排产分页列表(MySQL 口径,与 WorkOrdMaster 等表一致)</summary>
  76. [DisplayName("工单工序排产列表")]
  77. [HttpGet("scheduling/list")]
  78. public async Task<object> GetList([FromQuery] WorkOrderSchedulingListInput input)
  79. {
  80. //const string C = "utf8mb4_general_ci";
  81. const string C = "utf8mb4_0900_ai_ci";
  82. var pars = new List<SugarParameter>();
  83. var innerWhere = new List<string>
  84. {
  85. "IFNULL(a.Status,'') <> ''"
  86. };
  87. if (!string.IsNullOrWhiteSpace(input.WorkOrd))
  88. {
  89. innerWhere.Add($"a.WorkOrd LIKE @WorkOrd");
  90. pars.Add(new SugarParameter("@WorkOrd", $"%{input.WorkOrd.Trim()}%"));
  91. }
  92. if (!string.IsNullOrWhiteSpace(input.LotSerial))
  93. {
  94. innerWhere.Add($"a.LotSerial LIKE @LotSerial");
  95. pars.Add(new SugarParameter("@LotSerial", $"%{input.LotSerial.Trim()}%"));
  96. }
  97. if (!string.IsNullOrWhiteSpace(input.ItemNum))
  98. {
  99. innerWhere.Add($"a.ItemNum LIKE @ItemNum");
  100. pars.Add(new SugarParameter("@ItemNum", $"%{input.ItemNum.Trim()}%"));
  101. }
  102. if (!string.IsNullOrWhiteSpace(input.StartDateFrom))
  103. {
  104. innerWhere.Add("COALESCE(DATE(s.PlanDate), DATE(a.OrdDate)) >= @StartDateFrom");
  105. pars.Add(new SugarParameter("@StartDateFrom", input.StartDateFrom.Trim()));
  106. }
  107. if (!string.IsNullOrWhiteSpace(input.Status))
  108. {
  109. innerWhere.Add("LOWER(a.Status) = @Status");
  110. pars.Add(new SugarParameter("@Status", input.Status.Trim().ToLowerInvariant()));
  111. }
  112. var baseSql = BuildListBaseSql(string.Join(" AND ", innerWhere), C);
  113. var offset = (input.Page - 1) * input.PageSize;
  114. var total = await _db.Ado.GetIntAsync(
  115. $"SELECT COUNT(*) FROM ({baseSql}) AS t", pars);
  116. var list = await _db.Ado.SqlQueryAsync<WorkOrderSchedulingListRow>(
  117. $"SELECT * FROM ({baseSql}) AS t ORDER BY t.Id DESC LIMIT {input.PageSize} OFFSET {offset}",
  118. pars);
  119. return new { total, page = input.Page, pageSize = input.PageSize, list };
  120. }
  121. private static string BuildListBaseSql(string innerWhere, string C) => $"""
  122. SELECT
  123. a.RecID AS Id,
  124. a.Priority AS Priority,
  125. a.WorkOrd AS WorkOrd,
  126. a.LotSerial AS LotSerial,
  127. a.IssueSite AS IssueSite,
  128. a.ItemNum AS ItemNum,
  129. b.Descr AS Descr,
  130. b.Descr1 AS Descr1,
  131. a.QtyOrded AS QtyOrded,
  132. (IFNULL(a.LocationStock, 0) + IFNULL(a.OpQtyCompleted, 0)) AS LocationStock,
  133. a.QtyCompleted AS QtyCompleted,
  134. s.PlanDate AS PlanDate,
  135. s.ProdDate AS ProdDate,
  136. LOWER(a.Status) AS Status,
  137. COALESCE(NULLIF(TRIM(a.`Domain`), ''), IFNULL(CAST(a.tenant_id AS CHAR), '')) AS Domain,
  138. a.Urgent AS Urgent
  139. FROM WorkOrdMaster a
  140. LEFT JOIN ItemMaster b ON a.ItemNum = b.ItemNum
  141. LEFT JOIN ReplenishmentWeekPlan r
  142. ON a.WorkOrd = r.ProductionOrder
  143. AND CAST(a.`Domain` AS CHAR(64)) = CAST(r.factory_id AS CHAR(64))
  144. LEFT JOIN crm_seorder se ON se.bill_no = a.SalesJob
  145. LEFT JOIN CustMaster cm ON cm.Cust = se.custom_no
  146. LEFT JOIN (
  147. SELECT `Domain`, WorkOrds, MIN(PlanDate) AS PlanDate, MIN(ProdDate) AS ProdDate
  148. FROM PeriodSequenceDet
  149. GROUP BY `Domain`, WorkOrds
  150. ) s ON CAST(a.`Domain` AS CHAR(64)) = CAST(s.`Domain` AS CHAR(64))
  151. AND a.WorkOrd = s.WorkOrds
  152. LEFT JOIN (
  153. SELECT morder_no, MAX(create_time) AS checktime
  154. FROM b_examine_result
  155. GROUP BY morder_no
  156. ) exm ON exm.morder_no = a.WorkOrd
  157. LEFT JOIN WorkOrdInStorage ins
  158. ON a.WorkOrd = ins.WorkOrd
  159. AND ins.Remark = '工单预留'
  160. WHERE {innerWhere}
  161. """;
  162. // ══════════════════════════════════════════════════════════════
  163. // 编辑预览 GET /api/Production/scheduling/edit-preview
  164. // ══════════════════════════════════════════════════════════════
  165. [DisplayName("工单优先级编辑数据")]
  166. [HttpGet("scheduling/edit-preview")]
  167. public async Task<object> GetEditPreview([FromQuery] string workOrd, [FromQuery] string domain)
  168. {
  169. if (string.IsNullOrWhiteSpace(workOrd) || string.IsNullOrWhiteSpace(domain))
  170. throw Oops.Oh("工单号与公司域名不能为空");
  171. string sql = $"""
  172. SELECT
  173. a.WorkOrd,
  174. a.ItemNum,
  175. a.QtyOrded,
  176. a.Priority,
  177. a.LotSerial,
  178. IFNULL(a.Urgent, 0) AS Urgent,
  179. DATE(a.OrdDate) AS OrdDate
  180. FROM WorkOrdMaster a
  181. WHERE {SqlWorkOrdMasterMatchDomainOrTenantAliased("a")}
  182. LIMIT 1
  183. """;
  184. var row = (await _db.Ado.SqlQueryAsync<WorkOrderEditPreviewRow>(sql, new { WorkOrd = workOrd.Trim(), Domain = domain.Trim() }))
  185. .FirstOrDefault() ?? throw Oops.Oh("工单不存在");
  186. return row;
  187. }
  188. // ══════════════════════════════════════════════════════════════
  189. // 查看 GET /api/Production/scheduling/view
  190. // ══════════════════════════════════════════════════════════════
  191. [DisplayName("工单查看(主表+工序+物料)")]
  192. [HttpGet("scheduling/view")]
  193. public async Task<object> GetView([FromQuery] string workOrd, [FromQuery] string domain)
  194. {
  195. if (string.IsNullOrWhiteSpace(workOrd) || string.IsNullOrWhiteSpace(domain))
  196. throw Oops.Oh("工单号与公司域名不能为空");
  197. string masterSql = $"""
  198. SELECT
  199. a.WorkOrd,
  200. a.QtyOrded,
  201. a.QtyCompleted,
  202. a.ItemNum,
  203. IFNULL(a.ItemName, b.Descr) AS ItemName,
  204. b.Descr1 AS ItemModel,
  205. DATE(a.OrdDate) AS OrdDate,
  206. DATE(a.DueDate) AS DueDate,
  207. LOWER(a.Status) AS Status,
  208. IFNULL(a.Remark, '') AS Remark
  209. FROM WorkOrdMaster a
  210. LEFT JOIN ItemMaster b ON a.ItemNum = b.ItemNum
  211. WHERE {SqlWorkOrdMasterMatchDomainOrTenantAliased("a")}
  212. LIMIT 1
  213. """;
  214. var master = (await _db.Ado.SqlQueryAsync<WorkOrderViewMasterRow>(masterSql, new { WorkOrd = workOrd.Trim(), Domain = domain.Trim() }))
  215. .FirstOrDefault() ?? throw Oops.Oh("工单不存在");
  216. string routingSql = $"""
  217. SELECT
  218. r.OP AS Op,
  219. r.Descr AS Descr,
  220. IFNULL(r.ParentOp, '') AS ParentOp,
  221. IFNULL(r.MilestoneOp, 0) AS MilestoneOp,
  222. IFNULL(r.PackingQty, 0) AS PackingQty,
  223. IFNULL(r.QtyComplete, 0) AS QtyComplete,
  224. IFNULL(r.QtyReject, 0) AS QtyReject,
  225. IFNULL(r.QtyScrap, 0) AS QtyScrap,
  226. r.Status AS Status
  227. FROM WorkOrdRouting r
  228. WHERE {SqlWorkOrdChildMatchDomainOrTenant("r", "m")}
  229. ORDER BY (r.OP + 0) ASC, r.OP ASC, r.Line ASC, r.ColumnNum ASC
  230. """;
  231. var routings = await _db.Ado.SqlQueryAsync<WorkOrderViewRoutingRow>(routingSql, new { WorkOrd = workOrd.Trim(), Domain = domain.Trim() });
  232. string detailSql = $"""
  233. SELECT d.ItemNum, d.Op, d.QtyRequired, IFNULL(d.FrozenBOMQty, 0) AS FrozenBOMQty
  234. FROM WorkOrdDetail d
  235. WHERE {SqlWorkOrdChildMatchDomainOrTenant("d", "m")}
  236. ORDER BY d.LineNum
  237. """;
  238. var details = await _db.Ado.SqlQueryAsync<WorkOrderViewDetailRow>(detailSql, new { WorkOrd = workOrd.Trim(), Domain = domain.Trim() });
  239. return new { master, routings, details };
  240. }
  241. // ══════════════════════════════════════════════════════════════
  242. // 物料 / 工序明细列表(新标签页)
  243. // ══════════════════════════════════════════════════════════════
  244. [DisplayName("工单物料明细列表")]
  245. [HttpGet("scheduling/materials")]
  246. public async Task<object> GetMaterials([FromQuery] string workOrd, [FromQuery] string domain)
  247. {
  248. if (string.IsNullOrWhiteSpace(workOrd) || string.IsNullOrWhiteSpace(domain))
  249. throw Oops.Oh("工单号与公司域名不能为空");
  250. string sql = $"""
  251. SELECT d.ItemNum, d.Op, d.QtyRequired, IFNULL(d.FrozenBOMQty, 0) AS FrozenBOMQty
  252. FROM WorkOrdDetail d
  253. WHERE {SqlWorkOrdChildMatchDomainOrTenant("d", "m")}
  254. ORDER BY d.LineNum
  255. """;
  256. var list = await _db.Ado.SqlQueryAsync<WorkOrderViewDetailRow>(sql, new { WorkOrd = workOrd.Trim(), Domain = domain.Trim() });
  257. return new { list };
  258. }
  259. [DisplayName("工单工序明细列表")]
  260. [HttpGet("scheduling/routings")]
  261. public async Task<object> GetRoutings([FromQuery] string workOrd, [FromQuery] string domain)
  262. {
  263. if (string.IsNullOrWhiteSpace(workOrd) || string.IsNullOrWhiteSpace(domain))
  264. throw Oops.Oh("工单号与公司域名不能为空");
  265. string sql = $"""
  266. SELECT
  267. r.OP AS Op,
  268. r.Descr AS Descr,
  269. IFNULL(r.ParentOp, '') AS ParentOp,
  270. IFNULL(r.MilestoneOp, 0) AS MilestoneOp,
  271. IFNULL(r.PackingQty, 0) AS PackingQty,
  272. IFNULL(r.QtyComplete, 0) AS QtyComplete,
  273. IFNULL(r.QtyReject, 0) AS QtyReject,
  274. IFNULL(r.QtyScrap, 0) AS QtyScrap,
  275. r.Status AS Status
  276. FROM WorkOrdRouting r
  277. WHERE {SqlWorkOrdChildMatchDomainOrTenant("r", "m")}
  278. ORDER BY (r.OP + 0) ASC, r.OP ASC, r.Line ASC, r.ColumnNum ASC
  279. """;
  280. var list = await _db.Ado.SqlQueryAsync<WorkOrderViewRoutingRow>(sql, new { WorkOrd = workOrd.Trim(), Domain = domain.Trim() });
  281. return new { list };
  282. }
  283. // ══════════════════════════════════════════════════════════════
  284. // 执行追踪 GET /api/Production/scheduling/trace
  285. // ══════════════════════════════════════════════════════════════
  286. [DisplayName("工单执行追踪")]
  287. [HttpGet("scheduling/trace")]
  288. public async Task<object> GetTrace([FromQuery] string workOrd, [FromQuery] string domain)
  289. {
  290. if (string.IsNullOrWhiteSpace(workOrd) || string.IsNullOrWhiteSpace(domain))
  291. throw Oops.Oh("工单号与公司域名不能为空");
  292. var w = workOrd.Trim();
  293. var d = domain.Trim();
  294. string masterSql = $"""
  295. SELECT
  296. a.WorkOrd,
  297. a.QtyOrded,
  298. a.QtyCompleted,
  299. a.ItemNum,
  300. IFNULL(b.Descr, '') AS ItemName,
  301. IFNULL(b.Descr1, '') AS ItemModel,
  302. DATE(a.OrdDate) AS OrdDate,
  303. DATE(a.DueDate) AS DueDate,
  304. LOWER(a.Status) AS Status
  305. FROM WorkOrdMaster a
  306. LEFT JOIN ItemMaster b ON a.ItemNum = b.ItemNum
  307. WHERE {SqlWorkOrdMasterMatchDomainOrTenantAliased("a")}
  308. LIMIT 1
  309. """;
  310. var master = (await _db.Ado.SqlQueryAsync<WorkOrderTraceMasterRow>(masterSql, new { WorkOrd = w, Domain = d }))
  311. .FirstOrDefault() ?? throw Oops.Oh("工单不存在");
  312. var tab1 = await QueryTraceResourceCheckAsync(w, d);
  313. var tab2 = await QueryTraceScheduleAsync(w, d);
  314. var tab3Po = await QueryTracePoAsync(w, d);
  315. var tab3Pr = await QueryTracePrAsync(w, d);
  316. var tab4 = await QueryTraceNbrAsync(w, d);
  317. var tab5 = await QueryTraceProdReportAsync(w, d);
  318. var tab6 = await QueryTraceQualityAsync(w, d);
  319. var tab7 = await QueryTraceReceiptAsync(w, d);
  320. return new
  321. {
  322. master,
  323. resourceCheck = tab1,
  324. schedule = tab2,
  325. purchaseOrders = tab3Po,
  326. purchaseReqs = tab3Pr,
  327. picking = tab4,
  328. production = tab5,
  329. quality = tab6,
  330. receipt = tab7
  331. };
  332. }
  333. private async Task<List<TraceResourceRow>> QueryTraceResourceCheckAsync(string workOrd, string domain)
  334. {
  335. const string sql = """
  336. SELECT
  337. ROW_NUMBER() OVER (ORDER BY bce.id) AS Sno,
  338. CAST(bce.num AS CHAR) AS Num,
  339. bce.item_number AS ItemNumber,
  340. bce.item_name AS ItemName,
  341. bce.model AS Model,
  342. IFNULL(bce.unit, '') AS Unit,
  343. IFNULL(bce.bom_number, '') AS BomNumber,
  344. DATE_FORMAT(bce.kitting_time, '%Y-%m-%d') AS KittingTime,
  345. CASE bce.erp_cls
  346. WHEN 0 THEN '配置类'
  347. WHEN 1 THEN '自制'
  348. WHEN 2 THEN '委外加工'
  349. WHEN 3 THEN '外购'
  350. WHEN 4 THEN '虚拟件'
  351. ELSE ''
  352. END AS ErpClsName,
  353. CASE WHEN bce.backflush = 1 THEN '是' ELSE '否' END AS Backflush,
  354. CAST(bce.qty AS CHAR) AS Qty,
  355. CAST(bce.needCount AS CHAR) AS NeedCount
  356. FROM b_examine_result ber
  357. INNER JOIN b_bom_child_examine bce ON ber.Id = bce.examine_id AND bce.is_use = 1
  358. WHERE ber.morder_no = @WorkOrd
  359. AND ber.Id = (
  360. SELECT br.Id FROM b_examine_result br
  361. WHERE br.morder_no = @WorkOrd
  362. ORDER BY br.create_time DESC
  363. LIMIT 1
  364. )
  365. ORDER BY bce.id
  366. """;
  367. return await _db.Ado.SqlQueryAsync<TraceResourceRow>(sql, new { WorkOrd = workOrd });
  368. }
  369. private async Task<List<TraceScheduleRow>> QueryTraceScheduleAsync(string workOrd, string domain)
  370. {
  371. const string sql = """
  372. SELECT
  373. ROW_NUMBER() OVER (ORDER BY sch.PlanDate) AS Sno,
  374. sch.WorkOrds AS WorkOrds,
  375. DATE_FORMAT(sch.PlanDate, '%Y-%m-%d') AS WorkDate,
  376. IFNULL(sch.Line, '') AS Line,
  377. IFNULL(sch.Op, '') AS Op,
  378. CAST(IFNULL(sch.OrdQty, 0) AS CHAR) AS WorkQty,
  379. IFNULL(sch.ItemNum, '') AS ItemNum,
  380. IFNULL(im.Descr, '') AS Descr,
  381. IFNULL(im.Descr1, '') AS Descr1,
  382. DATE_FORMAT(sch.CreateTime, '%Y-%m-%d %H:%i:%s') AS CreateTime
  383. FROM PeriodSequenceDet sch
  384. LEFT JOIN ItemMaster im ON sch.ItemNum = im.ItemNum
  385. WHERE sch.WorkOrds = @WorkOrd
  386. AND CAST(sch.`Domain` AS CHAR(64)) = CAST(@Domain AS CHAR(64))
  387. ORDER BY sch.PlanDate
  388. """;
  389. return await _db.Ado.SqlQueryAsync<TraceScheduleRow>(sql, new { WorkOrd = workOrd, Domain = domain });
  390. }
  391. private async Task<List<TracePoRow>> QueryTracePoAsync(string workOrd, string domain)
  392. {
  393. const string sql = """
  394. SELECT
  395. ROW_NUMBER() OVER (ORDER BY t.po_billno) AS Sno,
  396. t.po_billno AS PoBillno,
  397. t.supplier_no AS SupplierNo,
  398. t.supplier_name AS SupplierName,
  399. CAST(t.po_total AS CHAR) AS PoTotal,
  400. IFNULL(t.po_purchaser, '') AS PoPurchaser,
  401. t.state AS State,
  402. IFNULL(DATE_FORMAT(t.po_ssend_date, '%Y-%m-%d'), '') AS PoSsendDate,
  403. IFNULL(DATE_FORMAT(t.create_time, '%Y-%m-%d %H:%i:%s'), '') AS CreateTime
  404. FROM (
  405. SELECT DISTINCT
  406. pm.po_billno,
  407. pm.supplier_no,
  408. pm.supplier_name,
  409. pm.po_total,
  410. pm.po_purchaser,
  411. pm.create_time,
  412. pm.po_ssend_date,
  413. CASE pm.state
  414. WHEN 0 THEN '新增'
  415. WHEN 1 THEN '审核中'
  416. WHEN 2 THEN '同意'
  417. WHEN 3 THEN '关闭'
  418. ELSE CAST(pm.state AS CHAR)
  419. END AS state
  420. FROM srm_po_main pm
  421. LEFT JOIN srm_po_list pl ON pm.id = pl.po_id
  422. LEFT JOIN srm_po_occupy occ ON pl.Id = occ.polist_id
  423. AND CAST(pm.factory_id AS CHAR(64)) = CAST(occ.factory_id AS CHAR(64))
  424. LEFT JOIN mes_morder mo ON mo.id = occ.morder_id
  425. WHERE mo.morder_no = @WorkOrd
  426. AND CAST(mo.factory_id AS CHAR(64)) = CAST(@Domain AS CHAR(64))
  427. AND pm.IsDeleted = 0
  428. ) AS t
  429. ORDER BY t.po_billno
  430. """;
  431. return await _db.Ado.SqlQueryAsync<TracePoRow>(sql, new { WorkOrd = workOrd, Domain = domain });
  432. }
  433. private async Task<List<TracePrRow>> QueryTracePrAsync(string workOrd, string domain)
  434. {
  435. const string sql = """
  436. SELECT
  437. ROW_NUMBER() OVER (ORDER BY pm.pr_billno) AS Sno,
  438. pm.pr_billno AS PrBillno,
  439. IFNULL(ic.number, '') AS Number,
  440. IFNULL(pm.icitem_name, '') AS IcitemName,
  441. IFNULL(ic.model, '') AS Model,
  442. IFNULL(pm.pr_purchasenumber, '') AS PrPurchasenumber,
  443. IFNULL(pm.pr_purchasename, '') AS PrPurchasename,
  444. CAST(IFNULL(pm.pr_aqty, 0) AS CHAR) AS PrAqty,
  445. IFNULL(DATE_FORMAT(pm.pr_ssend_date, '%Y-%m-%d'), '') AS PrSsendDate,
  446. IFNULL(DATE_FORMAT(pm.pr_sarrive_date, '%Y-%m-%d'), '') AS PrSarriveDate,
  447. IFNULL(pm.pr_unit, '') AS PrUnit,
  448. IFNULL(pm.pr_purchaser, '') AS PrPurchaser
  449. FROM srm_pr_main pm
  450. LEFT JOIN srm_po_occupy occ ON pm.Id = occ.polist_id
  451. AND CAST(pm.factory_id AS CHAR(64)) = CAST(occ.factory_id AS CHAR(64))
  452. LEFT JOIN ic_item ic ON pm.icitem_id = ic.Id
  453. LEFT JOIN mes_morder mo ON mo.id = occ.morder_id
  454. WHERE mo.morder_no = @WorkOrd
  455. AND CAST(mo.factory_id AS CHAR(64)) = CAST(@Domain AS CHAR(64))
  456. AND IFNULL(pm.state, 0) <> 0
  457. AND pm.IsDeleted = 0
  458. ORDER BY pm.pr_billno
  459. """;
  460. return await _db.Ado.SqlQueryAsync<TracePrRow>(sql, new { WorkOrd = workOrd, Domain = domain });
  461. }
  462. private async Task<List<TraceNbrRow>> QueryTraceNbrAsync(string workOrd, string domain)
  463. {
  464. const string sql = """
  465. SELECT
  466. CAST(nd.Line AS CHAR) AS Line,
  467. nm.Nbr AS Nbr,
  468. nd.ItemNum AS ItemNum,
  469. IFNULL(im.Descr, '') AS Descr,
  470. IFNULL(im.Descr1, '') AS Descr1,
  471. IFNULL(nd.LocationFrom, '') AS LocationFrom,
  472. IFNULL(nd.LocationTo, '') AS LocationTo,
  473. CAST(IFNULL(nd.CurrQtyOpened, 0) AS CHAR) AS CurrQtyOpened,
  474. CAST(IFNULL(nd.QtyOrd, 0) AS CHAR) AS QtyOrd,
  475. CAST(IFNULL(nd.QtyFrom, 0) AS CHAR) AS QtyFrom,
  476. CAST(IFNULL(nd.QtyRec, 0) AS CHAR) AS QtyRec,
  477. IFNULL(nd.UM, '') AS Unit
  478. FROM NbrDetail nd
  479. LEFT JOIN NbrMaster nm ON nd.nbr = nm.nbr
  480. LEFT JOIN ItemMaster im ON nd.ItemNum = im.ItemNum
  481. WHERE nm.WorkOrd = @WorkOrd
  482. AND nm.Type = 'SM'
  483. AND CAST(nm.`Domain` AS CHAR(64)) = CAST(@Domain AS CHAR(64))
  484. ORDER BY nm.Nbr
  485. """;
  486. return await _db.Ado.SqlQueryAsync<TraceNbrRow>(sql, new { WorkOrd = workOrd, Domain = domain });
  487. }
  488. private async Task<List<TraceProdRow>> QueryTraceProdReportAsync(string workOrd, string domain)
  489. {
  490. const string sql = """
  491. SELECT
  492. ROW_NUMBER() OVER (ORDER BY ote.ProdDate DESC) AS Sno,
  493. ote.WorkOrd AS WorkOrd,
  494. IFNULL(ote.ItemNum, '') AS ItemNum,
  495. IFNULL(im.Descr, '') AS Descr,
  496. IFNULL(im.Descr1, '') AS Descr1,
  497. IFNULL(im.Um, '') AS Um,
  498. CAST(IFNULL(ote.QtyCompleted, 0) AS CHAR) AS QtyCompleted,
  499. CAST(IFNULL(ote.QtyReject, 0) AS CHAR) AS QtyReject,
  500. CAST(IFNULL(ote.QtyScrapped, 0) AS CHAR) AS QtyScrapped,
  501. IFNULL(DATE_FORMAT(ote.ProdDate, '%Y-%m-%d %H:%i:%s'), '') AS ProdDate,
  502. IFNULL(ote.Op, '') AS Op,
  503. IFNULL(ote.Name, '') AS Name
  504. FROM OpTransEmployee ote
  505. LEFT JOIN ItemMaster im ON ote.ItemNum = im.ItemNum
  506. WHERE ote.WorkOrd = @WorkOrd
  507. ORDER BY ote.ProdDate DESC
  508. """;
  509. return await _db.Ado.SqlQueryAsync<TraceProdRow>(sql, new { WorkOrd = workOrd });
  510. }
  511. private async Task<List<TraceQualityRow>> QueryTraceQualityAsync(string workOrd, string domain)
  512. {
  513. const string sql = """
  514. SELECT
  515. ROW_NUMBER() OVER (ORDER BY mo.id) AS Sno,
  516. IFNULL(im.Um, '') AS Unit,
  517. IFNULL(mo.morder_no, '') AS MorderNo,
  518. CAST('' AS CHAR) AS WorkNumber,
  519. CAST('' AS CHAR) AS MorderProductionNumber,
  520. CAST('' AS CHAR) AS InspectionNumber,
  521. CAST('' AS CHAR) AS QualifiedNumber,
  522. CAST('' AS CHAR) AS InventoryNumber,
  523. IFNULL(mo.moentry_wrkcname, '') AS MoentryWrkcname,
  524. CAST('' AS CHAR) AS PlannerStartDate,
  525. CAST('' AS CHAR) AS PlannerEndDate,
  526. IFNULL(mo.moentry_prdname, '') AS MoentryPrdname,
  527. CAST('' AS CHAR) AS MorderFstate
  528. FROM mes_morder mo
  529. LEFT JOIN ItemMaster im ON mo.product_code = im.ItemNum
  530. WHERE mo.morder_no = @WorkOrd
  531. AND CAST(mo.factory_id AS CHAR(64)) = CAST(@Domain AS CHAR(64))
  532. AND mo.IsDeleted = 0
  533. ORDER BY mo.id
  534. """;
  535. return await _db.Ado.SqlQueryAsync<TraceQualityRow>(sql, new { WorkOrd = workOrd, Domain = domain });
  536. }
  537. private async Task<List<TraceReceiptRow>> QueryTraceReceiptAsync(string workOrd, string domain)
  538. {
  539. const string sql = """
  540. SELECT
  541. ROW_NUMBER() OVER (ORDER BY c.WorkOrd) AS Sno,
  542. CASE
  543. WHEN LOWER(c.Status) = 'r' THEN '下达'
  544. WHEN LOWER(c.Status) = 'c' THEN '关闭'
  545. WHEN LOWER(c.Status) = 'w' THEN '投产'
  546. ELSE IFNULL(c.Status, '')
  547. END AS Status,
  548. c.WorkOrd AS WorkOrd,
  549. IFNULL(im.Um, '') AS UM,
  550. CAST(IFNULL(c.QtyOrded, 0) AS CHAR) AS QtyOrded,
  551. CAST(IFNULL(compp.CompQty, 0) AS CHAR) AS CompQty,
  552. CAST('' AS CHAR) AS InspectionNumber,
  553. CAST('' AS CHAR) AS QualifiedNumber,
  554. CAST(IFNULL(a.QtyChange, 0) AS CHAR) AS QtyChangeAdvance,
  555. IFNULL(DATE_FORMAT(c.OrdDate, '%Y-%m-%d'), '') AS OrdDate,
  556. IFNULL(DATE_FORMAT(c.DueDate, '%Y-%m-%d'), '') AS DueDate
  557. FROM WorkOrdMaster c
  558. LEFT JOIN InvTransHist a ON a.WorkOrd = c.WorkOrd AND a.TransType = 'rct-wo' AND IFNULL(a.QtyChange, 0) > 0
  559. LEFT JOIN ItemMaster im ON c.ItemNum = im.ItemNum AND im.`Domain` = c.`Domain`
  560. LEFT JOIN (
  561. SELECT WorkOrds, SUM(IFNULL(CompQty, 0)) AS CompQty
  562. FROM PeriodSequenceDet
  563. GROUP BY WorkOrds
  564. ) compp ON c.WorkOrd = compp.WorkOrds
  565. LEFT JOIN mes_morder mo ON mo.morder_no = c.WorkOrd AND mo.IsDeleted = 0
  566. AND CAST(mo.factory_id AS CHAR(64)) = CAST(c.`Domain` AS CHAR(64))
  567. WHERE c.WorkOrd = @WorkOrd AND c.`Domain` = @Domain
  568. """;
  569. return await _db.Ado.SqlQueryAsync<TraceReceiptRow>(sql, new { WorkOrd = workOrd, Domain = domain });
  570. }
  571. // ══════════════════════════════════════════════════════════════
  572. // 保存 POST /api/Production/scheduling/save
  573. // ══════════════════════════════════════════════════════════════
  574. [DisplayName("保存工单(数量/批次/优先级/加急)")]
  575. [HttpPost("scheduling/save")]
  576. public async Task<object> Save([FromBody] WorkOrderSchedulingSaveInput input)
  577. {
  578. var account = _userManager.Account ?? "system";
  579. var pars = new List<SugarParameter>
  580. {
  581. new("@QtyOrded", input.QtyOrded ?? (object)DBNull.Value),
  582. new("@Priority", input.Priority ?? (object)DBNull.Value),
  583. new("@LotSerial", string.IsNullOrWhiteSpace(input.LotSerial) ? DBNull.Value : input.LotSerial.Trim()),
  584. new("@Urgent", input.Urgent ?? (object)DBNull.Value),
  585. new("@UpdateUser", account),
  586. new("@UpdateTime", DateTime.Now),
  587. new("@WorkOrd", input.WorkOrd.Trim()),
  588. new("@Domain", input.Domain.Trim())
  589. };
  590. var n = await _db.Ado.ExecuteCommandAsync(
  591. $"""
  592. UPDATE WorkOrdMaster
  593. SET QtyOrded = COALESCE(@QtyOrded, QtyOrded),
  594. Priority = COALESCE(@Priority, Priority),
  595. LotSerial = COALESCE(@LotSerial, LotSerial),
  596. Urgent = COALESCE(@Urgent, Urgent),
  597. UpdateUser = @UpdateUser,
  598. UpdateTime = @UpdateTime
  599. WHERE {SqlWorkOrdMasterMatchDomainOrTenant}
  600. """,
  601. pars);
  602. if (n == 0)
  603. throw Oops.Oh("工单不存在或未更新");
  604. return new { message = "保存成功" };
  605. }
  606. // ══════════════════════════════════════════════════════════════
  607. // 状态 PATCH POST /api/Production/scheduling/status
  608. // ══════════════════════════════════════════════════════════════
  609. [DisplayName("更新工单状态")]
  610. [HttpPost("scheduling/status")]
  611. public async Task<object> PatchStatus([FromBody] WorkOrderStatusPatchInput input)
  612. {
  613. var account = _userManager.Account ?? "system";
  614. var status = input.Status.Trim().ToLowerInvariant();
  615. var pars = new List<SugarParameter>
  616. {
  617. new("@Status", status),
  618. new("@UpdateUser", account),
  619. new("@UpdateTime", DateTime.Now),
  620. new("@Id", input.Id),
  621. new("@Domain", input.Domain.Trim())
  622. };
  623. var n = await _db.Ado.ExecuteCommandAsync(
  624. $"""
  625. UPDATE WorkOrdMaster
  626. SET Status = @Status,
  627. UpdateUser = @UpdateUser,
  628. UpdateTime = @UpdateTime
  629. WHERE {SqlWorkOrdMasterMatchRecIdAndDomainOrTenant}
  630. """,
  631. pars);
  632. if (n == 0)
  633. throw Oops.Oh("工单不存在或未更新");
  634. return new { message = "状态已更新" };
  635. }
  636. // ══════════════════════════════════════════════════════════════
  637. // 工单关闭 POST /api/Production/scheduling/close
  638. // ══════════════════════════════════════════════════════════════
  639. [DisplayName("工单关闭(存储过程)")]
  640. [HttpPost("scheduling/close")]
  641. public async Task<object> Close([FromBody] WorkOrderCloseInput input)
  642. {
  643. await _db.Ado.ExecuteCommandAsync(
  644. "CALL pr_MES_CloseWorkOrders(@Ids)",
  645. new SugarParameter("@Ids", input.Ids.Trim()));
  646. return new { message = "已提交关闭" };
  647. }
  648. // ══════════════════════════════════════════════════════════════
  649. // 同步工艺路线 POST /api/Production/scheduling/sync-routing
  650. // ══════════════════════════════════════════════════════════════
  651. [DisplayName("同步工艺路线")]
  652. [HttpPost("scheduling/sync-routing")]
  653. public async Task<object> SyncRouting([FromBody] WorkOrderKeyInput input)
  654. {
  655. var workOrd = input.WorkOrd.Trim();
  656. var domain = input.Domain.Trim();
  657. var createUser = (_userManager.Account ?? "system").Trim();
  658. if (createUser.Length > 24)
  659. createUser = createUser[..24];
  660. var pars = new List<SugarParameter>
  661. {
  662. new("@WorkOrd", workOrd),
  663. new("@Domain", domain),
  664. new("@CreateUser", createUser)
  665. };
  666. await _db.Ado.BeginTranAsync();
  667. try
  668. {
  669. await _db.Ado.ExecuteCommandAsync(
  670. $"""
  671. DELETE FROM WorkOrdRouting rr
  672. WHERE {SqlWorkOrdChildMatchDomainOrTenant("rr", "m")}
  673. """,
  674. pars);
  675. // 业务口径:由 RoutingOpDetail + ProdLineDetail 重算工单工艺路线(MySQL)。
  676. // 与历史脚本一致:DELETE 仍按工单 + 域/租户匹配,避免误删其它域数据。
  677. await _db.Ado.ExecuteCommandAsync(
  678. $"""
  679. INSERT INTO WorkOrdRouting (
  680. `Descr`, `Domain`, `ChargeCode`, `Machine`, `RunCrew`, `MilestoneOp`, `OP`, `StdOp`, `ItemNum`, `WorkCtr`,
  681. `Ufld1`, `Ufld3`, `Setup`, `MachinesperOp`, `Labor`, `RunTime`, `MachBdnRate`, `WorkCode`, `StdSetupTime`, `Engineer`,
  682. `WorkOrd`, `WorkOrdMasterRecID`, `ERPfld1`, `QtyOrded`, `ProcessOut`, `ProcessOutDay`, `ProcessOutSupp`,
  683. `IsActive`, `Status`, `ProdLine`, `CreateTime`, `CreateUser`, `tenant_id`,`CommentIndex`,`WaitTime`
  684. )
  685. SELECT
  686. a.`Descr`,
  687. LEFT(TRIM(COALESCE(NULLIF(TRIM(IFNULL(w.`Domain`, '')), ''), NULLIF(TRIM(IFNULL(a.`Domain`, '')), ''), ' ')), 8),
  688. '',
  689. b.`InternalEquipmentCode`,
  690. IFNULL(b.`StandardStaffCount`, 0),
  691. CAST(IFNULL(a.`MilestoneOp`, 0) AS UNSIGNED),
  692. IFNULL(a.`Op`, 0),
  693. a.`StdOp`,
  694. a.`RoutingCode`,
  695. LEFT(IFNULL(b.`Site`, ''), 8),
  696. '',
  697. '',
  698. IFNULL(a.`UDeci1`, 0),
  699. IFNULL(a.`UDeci2`, 0),
  700. IFNULL(a.`UDeci3`, 0),
  701. IFNULL(a.`UDeci3`, 0) / 3600.0,
  702. IFNULL(b.`Rate`, 0),
  703. b.`OpType`,
  704. IFNULL(b.`SetupTime`, 0),
  705. b.`SkillNo`,
  706. w.`WorkOrd`,
  707. w.`RecID`,
  708. w.`ERPfld1`,
  709. IFNULL(w.`QtyOrded`, 0),
  710. CAST(IFNULL(a.`UDeci5`, 0) AS SIGNED),
  711. IFNULL(a.`ProcessOutDay`, 0),
  712. a.`ProcessOutSupp`,
  713. b'1',
  714. 'r',
  715. LEFT(IFNULL(b.`Line`, ''), 8),
  716. NOW(3),
  717. @CreateUser,
  718. w.`tenant_id`,CAST(IFNULL(a.`MilestoneOp`, 0) AS UNSIGNED),0
  719. FROM WorkOrdMaster w
  720. LEFT JOIN RoutingOpDetail a ON w.`ItemNum` = a.`RoutingCode`
  721. LEFT JOIN ProdLineDetail b ON a.`RoutingCode` = b.`Part` AND a.`Op` = b.`Op`
  722. WHERE w.`WorkOrd` = @WorkOrd
  723. AND {SqlWorkOrdMasterMatchDomainOrTenantAliased("w")}
  724. AND IFNULL(a.`IsActive`, 0) = 1
  725. AND a.`MilestoneOp` IS NOT NULL
  726. """,
  727. pars);
  728. await _db.Ado.CommitTranAsync();
  729. }
  730. catch (Exception ex)
  731. {
  732. await _db.Ado.RollbackTranAsync();
  733. throw Oops.Oh($"同步工艺路线失败:{ex.Message}");
  734. }
  735. return new { message = "同步工艺路线已执行" };
  736. }
  737. // ══════════════════════════════════════════════════════════════
  738. // 加急 POST /api/Production/scheduling/urgent
  739. // ══════════════════════════════════════════════════════════════
  740. [DisplayName("设置加急")]
  741. [HttpPost("scheduling/urgent")]
  742. public async Task<object> SetUrgent([FromBody] WorkOrderUrgentInput input)
  743. {
  744. var account = _userManager.Account ?? "system";
  745. var n = await _db.Ado.ExecuteCommandAsync(
  746. $"""
  747. UPDATE WorkOrdMaster
  748. SET Urgent = @Urgent,
  749. UpdateUser = @UpdateUser,
  750. UpdateTime = @UpdateTime
  751. WHERE {SqlWorkOrdMasterMatchDomainOrTenant}
  752. """,
  753. new List<SugarParameter>
  754. {
  755. new("@Urgent", input.Urgent),
  756. new("@UpdateUser", account),
  757. new("@UpdateTime", DateTime.Now),
  758. new("@WorkOrd", input.WorkOrd.Trim()),
  759. new("@Domain", input.Domain.Trim())
  760. });
  761. if (n == 0)
  762. throw Oops.Oh("工单不存在");
  763. return new { message = input.Urgent == 2 ? "已设为特急" : "已设为加急" };
  764. }
  765. // ──────────────── 行类型 ────────────────
  766. private sealed class WorkOrderSchedulingListRow
  767. {
  768. public int Id { get; set; }
  769. public string? Priority { get; set; }
  770. public string? WorkOrd { get; set; }
  771. public string? LotSerial { get; set; }
  772. public string? IssueSite { get; set; }
  773. public string? ItemNum { get; set; }
  774. public string? Descr { get; set; }
  775. public string? Descr1 { get; set; }
  776. public decimal? QtyOrded { get; set; }
  777. public decimal? LocationStock { get; set; }
  778. public decimal? QtyCompleted { get; set; }
  779. public DateTime? PlanDate { get; set; }
  780. public DateTime? ProdDate { get; set; }
  781. public string? Status { get; set; }
  782. public string? Domain { get; set; }
  783. public int? Urgent { get; set; }
  784. }
  785. private sealed class WorkOrderEditPreviewRow
  786. {
  787. public string? WorkOrd { get; set; }
  788. public string? ItemNum { get; set; }
  789. public decimal? QtyOrded { get; set; }
  790. public string? Priority { get; set; }
  791. public string? LotSerial { get; set; }
  792. public int Urgent { get; set; }
  793. public DateTime? OrdDate { get; set; }
  794. }
  795. private sealed class WorkOrderViewMasterRow
  796. {
  797. public string? WorkOrd { get; set; }
  798. public decimal? QtyOrded { get; set; }
  799. public decimal? QtyCompleted { get; set; }
  800. public string? ItemNum { get; set; }
  801. public string? ItemName { get; set; }
  802. public string? ItemModel { get; set; }
  803. public DateTime? OrdDate { get; set; }
  804. public DateTime? DueDate { get; set; }
  805. public string? Status { get; set; }
  806. public string? Remark { get; set; }
  807. }
  808. private sealed class WorkOrderViewRoutingRow
  809. {
  810. public string? Op { get; set; }
  811. public string? Descr { get; set; }
  812. public string? ParentOp { get; set; }
  813. public int MilestoneOp { get; set; }
  814. public decimal PackingQty { get; set; }
  815. public decimal QtyComplete { get; set; }
  816. public decimal QtyReject { get; set; }
  817. public decimal QtyScrap { get; set; }
  818. public string? Status { get; set; }
  819. }
  820. private sealed class WorkOrderViewDetailRow
  821. {
  822. public string? ItemNum { get; set; }
  823. public string? Op { get; set; }
  824. public decimal? QtyRequired { get; set; }
  825. public decimal FrozenBOMQty { get; set; }
  826. }
  827. private sealed class WorkOrderTraceMasterRow
  828. {
  829. public string? WorkOrd { get; set; }
  830. public decimal? QtyOrded { get; set; }
  831. public decimal? QtyCompleted { get; set; }
  832. public string? ItemNum { get; set; }
  833. public string? ItemName { get; set; }
  834. public string? ItemModel { get; set; }
  835. public DateTime? OrdDate { get; set; }
  836. public DateTime? DueDate { get; set; }
  837. public string? Status { get; set; }
  838. }
  839. private sealed class TraceResourceRow
  840. {
  841. public long Sno { get; set; }
  842. public string? Num { get; set; }
  843. public string? ItemNumber { get; set; }
  844. public string? ItemName { get; set; }
  845. public string? Model { get; set; }
  846. public string? Unit { get; set; }
  847. public string? BomNumber { get; set; }
  848. public string? KittingTime { get; set; }
  849. public string? ErpClsName { get; set; }
  850. public string? Backflush { get; set; }
  851. public string? Qty { get; set; }
  852. public string? NeedCount { get; set; }
  853. }
  854. private sealed class TraceScheduleRow
  855. {
  856. public long Sno { get; set; }
  857. public string? WorkOrds { get; set; }
  858. public string? WorkDate { get; set; }
  859. public string? Line { get; set; }
  860. public string? Op { get; set; }
  861. public string? WorkQty { get; set; }
  862. public string? ItemNum { get; set; }
  863. public string? Descr { get; set; }
  864. public string? Descr1 { get; set; }
  865. public string? CreateTime { get; set; }
  866. }
  867. private sealed class TracePoRow
  868. {
  869. public long Sno { get; set; }
  870. public string? PoBillno { get; set; }
  871. public string? SupplierNo { get; set; }
  872. public string? SupplierName { get; set; }
  873. public string? PoTotal { get; set; }
  874. public string? PoPurchaser { get; set; }
  875. public string? State { get; set; }
  876. public string? PoSsendDate { get; set; }
  877. public string? CreateTime { get; set; }
  878. }
  879. private sealed class TracePrRow
  880. {
  881. public long Sno { get; set; }
  882. public string? PrBillno { get; set; }
  883. public string? Number { get; set; }
  884. public string? IcitemName { get; set; }
  885. public string? Model { get; set; }
  886. public string? PrPurchasenumber { get; set; }
  887. public string? PrPurchasename { get; set; }
  888. public string? PrAqty { get; set; }
  889. public string? PrSsendDate { get; set; }
  890. public string? PrSarriveDate { get; set; }
  891. public string? PrUnit { get; set; }
  892. public string? PrPurchaser { get; set; }
  893. }
  894. private sealed class TraceNbrRow
  895. {
  896. public string? Line { get; set; }
  897. public string? Nbr { get; set; }
  898. public string? ItemNum { get; set; }
  899. public string? Descr { get; set; }
  900. public string? Descr1 { get; set; }
  901. public string? LocationFrom { get; set; }
  902. public string? LocationTo { get; set; }
  903. public string? CurrQtyOpened { get; set; }
  904. public string? QtyOrd { get; set; }
  905. public string? QtyFrom { get; set; }
  906. public string? QtyRec { get; set; }
  907. public string? Unit { get; set; }
  908. }
  909. private sealed class TraceProdRow
  910. {
  911. public long Sno { get; set; }
  912. public string? WorkOrd { get; set; }
  913. public string? ItemNum { get; set; }
  914. public string? Descr { get; set; }
  915. public string? Descr1 { get; set; }
  916. public string? Um { get; set; }
  917. public string? QtyCompleted { get; set; }
  918. public string? QtyReject { get; set; }
  919. public string? QtyScrapped { get; set; }
  920. public string? ProdDate { get; set; }
  921. public string? Op { get; set; }
  922. public string? Name { get; set; }
  923. }
  924. private sealed class TraceQualityRow
  925. {
  926. public long Sno { get; set; }
  927. public string? Unit { get; set; }
  928. public string? MorderNo { get; set; }
  929. public string? WorkNumber { get; set; }
  930. public string? MorderProductionNumber { get; set; }
  931. public string? InspectionNumber { get; set; }
  932. public string? QualifiedNumber { get; set; }
  933. public string? InventoryNumber { get; set; }
  934. public string? MoentryWrkcname { get; set; }
  935. public string? PlannerStartDate { get; set; }
  936. public string? PlannerEndDate { get; set; }
  937. public string? MoentryPrdname { get; set; }
  938. public string? MorderFstate { get; set; }
  939. }
  940. private sealed class TraceReceiptRow
  941. {
  942. public long Sno { get; set; }
  943. public string? Status { get; set; }
  944. public string? WorkOrd { get; set; }
  945. public string? UM { get; set; }
  946. public string? QtyOrded { get; set; }
  947. public string? CompQty { get; set; }
  948. public string? InspectionNumber { get; set; }
  949. public string? QualifiedNumber { get; set; }
  950. public string? QtyChangeAdvance { get; set; }
  951. public string? OrdDate { get; set; }
  952. public string? DueDate { get; set; }
  953. }
  954. }