OrderDeliveryService.cs 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718
  1. namespace Admin.NET.Plugin.AiDOP.Order;
  2. /// <summary>
  3. /// 订单交付服务 📦
  4. /// 路由前缀:/api/Order/delivery/...
  5. ///
  6. /// 【租户安全边界】需认证访问;租户一律经 <c>AidopTenantScope.ResolveOrThrow</c> 从认证后 JWT 解析,
  7. /// 无有效租户即拒绝,不读前端 tenantId、无默认回退(原实现:直接取 <c>_userManager.TenantId</c>,
  8. /// 无效租户时 TenantId=0 退化为查不到数据而非拒绝)。
  9. /// </summary>
  10. [ApiDescriptionSettings(Order = 270, Description = "订单交付")]
  11. [Route("api/Order")]
  12. [NonUnify]
  13. public class OrderDeliveryService : IDynamicApiController, ITransient
  14. {
  15. private readonly ISqlSugarClient _db;
  16. private readonly UserManager _userManager;
  17. public OrderDeliveryService(ISqlSugarClient db, UserManager userManager)
  18. {
  19. _db = db;
  20. _userManager = userManager;
  21. }
  22. // ══════════════════════════════════════════════════════════════
  23. // 列表 GET /api/Order/delivery/list
  24. // ══════════════════════════════════════════════════════════════
  25. /// <summary>获取订单交付分页列表 📦</summary>
  26. [DisplayName("获取订单交付列表")]
  27. [HttpGet("delivery/list")]
  28. [RequirePcMenuPath("/aidop/s1/order-mgmt/orderDelivery")]
  29. public async Task<object> GetDeliveryList([FromQuery] OrderDeliveryListInput input)
  30. {
  31. var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
  32. var pars = new List<SugarParameter> { new("@TenantId", tenantId) };
  33. var innerConditions = new List<string>
  34. {
  35. "e.tenant_id = @TenantId",
  36. "e.IsDeleted = 0",
  37. "o.bill_no IS NOT NULL"
  38. };
  39. if (!string.IsNullOrWhiteSpace(input.BillNo))
  40. {
  41. innerConditions.Add("o.bill_no LIKE @BillNo");
  42. pars.Add(new SugarParameter("@BillNo", $"%{input.BillNo.Trim()}%"));
  43. }
  44. if (!string.IsNullOrWhiteSpace(input.CustomNo))
  45. {
  46. innerConditions.Add("o.custom_no LIKE @CustomNo");
  47. pars.Add(new SugarParameter("@CustomNo", $"%{input.CustomNo.Trim()}%"));
  48. }
  49. if (!string.IsNullOrWhiteSpace(input.ItemNumber))
  50. {
  51. innerConditions.Add("e.item_number LIKE @ItemNumber");
  52. pars.Add(new SugarParameter("@ItemNumber", $"%{input.ItemNumber.Trim()}%"));
  53. }
  54. if (!string.IsNullOrWhiteSpace(input.PlanDateFrom))
  55. {
  56. innerConditions.Add("e.plan_date >= @PlanDateFrom");
  57. pars.Add(new SugarParameter("@PlanDateFrom", input.PlanDateFrom.Trim()));
  58. }
  59. var innerWhere = string.Join(" AND ", innerConditions);
  60. var baseSql = BuildListBaseSql(innerWhere);
  61. // progress 是计算列,需包一层过滤
  62. var outerWhere = "";
  63. if (!string.IsNullOrWhiteSpace(input.Progress))
  64. {
  65. outerWhere = "WHERE t.progress = @Progress";
  66. pars.Add(new SugarParameter("@Progress", input.Progress.Trim()));
  67. }
  68. var offset = (input.Page - 1) * input.PageSize;
  69. var total = await _db.Ado.GetIntAsync(
  70. $"SELECT COUNT(*) FROM ({baseSql}) AS t {outerWhere}", pars);
  71. var list = await _db.Ado.SqlQueryAsync<OrderDeliveryListRow>(
  72. $"SELECT * FROM ({baseSql}) AS t {outerWhere} ORDER BY t.PlanDate DESC, t.Id DESC LIMIT {input.PageSize} OFFSET {offset}",
  73. pars);
  74. return new { total, page = input.Page, pageSize = input.PageSize, list };
  75. }
  76. private static string BuildListBaseSql(string innerWhere) => $"""
  77. SELECT
  78. e.Id AS Id,
  79. e.seorder_id AS SeOrderId,
  80. o.bill_no AS BillNo,
  81. NULL AS Rstate,
  82. e.entry_seq AS EntrySeq,
  83. IFNULL(e.item_number, '') AS ItemNumber,
  84. IFNULL(e.item_name, '') AS ItemName,
  85. IFNULL(e.specification, '') AS Specification,
  86. e.unit AS Unit,
  87. IFNULL(o.custom_no, '') AS CustomNo,
  88. NULL AS CustomOrderBillNo,
  89. NULL AS CustomOrderItemNo,
  90. o.custom_level AS CustomLevel,
  91. e.qty AS Qty,
  92. e.deliver_count AS DeliverCount,
  93. NULL AS BomNumber,
  94. '' AS EmpName,
  95. '' AS PlannerName,
  96. e.plan_date AS PlanDate,
  97. e.sys_capacity_date AS SysCapacityDate,
  98. e.date AS Date,
  99. o.rdate AS RDate,
  100. e.create_time AS CreateTime,
  101. NULL AS CreateByName,
  102. NULL AS Auditor,
  103. COALESCE(NULLIF(e.progress, ''), '1') AS Progress,
  104. NULL AS ProdLine,
  105. NULL AS ProdRange,
  106. NULL AS ShipDate,
  107. NULL AS Recid,
  108. '否' AS Spstatus,
  109. IFNULL(NULLIF(me.mono_list, 'null'), '') AS MoentryMono
  110. FROM crm_seorderentry e
  111. INNER JOIN crm_seorder o ON o.Id = e.seorder_id AND o.tenant_id = e.tenant_id AND o.IsDeleted = 0
  112. LEFT JOIN (
  113. SELECT soentry_id, tenant_id,
  114. GROUP_CONCAT(moentry_mono SEPARATOR ', ') AS mono_list
  115. FROM mes_moentry
  116. WHERE IsDeleted = 0
  117. GROUP BY soentry_id, tenant_id
  118. ) me ON me.soentry_id = e.Id
  119. AND me.tenant_id = e.tenant_id
  120. WHERE {innerWhere}
  121. """;
  122. // ══════════════════════════════════════════════════════════════
  123. // 价值流 GET /api/Order/delivery/flow/{entryId}
  124. // ══════════════════════════════════════════════════════════════
  125. /// <summary>获取订单价值流全量数据(8个Tab)📦</summary>
  126. [DisplayName("获取订单价值流")]
  127. [HttpGet("delivery/flow/{entryId}")]
  128. [RequirePcMenuPath("/aidop/s1/order-mgmt/orderDelivery")]
  129. public async Task<object> GetDeliveryFlow(long entryId)
  130. {
  131. // 同一 ISqlSugarClient 底层共用一条连接,不能对 _db.Ado 并发执行多查询(MySqlConnector 会报 Cannot Open when State is Connecting)
  132. var basic = await QueryBasic(entryId);
  133. var orderInfo = await QueryOrderInfo(entryId);
  134. var review = await QueryReview(entryId);
  135. var changes = await QueryChanges(entryId);
  136. var schedule = await QuerySchedule(entryId);
  137. var material = await QueryMaterials(entryId);
  138. var po = await QueryPo(entryId);
  139. var pr = await QueryPr(entryId);
  140. var deliverySchedule = await QueryDeliverySchedule(entryId);
  141. var nbr = await QueryNbr(entryId);
  142. var prodReport = await QueryProdReport(entryId);
  143. var iqc = await QueryIqc(entryId);
  144. var fqc = await QueryFqc(entryId);
  145. var receipt = await QueryReceipt(entryId);
  146. var shipment = await QueryShipment(entryId);
  147. return new
  148. {
  149. basic,
  150. tab1 = orderInfo,
  151. tab2 = new { review, changes },
  152. tab3 = schedule,
  153. tab4 = material,
  154. tab5 = new { po, pr, deliverySchedule },
  155. tab6 = nbr,
  156. tab7 = new { prodReport, iqc },
  157. tab8 = new { fqc, receipt, shipment },
  158. };
  159. }
  160. // ── 基础信息 ──
  161. private async Task<FlowBasicRow?> QueryBasic(long entryId)
  162. {
  163. var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
  164. const string sql = """
  165. SELECT
  166. IFNULL(e.item_number, '') AS ItemNumber,
  167. IFNULL(e.item_name, '') AS ItemName,
  168. IFNULL(e.specification, '') AS Specification,
  169. e.qty AS Qty,
  170. COALESCE(o.bill_from, '') AS BillFrom,
  171. e.plan_date AS PlanDate,
  172. COALESCE(NULLIF(e.progress, ''), '1') AS Progress,
  173. e.update_time AS Mrptime
  174. FROM crm_seorderentry e
  175. INNER JOIN crm_seorder o ON o.Id = e.seorder_id AND o.tenant_id = e.tenant_id AND o.IsDeleted = 0
  176. WHERE (e.Id = @Id)
  177. AND e.tenant_id = @TenantId
  178. AND e.IsDeleted = 0
  179. AND o.bill_no IS NOT NULL
  180. ORDER BY e.update_time DESC, e.Id DESC
  181. LIMIT 1
  182. """;
  183. return (await _db.Ado.SqlQueryAsync<FlowBasicRow>(sql, new { Id = entryId, TenantId = tenantId })).FirstOrDefault();
  184. }
  185. // ── Tab1 订单信息 ──
  186. private async Task<FlowOrderInfoRow?> QueryOrderInfo(long entryId)
  187. {
  188. var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
  189. const string sql = """
  190. SELECT
  191. o.bill_no AS BillNo,
  192. o.custom_name AS CustomName,
  193. o.custom_level AS CustomLevel,
  194. COALESCE(o.flowstate, '') AS State,
  195. o.date AS Date,
  196. '' AS CreateByName,
  197. o.order_type AS OrderType,
  198. o.update_time AS AuditDate,
  199. '' AS Auditor,
  200. COALESCE(o.bill_from, '') AS BillFrom,
  201. '' AS EmpName,
  202. '' AS EmpNo
  203. FROM crm_seorder o
  204. INNER JOIN crm_seorderentry e ON e.seorder_id = o.Id AND e.tenant_id = o.tenant_id AND e.IsDeleted = 0
  205. WHERE e.Id = @Id
  206. AND o.tenant_id = @TenantId
  207. AND o.IsDeleted = 0
  208. LIMIT 1
  209. """;
  210. return (await _db.Ado.SqlQueryAsync<FlowOrderInfoRow>(sql, new { Id = entryId, TenantId = tenantId })).FirstOrDefault();
  211. }
  212. // ── Tab2 订单评审 ──
  213. /// <remarks>
  214. /// 查询最新评审记录(num=1 的首行物料)的预计交期评估信息。
  215. /// </remarks>
  216. private async Task<FlowReviewRow?> QueryReview(long entryId)
  217. {
  218. var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
  219. const string sql = """
  220. SELECT
  221. bce.satisfy_time AS SysCapacityDate,
  222. ber.morder_no AS MorderNo,
  223. ber.create_time AS CreateTime,
  224. bce.make_qty AS MakeQty,
  225. bce.use_qty AS UseQty,
  226. bce.mo_qty AS MoQty
  227. FROM b_examine_result ber
  228. INNER JOIN b_bom_child_examine bce ON ber.Id = bce.examine_id AND bce.num = '1' AND bce.is_use = 1
  229. WHERE ber.sentry_id = @EntryId
  230. AND ber.tenant_id = @TenantId
  231. AND ber.IsDeleted = 0
  232. AND ber.Id = (
  233. SELECT br.Id FROM b_examine_result br
  234. WHERE br.sentry_id = @EntryId
  235. AND br.tenant_id = @TenantId
  236. AND br.IsDeleted = 0
  237. ORDER BY br.create_time DESC
  238. LIMIT 1
  239. )
  240. ORDER BY bce.id
  241. LIMIT 1
  242. """;
  243. return (await _db.Ado.SqlQueryAsync<FlowReviewRow>(sql, new { EntryId = entryId, TenantId = tenantId })).FirstOrDefault();
  244. }
  245. // ── Tab2 变更记录(MySQL:按订单行关联变更单)──
  246. private async Task<List<FlowChangeRow>> QueryChanges(long entryId)
  247. {
  248. var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
  249. const string sql = """
  250. SELECT
  251. ROW_NUMBER() OVER (ORDER BY c.create_time, c.Id) AS Sno,
  252. c.bill_no AS BillNo,
  253. c.change_Type AS ChangeType,
  254. c.change_Reason AS ChangeReason,
  255. c.change_content AS ChangeContent,
  256. c.update_time AS UpdateTime
  257. FROM crm_seorder_change c
  258. WHERE c.seorder_id = (
  259. SELECT e.seorder_id FROM crm_seorderentry e
  260. WHERE e.Id = @Id AND e.tenant_id = @TenantId AND e.IsDeleted = 0
  261. LIMIT 1
  262. )
  263. AND c.tenant_id = @TenantId
  264. AND c.IsDeleted = 0
  265. ORDER BY c.create_time, c.Id
  266. """;
  267. return await _db.Ado.SqlQueryAsync<FlowChangeRow>(sql, new { Id = entryId, TenantId = tenantId });
  268. }
  269. // ── Tab3 计划排程1(MySQL:按分录 Id 关联工单排程 + 物料主档)──
  270. private async Task<List<FlowScheduleRow>> QuerySchedule(long entryId)
  271. {
  272. var tenantId = Infrastructure.AidopTenantScope.ResolveOrThrow(_userManager);
  273. const string sql = """
  274. SELECT
  275. ROW_NUMBER() OVER (ORDER BY sch.PlanDate) AS Sno,
  276. sch.WorkOrds,
  277. DATE(sch.PlanDate) AS WorkDate,
  278. sch.Line,
  279. sch.Op,
  280. CAST(sch.OrdQty AS DECIMAL(18, 2)) AS WorkQty,
  281. sch.ItemNum,
  282. im.Descr,
  283. im.Descr1,
  284. sch.CreateTime
  285. FROM PeriodSequenceDet sch
  286. LEFT JOIN ItemMaster im ON sch.ItemNum = im.ItemNum AND im.tenant_id = @TenantId
  287. LEFT JOIN mes_morder mo ON mo.morder_no = sch.WorkOrds AND mo.tenant_id = @TenantId
  288. INNER JOIN mes_moentry me ON mo.Id = me.moentry_moid AND me.tenant_id = @TenantId
  289. LEFT JOIN crm_seorderentry se ON se.Id = me.soentry_id
  290. WHERE se.id = @EntryId AND se.tenant_id = @TenantId AND mo.IsDeleted = 0
  291. ORDER BY sch.PlanDate
  292. """;
  293. return await _db.Ado.SqlQueryAsync<FlowScheduleRow>(sql, new { EntryId = entryId, TenantId = tenantId });
  294. }
  295. // ── Tab4 物料需求(MySQL:最新评审结果 + BOM 子件明细,同需求明细核验口径)──
  296. private async Task<List<FlowMaterialRow>> QueryMaterials(long entryId)
  297. {
  298. var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
  299. const string sql = """
  300. SELECT
  301. ROW_NUMBER() OVER (ORDER BY bce.id) AS Sno,
  302. bce.num AS Num,
  303. IFNULL(bce.item_number, '') AS ItemNumber,
  304. IFNULL(bce.item_name, '') AS ItemName,
  305. bce.bom_number AS BomNumber,
  306. bce.model AS Model,
  307. DATE_FORMAT(bce.kitting_time, '%Y-%m-%d') AS KittingTime,
  308. bce.type AS Type,
  309. NULL AS Unit,
  310. bce.erp_cls_name AS ErpClsName,
  311. bce.qty AS Qty,
  312. bce.needCount AS NeedCount,
  313. bce.sqty AS Sqty,
  314. bce.use_qty AS UseQty,
  315. bce.self_lack_qty AS SelfLackQty,
  316. bce.lack_qty AS LackQty,
  317. bce.mo_qty AS MoQty,
  318. bce.make_qty AS MakeQty,
  319. bce.purchase_qty AS PurchaseQty,
  320. bce.purchase_occupy_qty AS PurchaseOccupyQty,
  321. NULL AS SubcontractingQty,
  322. NULL AS Backflush,
  323. DATE_FORMAT(bce.satisfy_time, '%Y-%m-%d') AS SatisfyTime,
  324. bce.haveicsubs AS HaveIcSubs,
  325. bce.substitute_code AS SubstituteCode
  326. FROM b_bom_child_examine bce
  327. INNER JOIN b_examine_result ber ON ber.Id = bce.examine_id
  328. WHERE ber.sentry_id = @EntryId
  329. AND ber.tenant_id = @TenantId
  330. AND ber.IsDeleted = 0
  331. AND bce.is_use = 1
  332. AND ber.Id = (
  333. SELECT br.Id FROM b_examine_result br
  334. WHERE br.sentry_id = @EntryId
  335. AND br.tenant_id = @TenantId
  336. AND br.IsDeleted = 0
  337. ORDER BY br.create_time DESC
  338. LIMIT 1
  339. )
  340. ORDER BY bce.id ASC
  341. """;
  342. return await _db.Ado.SqlQueryAsync<FlowMaterialRow>(sql, new { EntryId = entryId, TenantId = tenantId });
  343. }
  344. // ── Tab5 采购订单(MySQL:参考工单执行跟踪,双路合并 PW + DO/PO)──
  345. private async Task<List<FlowPoRow>> QueryPo(long entryId)
  346. {
  347. var tenantId = Infrastructure.AidopTenantScope.ResolveOrThrow(_userManager);
  348. const string sql = """
  349. SELECT
  350. ROW_NUMBER() OVER (ORDER BY PoBillno) AS Sno,
  351. PoBillno, SupplierNo, SupplierName, PoTotal, PoPurchaser, State, PoSsendDate, CreateTime
  352. FROM (
  353. SELECT DISTINCT
  354. pm.PurOrd AS PoBillno,
  355. IFNULL(TRIM(pm.Supp), '') AS SupplierNo,
  356. IFNULL(TRIM(s.SortName), '') AS SupplierName,
  357. CAST(IFNULL(det.PoTotal, 0) AS DECIMAL(18, 2)) AS PoTotal,
  358. IFNULL(TRIM(pm.Buyer), '') AS PoPurchaser,
  359. CASE
  360. WHEN IFNULL(LENGTH(pm.Status), 0) = 0 THEN '新增'
  361. WHEN pm.Status = 'R' THEN '新增'
  362. WHEN pm.Status = 'S' THEN '已下达'
  363. WHEN pm.Status = 'C' THEN '关闭'
  364. ELSE pm.Status
  365. END AS State,
  366. pm.OrdDate AS PoSsendDate,
  367. pm.CreateTime AS CreateTime
  368. FROM PurOrdMaster pm
  369. LEFT JOIN SuppMaster s ON pm.Supp = s.Supp AND s.tenant_id = pm.tenant_id
  370. LEFT JOIN (
  371. SELECT PurOrdRecID,
  372. SUM(IFNULL(QtyOrded, 0) * IFNULL(PurCost, 0)) AS PoTotal
  373. FROM PurOrdDetail
  374. GROUP BY PurOrdRecID
  375. ) det ON det.PurOrdRecID = pm.RecID
  376. WHERE pm.tenant_id = @TenantId
  377. AND IFNULL(pm.IsActive, 0) <= 1
  378. AND (
  379. pm.WorkOrd IN (
  380. SELECT me.moentry_mono FROM mes_moentry me
  381. WHERE me.soentry_id = @EntryId AND me.tenant_id = @TenantId
  382. )
  383. OR pm.RecID IN (
  384. SELECT pd.PurOrdRecID
  385. FROM PurOrdDetail pd
  386. INNER JOIN srm_pr_main pr
  387. ON (pr.pr_billno = pd.Req OR pr.SAP_pr_billno = pd.Req)
  388. AND pr.tenant_id = pd.tenant_id
  389. WHERE pr.pr_mono IN (
  390. SELECT me.moentry_mono FROM mes_moentry me
  391. WHERE me.soentry_id = @EntryId AND me.tenant_id = @TenantId
  392. )
  393. AND pr.tenant_id = @TenantId
  394. AND IFNULL(pr.state, 0) <> 0
  395. AND IFNULL(pr.IsDeleted, 0) = 0
  396. )
  397. )
  398. ) AS t
  399. ORDER BY PoBillno
  400. """;
  401. return await _db.Ado.SqlQueryAsync<FlowPoRow>(sql, new { EntryId = entryId, TenantId = tenantId });
  402. }
  403. // ── Tab5 采购申请(MySQL:参考工单执行跟踪,pr_mono 直连工单)──
  404. private async Task<List<FlowPrRow>> QueryPr(long entryId)
  405. {
  406. var tenantId = Infrastructure.AidopTenantScope.ResolveOrThrow(_userManager);
  407. const string sql = """
  408. SELECT
  409. ROW_NUMBER() OVER (ORDER BY pm.pr_billno) AS Sno,
  410. pm.pr_billno AS PrBillNo,
  411. IFNULL(ic.number, '') AS Number,
  412. IFNULL(pm.icitem_name, '') AS IcItemName,
  413. IFNULL(ic.model, '') AS Model,
  414. IFNULL(pm.pr_purchasenumber, '') AS PrPurchaseNumber,
  415. IFNULL(pm.pr_purchasename, '') AS PrPurchaseName,
  416. CAST(IFNULL(pm.pr_aqty, 0) AS DECIMAL(18, 4)) AS PrAQty,
  417. pm.pr_ssend_date AS PrSsendDate,
  418. pm.pr_sarrive_date AS PrSarriveDate,
  419. IFNULL(pm.pr_unit, '') AS PrUnit,
  420. IFNULL(pm.pr_purchaser, '') AS PrPurchaser
  421. FROM srm_pr_main pm
  422. LEFT JOIN ic_item ic ON pm.icitem_id = ic.Id
  423. WHERE pm.pr_mono IN (
  424. SELECT me.moentry_mono FROM mes_moentry me
  425. WHERE me.soentry_id = @EntryId AND me.tenant_id = @TenantId
  426. )
  427. AND pm.tenant_id = @TenantId
  428. AND IFNULL(pm.state, 0) <> 0
  429. AND pm.IsDeleted = 0
  430. ORDER BY pm.pr_billno
  431. """;
  432. return await _db.Ado.SqlQueryAsync<FlowPrRow>(sql, new { EntryId = entryId, TenantId = tenantId });
  433. }
  434. // ── Tab5 物料交货单(MySQL:经采购订单关联到交货单 srm_polist_ds)──
  435. private async Task<List<FlowDeliveryScheduleRow>> QueryDeliverySchedule(long entryId)
  436. {
  437. var tenantId = Infrastructure.AidopTenantScope.ResolveOrThrow(_userManager);
  438. const string sql = """
  439. SELECT
  440. ROW_NUMBER() OVER (ORDER BY t.DsNum) AS Sno,
  441. t.PoNumber,
  442. t.DsNum,
  443. t.ItemNum,
  444. t.Descr,
  445. t.SupplierCode,
  446. t.Supplier,
  447. t.RequestDate,
  448. t.NeedDate,
  449. t.SchedQty,
  450. t.SentQty,
  451. t.RestQty
  452. FROM (
  453. SELECT DISTINCT
  454. ds.ponumber AS PoNumber,
  455. ds.dsnum AS DsNum,
  456. ds.itemnum AS ItemNum,
  457. IFNULL(im.Descr, '') AS Descr,
  458. ds.suppliercode AS SupplierCode,
  459. ds.supplier AS Supplier,
  460. ds.requestdate AS RequestDate,
  461. ds.needdate AS NeedDate,
  462. CAST(IFNULL(ds.schedqty, 0) AS DECIMAL(18, 4)) AS SchedQty,
  463. CAST(IFNULL(ds.sentqty, 0) AS DECIMAL(18, 4)) AS SentQty,
  464. CAST(IFNULL(ds.restqty, 0) AS DECIMAL(18, 4)) AS RestQty
  465. FROM PurOrdMaster pm
  466. INNER JOIN srm_polist_ds ds
  467. ON pm.PurOrd = ds.ponumber
  468. AND ds.tenant_id = pm.tenant_id
  469. LEFT JOIN ItemMaster im
  470. ON ds.itemnum = im.ItemNum
  471. AND (im.tenant_id = pm.tenant_id OR pm.tenant_id IS NULL)
  472. WHERE pm.tenant_id = @TenantId
  473. AND IFNULL(pm.IsActive, 0) <= 1
  474. AND (
  475. pm.WorkOrd IN (
  476. SELECT me.moentry_mono FROM mes_moentry me
  477. WHERE me.soentry_id = @EntryId AND me.tenant_id = @TenantId
  478. )
  479. OR pm.RecID IN (
  480. SELECT pd.PurOrdRecID
  481. FROM PurOrdDetail pd
  482. INNER JOIN srm_pr_main pr
  483. ON (pr.pr_billno = pd.Req OR pr.SAP_pr_billno = pd.Req)
  484. AND pr.tenant_id = pd.tenant_id
  485. WHERE pr.pr_mono IN (
  486. SELECT me.moentry_mono FROM mes_moentry me
  487. WHERE me.soentry_id = @EntryId AND me.tenant_id = @TenantId
  488. )
  489. AND pr.tenant_id = @TenantId
  490. AND IFNULL(pr.state, 0) <> 0
  491. AND IFNULL(pr.IsDeleted, 0) = 0
  492. )
  493. )
  494. ) AS t
  495. ORDER BY t.DsNum
  496. """;
  497. return await _db.Ado.SqlQueryAsync<FlowDeliveryScheduleRow>(sql, new { EntryId = entryId, TenantId = tenantId });
  498. }
  499. // ── Tab6 生产备料(MySQL:备料明细经工单关联分录)──
  500. private async Task<List<FlowNbrRow>> QueryNbr(long entryId)
  501. {
  502. var tenantId = Infrastructure.AidopTenantScope.ResolveOrThrow(_userManager);
  503. const string sql = """
  504. SELECT
  505. CAST(nd.Line AS CHAR) AS Line,
  506. nm.Nbr AS Nbr,
  507. nd.ItemNum,
  508. im.Descr AS Descr,
  509. im.Descr1 AS Descr1,
  510. CAST(nd.QtyFrom AS DECIMAL(18, 2)) AS QtyFrom,
  511. CAST(nd.QtyOrd AS DECIMAL(18, 2)) AS QtyOrd,
  512. CAST(nd.CurrQtyOpened AS DECIMAL(18, 2)) AS CurrQtyOpened,
  513. CAST(nd.QtyRec AS DECIMAL(18, 2)) AS QtyRec,
  514. nd.UM AS Unit,
  515. nd.LocationFrom,
  516. nd.LocationTo,
  517. nd.UpdateTime AS UpdateTime,
  518. nd.WorkOrd AS WorkOrd
  519. FROM NbrDetail nd
  520. LEFT JOIN NbrMaster nm ON nd.nbr = nm.nbr AND nm.tenant_id = @TenantId
  521. LEFT JOIN mes_morder mo ON mo.morder_no = nm.WorkOrd AND mo.tenant_id = @TenantId
  522. INNER JOIN mes_moentry me ON mo.Id = me.moentry_moid AND me.tenant_id = @TenantId
  523. LEFT JOIN crm_seorderentry se ON se.Id = me.soentry_id AND mo.product_code = se.item_number
  524. LEFT JOIN ItemMaster im ON nd.ItemNum = im.ItemNum AND im.tenant_id = @TenantId
  525. WHERE se.id = @EntryId AND se.tenant_id = @TenantId AND mo.IsDeleted = 0 AND nm.Type = 'SM'
  526. ORDER BY nm.Nbr
  527. """;
  528. return await _db.Ado.SqlQueryAsync<FlowNbrRow>(sql, new { EntryId = entryId, TenantId = tenantId });
  529. }
  530. // ── Tab7 生产报工(OpTransEmployee + 销售订单行)──
  531. private async Task<List<FlowProdReportRow>> QueryProdReport(long entryId)
  532. {
  533. var tenantId = Infrastructure.AidopTenantScope.ResolveOrThrow(_userManager);
  534. const string sql = """
  535. SELECT
  536. ROW_NUMBER() OVER (ORDER BY ote.ProdDate DESC) AS Sno,
  537. ote.WorkOrd,
  538. ote.Name AS Name,
  539. ote.Op,
  540. ote.ProdDate,
  541. im.ItemNum,
  542. im.Descr,
  543. im.Descr1,
  544. im.Um,
  545. CAST(ote.QtyCompleted AS DECIMAL(18,2)) AS QtyCompleted,
  546. CAST(ote.QtyReject AS DECIMAL(18,2)) AS QtyReject,
  547. CAST(ote.QtyScrapped AS DECIMAL(18,2)) AS QtyScrapped
  548. FROM OpTransEmployee ote
  549. LEFT JOIN mes_morder mo ON mo.morder_no = ote.WorkOrd AND mo.tenant_id = @TenantId
  550. INNER JOIN mes_moentry me ON mo.Id = me.moentry_moid AND me.tenant_id = @TenantId
  551. LEFT JOIN crm_seorderentry se ON se.Id = me.soentry_id
  552. LEFT JOIN ItemMaster im ON ote.ItemNum = im.ItemNum AND im.tenant_id = @TenantId
  553. WHERE se.id = @EntryId AND se.tenant_id = @TenantId AND mo.IsDeleted = 0
  554. ORDER BY ote.ProdDate DESC
  555. """;
  556. return await _db.Ado.SqlQueryAsync<FlowProdReportRow>(sql, new { EntryId = entryId, TenantId = tenantId });
  557. }
  558. // ── Tab7 IQC信息(请购/采购/检验链路)──
  559. private async Task<List<FlowIqcRow>> QueryIqc(long entryId)
  560. {
  561. // 同库分租户:IQC 派生表锚表 qms_qcp_inspbill 已补 tenant_id,按当前租户过滤(避免跨租户检验单串入链路)。
  562. const string sql = """
  563. SELECT
  564. ROW_NUMBER() OVER (ORDER BY iqcjy.FAPPLYTIME DESC) AS Line,
  565. iqcjy.po_bill AS PoBill,
  566. iqcjy.po_billline AS PoBillLine,
  567. iqcjy.FBILLNO AS FBillNo,
  568. iqcjy.FMATERIALCFG AS FMaterialCfg,
  569. iqcjy.wlmc AS Wlmc,
  570. iqcjy.ggxh AS Ggxh,
  571. CAST(iqcjy.FRINSQTY AS DECIMAL(18,2)) AS FRinsQty,
  572. iqcjy.FAPPLYTIME AS FApplyTime,
  573. iqcjy.FINSPESTARTDATE AS FInspeStartDate,
  574. iqcjy.FINSPECTSTATUS AS FInspecStatus
  575. FROM srm_pr_main pr
  576. LEFT JOIN PurOrdDetail pod ON pr.pr_billno = pod.Req LEFT JOIN vscm_cgshrk sh ON pod.PurOrd = sh.OrdNbr AND CAST(pod.Line AS CHAR(32)) = CAST(sh.OrdLine AS CHAR(32)) LEFT JOIN scm_jhjh_jq jq ON pod.PurOrd = jq.cgdd AND CAST(pod.Line AS CHAR(32)) = CAST(jq.ddhh AS CHAR(32)) LEFT JOIN (
  577. SELECT
  578. f.po_bill,
  579. f.po_billline,
  580. a.FINSPESTARTDATE,
  581. b.FAPPLYTIME,
  582. b.FBILLNO,
  583. a.FMATERIALCFG,
  584. a.wlmc,
  585. a.ggxh,
  586. a.FRINSQTY,
  587. c.FINSPECTSTATUS
  588. FROM qms_qcp_inspbill a
  589. LEFT JOIN qms_qcp_inspecapplyn b ON a.lydjbh = b.FBILLNO AND b.tenant_id = @TenantId LEFT JOIN qms_qcp_insappnentry c ON b.id = c.glid AND c.tenant_id = @TenantId
  590. LEFT JOIN scm_shd cd ON c.shdh = cd.shddh LEFT JOIN scm_shdzb f ON f.shpc = c.FLOTNUMBER AND f.sh_material_code = c.FMATERIALCFG AND f.glid = cd.id
  591. WHERE a.tenant_id = @TenantId
  592. ) iqcjy ON pod.PurOrd = iqcjy.po_bill AND CAST(pod.Line AS CHAR(32)) = CAST(iqcjy.po_billline AS CHAR(32)) WHERE jq.flag = '1'
  593. AND pr.sentry_id IS NOT NULL
  594. AND pr.sentry_id = @EntryId
  595. ORDER BY iqcjy.FAPPLYTIME DESC
  596. """;
  597. return await _db.Ado.SqlQueryAsync<FlowIqcRow>(sql, new { EntryId = entryId, TenantId = AidopTenantScope.ResolveOrThrow(_userManager) });
  598. }
  599. // ── Tab8 FQC信息 ──
  600. private async Task<List<FlowFqcRow>> QueryFqc(long entryId)
  601. {
  602. var tenantId = Infrastructure.AidopTenantScope.ResolveOrThrow(_userManager);
  603. const string sql = """
  604. SELECT
  605. ROW_NUMBER() OVER (ORDER BY a.FBILLNO ASC) AS Sno,
  606. b.WorkOrd,
  607. a.sczld,
  608. a.FBILLNO,
  609. a.FMATERIALCFG AS FMaterialCfg,
  610. a.wlmc,
  611. a.ggxh,
  612. a.scph,
  613. CAST(a.FRINSQTY AS DECIMAL(18,2)) AS FRinsQty,
  614. a.FINSPEENDDATE AS FInspeEndDate
  615. FROM qms_qcpp_inspbill a
  616. LEFT JOIN WorkOrdMaster b ON a.sczld = b.Batch AND b.tenant_id = @TenantId
  617. LEFT JOIN mes_moentry c ON b.WorkOrd = c.moentry_mono AND c.tenant_id = @TenantId
  618. LEFT JOIN crm_seorderentry se ON c.soentry_id = se.id
  619. WHERE c.soentry_id = @EntryId AND se.tenant_id = @TenantId
  620. ORDER BY a.FBILLNO
  621. """;
  622. return await _db.Ado.SqlQueryAsync<FlowFqcRow>(sql, new { EntryId = entryId, TenantId = tenantId });
  623. }
  624. // ── Tab8 生产入库 ──
  625. private async Task<List<FlowReceiptRow>> QueryReceipt(long entryId)
  626. {
  627. var tenantId = Infrastructure.AidopTenantScope.ResolveOrThrow(_userManager);
  628. const string sql = """
  629. SELECT
  630. ROW_NUMBER() OVER (ORDER BY mo.morder_no) AS Sno,
  631. CASE
  632. WHEN c.Status = 'r' THEN '下达'
  633. WHEN c.Status = 'c' THEN '关闭'
  634. WHEN c.Status = 'w' THEN '投产'
  635. ELSE '初始'
  636. END AS Status,
  637. c.WorkOrd,
  638. im.Um,
  639. c.QtyOrded,
  640. IFNULL(compp.CompQty, 0) AS CompQty,
  641. NULL AS InspectionNumber,
  642. NULL AS QualifiedNumber,
  643. CASE WHEN a.qty_change > 0 THEN a.qty_change ELSE 0 END AS QtyChangeAdvance,
  644. mo.moentry_prdname,
  645. mo.moentry_wrkcname,
  646. c.OrdDate,
  647. c.DueDate,
  648. a.trans_time AS CreateTime,
  649. c.itemnum AS ItemNum,
  650. im.descr AS Descr
  651. FROM WorkOrdMaster c
  652. LEFT JOIN mdp_std_inv_trans a ON a.work_ord = c.WorkOrd AND IFNULL(a.src_trans_type_raw, a.trans_type) = 'rct-wo' AND a.qty_change > 0 AND a.tenant_id = c.tenant_id
  653. LEFT JOIN GeneralizedCodeMaster b ON IFNULL(a.src_trans_type_raw, a.trans_type) = b.Val AND b.FldName = 'TransType' AND b.`Domain` = c.`Domain` AND b.tenant_id = c.tenant_id
  654. LEFT JOIN ItemMaster im ON c.ItemNum = im.ItemNum AND im.tenant_id = c.tenant_id
  655. LEFT JOIN PeriodSequenceDet det ON det.ItemNum = im.ItemNum AND det.WorkOrds = c.WorkOrd AND det.tenant_id = c.tenant_id
  656. LEFT JOIN (
  657. SELECT WorkOrds, tenant_id, SUM(IFNULL(CompQty, 0)) AS CompQty
  658. FROM PeriodSequenceDet
  659. GROUP BY WorkOrds, tenant_id
  660. ) compp ON c.WorkOrd = compp.WorkOrds AND compp.tenant_id = c.tenant_id
  661. LEFT JOIN mes_morder mo ON mo.morder_no = c.WorkOrd AND mo.IsDeleted = 0 AND mo.tenant_id = c.tenant_id
  662. LEFT JOIN mes_moentry motry ON motry.moentry_moid = mo.Id AND motry.IsDeleted = 0 AND motry.tenant_id = c.tenant_id
  663. INNER JOIN crm_seorderentry se ON motry.soentry_id = se.id
  664. WHERE motry.soentry_id = @EntryId AND se.tenant_id = @TenantId AND c.tenant_id = @TenantId
  665. ORDER BY mo.morder_no
  666. """;
  667. return await _db.Ado.SqlQueryAsync<FlowReceiptRow>(sql, new { EntryId = entryId, TenantId = tenantId });
  668. }
  669. // ── Tab8 成品发运(与订单行 OrdNbr + 行物料一致)──
  670. private async Task<List<FlowShipmentRow>> QueryShipment(long entryId)
  671. {
  672. var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
  673. const string sql = """
  674. SELECT
  675. ROW_NUMBER() OVER (ORDER BY d.ShipDate DESC, d.RecID DESC) AS Sno,
  676. COALESCE(d.LotSerial, CAST(d.RecID AS CHAR)) AS Id,
  677. d.Site AS Location,
  678. d.ShipDate AS ShipDate,
  679. CAST(d.QtyToShip AS DECIMAL(18,2)) AS QtyToShip,
  680. d.Site AS LocationCode,
  681. d.RealQty AS RealQty
  682. FROM ASNBOLShipperDetail d
  683. INNER JOIN ASNBOLShipperMaster m ON m.RecID = d.ASNBOLShipperRecID AND m.tenant_id = d.tenant_id
  684. INNER JOIN crm_seorder o ON o.bill_no = m.OrdNbr AND o.tenant_id = m.tenant_id AND o.IsDeleted = 0
  685. INNER JOIN crm_seorderentry e ON e.seorder_id = o.Id AND e.tenant_id = o.tenant_id AND e.IsDeleted = 0
  686. WHERE e.Id = @EntryId
  687. AND d.tenant_id = @TenantId
  688. ORDER BY d.ShipDate DESC, d.RecID DESC
  689. """;
  690. return await _db.Ado.SqlQueryAsync<FlowShipmentRow>(sql, new { EntryId = entryId, TenantId = tenantId });
  691. }
  692. }