ProcessOutsourceOrderService.cs 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912
  1. namespace Admin.NET.Plugin.AiDOP.Supply;
  2. /// <summary>
  3. /// 工序外协订单服务
  4. /// </summary>
  5. [ApiDescriptionSettings(Order = 311, Description = "工序外协订单")]
  6. [Route("api/Supply")]
  7. [AllowAnonymous]
  8. [NonUnify]
  9. public class ProcessOutsourceOrderService : IDynamicApiController, ITransient
  10. {
  11. private const int PwPurOrdSerialWidth = 4;
  12. private readonly ISqlSugarClient _db;
  13. private readonly UserManager _userManager;
  14. public ProcessOutsourceOrderService(ISqlSugarClient db, UserManager userManager)
  15. {
  16. _db = db;
  17. _userManager = userManager;
  18. }
  19. [DisplayName("工序外协订单列表")]
  20. [HttpGet("process-outsource-order/list")]
  21. public async Task<object> GetList([FromQuery] ProcessOutsourceOrderListInput input)
  22. {
  23. var page = input.Page <= 0 ? 1 : input.Page;
  24. var pageSize = input.PageSize <= 0 ? 10 : input.PageSize;
  25. var offset = (page - 1) * pageSize;
  26. var where = new List<string> { "p.Potype='PW'", "p.IsActive<=1", "IFNULL(p.`Usage`,'')='外协'" };
  27. var pars = new List<SugarParameter>();
  28. if (_userManager.TenantId > 0)
  29. {
  30. where.Add("p.tenant_id = @TenantId");
  31. pars.Add(new SugarParameter("@TenantId", _userManager.TenantId));
  32. }
  33. if (!string.IsNullOrWhiteSpace(input.PurOrd))
  34. {
  35. where.Add("p.PurOrd LIKE @PurOrd");
  36. pars.Add(new SugarParameter("@PurOrd", $"%{input.PurOrd.Trim()}%"));
  37. }
  38. if (!string.IsNullOrWhiteSpace(input.WorkOrd))
  39. {
  40. where.Add("p.WorkOrd LIKE @WorkOrd");
  41. pars.Add(new SugarParameter("@WorkOrd", $"%{input.WorkOrd.Trim()}%"));
  42. }
  43. if (!string.IsNullOrWhiteSpace(input.Buyer))
  44. {
  45. where.Add("p.Buyer=@Buyer");
  46. pars.Add(new SugarParameter("@Buyer", input.Buyer.Trim()));
  47. }
  48. if (!string.IsNullOrWhiteSpace(input.Supp))
  49. {
  50. where.Add("p.Supp=@Supp");
  51. pars.Add(new SugarParameter("@Supp", input.Supp.Trim()));
  52. }
  53. var baseSql = $"""
  54. SELECT
  55. p.RecID AS Id,
  56. p.PurOrd AS PurOrd,
  57. p.WorkOrd AS WorkOrd,
  58. p.ERPWorkOrd AS ERPWorkOrd,
  59. CONCAT(TRIM(IFNULL(p.Supp,'')),' ',TRIM(IFNULL(s.SortName,''))) AS SuppName,
  60. p.Buyer AS BuyerCode,
  61. CONCAT(TRIM(IFNULL(p.Buyer,'')),' ',TRIM(IFNULL(e.Name,''))) AS Buyer,
  62. f.ItemNum AS ItemNum,
  63. i.Descr AS ItemName,
  64. CONCAT(TRIM(CAST(IFNULL(r.Op,0) AS CHAR(20))),' ',TRIM(IFNULL(r.Descr,''))) AS Op,
  65. p.OrdDate AS OrdDate,
  66. f.QtyOrded AS QtyOrded,
  67. f.CumQtyBO AS CumQtyBO,
  68. f.DueDate AS DueDate,
  69. n.`Date` AS FlDate,
  70. CASE
  71. WHEN IFNULL(LENGTH(p.Status),0)=0 OR p.Buyer IS NULL THEN 'R'
  72. ELSE p.Status
  73. END AS Status,
  74. p.Supp AS Supp,
  75. p.ReqBy AS ReqBy,
  76. p.Department AS Department,
  77. p.`Usage` AS `Usage`,
  78. p.Curr AS Curr,
  79. p.Remark AS Remark,
  80. (CASE WHEN EXISTS(SELECT 1 FROM PurOrdDetail pd WHERE pd.PurOrd=p.PurOrd AND IFNULL(pd.QtyReceived,0)>0) THEN 1 ELSE 0 END) AS HasReceived
  81. FROM PurOrdMaster p
  82. LEFT JOIN SuppMaster s ON p.Supp=s.Supp
  83. LEFT JOIN DepartmentMaster d ON p.Department=d.Department
  84. LEFT JOIN EmployeeMaster e ON p.Buyer=e.Employee
  85. LEFT JOIN PurOrdDetail f ON p.PurOrd=f.PurOrd
  86. LEFT JOIN RoutingOpDetail r ON f.Op=r.Op AND f.ItemNum=r.RoutingCode
  87. LEFT JOIN ItemMaster i ON f.ItemNum=i.ItemNum
  88. LEFT JOIN PurOrdRctMaster o ON o.OrdNbr=p.PurOrd
  89. LEFT JOIN NbrMaster n ON p.WorkOrd=n.WorkOrd AND n.Type='CA'
  90. WHERE {string.Join(" AND ", where)}
  91. """;
  92. var outerWhere = string.Empty;
  93. if (!string.IsNullOrWhiteSpace(input.Status))
  94. {
  95. outerWhere = "WHERE t.Status=@Status";
  96. pars.Add(new SugarParameter("@Status", input.Status.Trim()));
  97. }
  98. var total = await _db.Ado.GetIntAsync($"SELECT COUNT(1) FROM ({baseSql}) t {outerWhere}", pars);
  99. var list = await _db.Ado.SqlQueryAsync<ProcessOutsourceOrderListRow>(
  100. $"SELECT * FROM ({baseSql}) t {outerWhere} ORDER BY {BuildOrderBy(input.SortField, input.SortOrder)} LIMIT {pageSize} OFFSET {offset}",
  101. pars);
  102. return new { total, page, pageSize, list };
  103. }
  104. [DisplayName("工序外协订单详情")]
  105. [HttpGet("process-outsource-order/{id:int}")]
  106. public async Task<object> GetDetail(int id)
  107. {
  108. var tenantWhere = _userManager.TenantId > 0 ? " AND p.tenant_id=@TenantId" : string.Empty;
  109. var masterPars = _userManager.TenantId > 0
  110. ? new[] { new SugarParameter("@Id", id), new SugarParameter("@TenantId", _userManager.TenantId) }
  111. : new[] { new SugarParameter("@Id", id) };
  112. var master = (await _db.Ado.SqlQueryAsync<ProcessOutsourceOrderMasterRow>(
  113. $"""
  114. SELECT
  115. p.RecID AS Id,p.PurOrd AS PurOrd,p.OrdDate AS OrdDate,p.Supp AS Supp,p.ReqBy AS ReqBy,p.WorkOrd AS WorkOrd,
  116. p.Buyer AS Buyer,p.Department AS Department,p.`Usage` AS `Usage`,p.Curr AS Curr,p.Remark AS Remark,
  117. CASE WHEN IFNULL(LENGTH(p.Status),0)=0 OR p.Buyer IS NULL THEN 'R' ELSE p.Status END AS Status
  118. FROM PurOrdMaster p
  119. WHERE p.RecID=@Id{tenantWhere}
  120. LIMIT 1
  121. """,
  122. masterPars)).FirstOrDefault();
  123. if (master == null) throw Oops.Oh("记录不存在");
  124. var details = await _db.Ado.SqlQueryAsync<ProcessOutsourceOrderDetailRow>(
  125. """
  126. SELECT
  127. d.RecID AS Id,
  128. d.Line AS Line,
  129. d.ItemNum AS ItemNum,
  130. IFNULL(i.Descr,'') AS ItemName,
  131. d.Op AS Op,
  132. d.UM AS UM,
  133. d.Location AS Location,
  134. d.QtyOrded AS QtyOrded,
  135. d.QtyReceived AS QtyReceived,
  136. d.ReceiptQty AS ReceiptQty,
  137. d.DueDate AS DueDate,
  138. d.LotSerial AS LotSerial,
  139. d.Potype AS Potype,
  140. d.PurOrd AS PurOrd,
  141. d.PurOrdRecID AS PurOrdRecID
  142. FROM PurOrdDetail d
  143. LEFT JOIN ItemMaster i ON d.ItemNum=i.ItemNum
  144. WHERE d.PurOrdRecID=@PurOrdRecID
  145. ORDER BY d.Line,d.RecID
  146. """,
  147. new SugarParameter("@PurOrdRecID", id));
  148. return new { master, details };
  149. }
  150. [DisplayName("按工单生成工序外协订单")]
  151. [HttpPost("process-outsource-order/create-by-workord")]
  152. public async Task<object> CreateByWorkOrd([FromBody] ProcessOutsourceOrderCreateInput input)
  153. {
  154. if (string.IsNullOrWhiteSpace(input.WorkOrd)) throw Oops.Oh("请选择工单");
  155. var workOrd = input.WorkOrd.Trim();
  156. // 先筛出“当前工单且未生成采购单”的外协供应商清单(等价于存储过程内 @v_temp_supp)。
  157. var suppliers = await _db.Ado.SqlQueryAsync<SuppRow>(
  158. """
  159. SELECT DISTINCT d.SupplierCode AS Supp
  160. FROM WorkOrdMaster m
  161. LEFT JOIN WorkOrdRouting r ON m.WorkOrd=r.WorkOrd
  162. LEFT JOIN RoutingOpDetail d ON r.Op=d.Op AND r.ItemNum=d.RoutingCode
  163. LEFT JOIN PurOrdMaster p ON m.WorkOrd=p.WorkOrd AND d.SupplierCode=p.Supp
  164. WHERE r.ProcessOut=1
  165. AND m.WorkOrd=@WorkOrd
  166. AND p.RecID IS NULL
  167. AND IFNULL(TRIM(d.SupplierCode),'')<>''
  168. ORDER BY d.SupplierCode
  169. """,
  170. new SugarParameter("@WorkOrd", workOrd));
  171. if (suppliers.Count == 0)
  172. throw Oops.Oh("当前工单无需生成外协采购订单。");
  173. var domain = await _db.Ado.GetStringAsync(
  174. """
  175. SELECT IFNULL(TRIM(Domain),'')
  176. FROM WorkOrdMaster
  177. WHERE WorkOrd=@WorkOrd
  178. LIMIT 1
  179. """,
  180. new SugarParameter("@WorkOrd", workOrd)) ?? string.Empty;
  181. var created = new List<IdPurOrdRow>();
  182. try
  183. {
  184. _db.Ado.BeginTran();
  185. var purOrdNumbers = await PurOrdNumberGenerator.NextBatchAsync(
  186. _db, "PW", DateTime.Today, PwPurOrdSerialWidth, suppliers.Count);
  187. for (var i = 0; i < suppliers.Count; i++)
  188. {
  189. var supp = suppliers[i].Supp!.Trim();
  190. var purOrd = purOrdNumbers[i].Trim();
  191. var now = DateTime.Now;
  192. await _db.Ado.ExecuteCommandAsync(
  193. """
  194. INSERT INTO PurOrdMaster
  195. (
  196. Confirming, CreditTermsInt, Disc, ExchRate, EstVal, ExchRate1, ExchRate2,
  197. FixedPrice, FixedRate, Frt, PartialOK, AmtPrepaid, PrintPO, PST, Recurr,
  198. `Release`, Revision, Scheduled, ServiceCharge, SpecialCharge, Taxable,
  199. Tax1, Tax2, Tax3, TransportDays, IsActive, IsConfirm, Potype, IsChanged,
  200. TaxIn, Amt, IsPriceChanged,
  201. Buyer, Domain, PurOrd, OrdDate, ReqBy, Status, Supp, CreateUser, CreateTime,
  202. Department, `Usage`, FSTID, ERPWorkOrd, WorkOrd, tenant_id
  203. )
  204. SELECT
  205. 0, 0, 0, 1, 0, 1, 1,
  206. 0, 0, 0, 1, 0, 0, 0, 0,
  207. 0, 0, 0, 0, 0, 1,
  208. 0, 0, 0, 0, 1, 1, 'PW', 0,
  209. 1, 0, 0,
  210. @Buyer,
  211. IFNULL(TRIM(m.Domain),''),
  212. @PurOrd,
  213. @Now,
  214. 'PO',
  215. '',
  216. @Supp,
  217. @CreateUser,
  218. @Now,
  219. m.Department,
  220. '外协',
  221. 1,
  222. m.Batch,
  223. m.WorkOrd,
  224. @TenantId
  225. FROM WorkOrdMaster m
  226. WHERE m.WorkOrd=@WorkOrd
  227. LIMIT 1
  228. """,
  229. new SugarParameter("@Buyer", _userManager.Account),
  230. new SugarParameter("@PurOrd", purOrd),
  231. new SugarParameter("@Now", now),
  232. new SugarParameter("@Supp", supp),
  233. new SugarParameter("@CreateUser", _userManager.Account),
  234. new SugarParameter("@WorkOrd", workOrd),
  235. new SugarParameter("@TenantId", _userManager.TenantId <= 0 ? null : _userManager.TenantId));
  236. var masterId = await _db.Ado.GetIntAsync(
  237. "SELECT IFNULL(MAX(RecID),0) FROM PurOrdMaster WHERE PurOrd=@PurOrd",
  238. new SugarParameter("@PurOrd", purOrd));
  239. if (masterId <= 0)
  240. throw Oops.Oh("采购订单生成失败。");
  241. var lines = await _db.Ado.SqlQueryAsync<CreateLineRow>(
  242. """
  243. SELECT
  244. IFNULL(i.PMBOM,'') AS PMBOM,
  245. IFNULL(r.Op,0) AS Op,
  246. r.ItemNum AS ItemNum,
  247. IFNULL(i.UM,'') AS UM,
  248. IFNULL(m.QtyOrded,0) AS QtyOrded,
  249. IFNULL(i.Rev,'') AS Rev,
  250. IFNULL(m.Department,'') AS Department,
  251. m.WorkOrd AS WorkOrd,
  252. IFNULL(i.Drawing,'') AS Drawing,
  253. IFNULL(m.RecID,0) AS WorkOrdID,
  254. IFNULL(d.OutsourcedLeadTime,0) AS ProcessOutDay
  255. FROM WorkOrdMaster m
  256. LEFT JOIN WorkOrdRouting r ON m.WorkOrd=r.WorkOrd
  257. LEFT JOIN RoutingOpDetail d ON r.Op=d.Op AND r.ItemNum=d.RoutingCode
  258. LEFT JOIN ItemMaster i ON r.ItemNum=i.ItemNum
  259. WHERE r.ProcessOut=1
  260. AND m.WorkOrd=@WorkOrd
  261. AND d.SupplierCode=@Supp
  262. ORDER BY r.RecID
  263. """,
  264. new SugarParameter("@WorkOrd", workOrd),
  265. new SugarParameter("@Supp", supp));
  266. for (var lineIndex = 0; lineIndex < lines.Count; lineIndex++)
  267. {
  268. var row = lines[lineIndex];
  269. await _db.Ado.ExecuteCommandAsync(
  270. """
  271. INSERT INTO PurOrdDetail
  272. (
  273. QtyBO, RctCost, CreditTermsInt, UpdateCurrentCost, CumReceived1, CumReceived2,
  274. CumReceived3, CumReceived4, Disc, FixedPrice, InspectReq, SingleLot, SupplyPer,
  275. PurOrd, PST, PackingSlipQty, PayUMConv, PurCost, RctQty, QtyOrded, QtyReceived,
  276. QtyReturned, Active, QtyReleased, RctUMConversion, Scheduled, ScheduledChanged,
  277. SchedMRPReq, SafetyDays, SafetyHours, StdCost, Taxable, TaxIn, MaxTaxableAmt,
  278. TransportHours, UMConversion, VAT, IsActive, IsConfirm, Potype, IsChanged,
  279. TaxRate, IsRounding, ReceiptQty, BarCodeQty, IsClosed, QtyReturnedRefund, CumQtyBO,
  280. ActiveRlseID1, Domain, DueDate, Line, Location, Op, ItemNum, Status, UM, WorkOrdID,
  281. CreateUser, CreateTime, `Usage`, Rev, Department, WorkOrd, Drawing, PurOrdRecID, tenant_id
  282. )
  283. VALUES
  284. (
  285. 0, 0, 0, 0, 0, 0,
  286. 0, 0, 0, 0, 0, 0, 0,
  287. @PurOrd, 0, 0, 1, 0, 0, @QtyOrded, 0,
  288. 0, 1, 0, 1, 0, 0,
  289. 0, 0, 0, 0, 1, 1, 0,
  290. 0, 1, 0, 1, 1, 'PW', 0,
  291. 0, 0, 0, 0, 0, 0, 0,
  292. @ActiveRlseID1, @Domain, DATE_ADD(@Now, INTERVAL @ProcessOutDay DAY), @Line, '5008', @Op, @ItemNum, '', @UM, @WorkOrdID,
  293. @CreateUser, @Now, '外协', @Rev, @Department, @WorkOrd, @Drawing, @PurOrdRecID, @TenantId
  294. )
  295. """,
  296. new SugarParameter("@ActiveRlseID1", row.PMBOM),
  297. new SugarParameter("@Domain", domain),
  298. new SugarParameter("@Now", now),
  299. new SugarParameter("@ProcessOutDay", row.ProcessOutDay),
  300. new SugarParameter("@Line", lineIndex + 1),
  301. new SugarParameter("@PurOrd", purOrd),
  302. new SugarParameter("@Op", row.Op),
  303. new SugarParameter("@ItemNum", row.ItemNum),
  304. new SugarParameter("@QtyOrded", row.QtyOrded),
  305. new SugarParameter("@UM", row.UM),
  306. new SugarParameter("@WorkOrdID", row.WorkOrdID),
  307. new SugarParameter("@CreateUser", _userManager.Account),
  308. new SugarParameter("@Rev", row.Rev),
  309. new SugarParameter("@Department", row.Department),
  310. new SugarParameter("@WorkOrd", row.WorkOrd),
  311. new SugarParameter("@Drawing", row.Drawing),
  312. new SugarParameter("@PurOrdRecID", masterId),
  313. new SugarParameter("@TenantId", _userManager.TenantId <= 0 ? null : _userManager.TenantId));
  314. }
  315. await UpsertProcessOutsourceMdpAsync(purOrd, now);
  316. created.Add(new IdPurOrdRow { Id = masterId, PurOrd = purOrd });
  317. }
  318. _db.Ado.CommitTran();
  319. }
  320. catch
  321. {
  322. _db.Ado.RollbackTran();
  323. throw Oops.Oh("采购订单生成失败。");
  324. }
  325. var latest = created.OrderByDescending(x => x.Id).FirstOrDefault();
  326. if (latest == null) throw Oops.Oh("采购订单生成失败。");
  327. return new { id = latest.Id, purOrd = latest.PurOrd, message = "采购订单生成成功。" };
  328. }
  329. private async Task UpsertProcessOutsourceMdpAsync(string purOrd, DateTime now)
  330. {
  331. var batchId = $"S3_OUTSOURCE_{now:yyyyMMddHHmmss}";
  332. await _db.Ado.ExecuteCommandAsync(
  333. """
  334. INSERT INTO mdp_std_purchase_order
  335. (tenant_id, source_system, po_no, po_line, po_type, supplier_code, item_code, item_name, order_qty, received_qty, returned_qty, due_date, need_date, order_date, status, buyer, work_order, source_biz_key, sync_batch_id, sync_time)
  336. SELECT
  337. COALESCE(m.tenant_id, 0),
  338. 'AIDOP',
  339. IFNULL(m.PurOrd, ''),
  340. CAST(IFNULL(d.Line, 0) AS CHAR),
  341. IFNULL(m.Potype, ''),
  342. IFNULL(m.Supp, ''),
  343. IFNULL(d.ItemNum, ''),
  344. IFNULL(i.Descr, ''),
  345. IFNULL(d.QtyOrded, 0),
  346. IFNULL(d.QtyReceived, IFNULL(d.RctQty, 0)),
  347. IFNULL(d.QtyReturned, 0),
  348. d.DueDate,
  349. d.NeedDate,
  350. m.OrdDate,
  351. CASE WHEN IFNULL(LENGTH(m.Status), 0) = 0 THEN 'R' ELSE m.Status END,
  352. m.Buyer,
  353. m.WorkOrd,
  354. CONCAT(IFNULL(m.PurOrd,''), '|', IFNULL(d.Line, 0)),
  355. @BatchId,
  356. @Now
  357. FROM PurOrdMaster m
  358. JOIN PurOrdDetail d ON m.RecID = d.PurOrdRecID
  359. LEFT JOIN ItemMaster i ON d.ItemNum = i.ItemNum
  360. WHERE m.PurOrd = @PurOrd
  361. AND IFNULL(d.ItemNum, '') <> ''
  362. ON DUPLICATE KEY UPDATE
  363. po_type=VALUES(po_type),
  364. supplier_code=VALUES(supplier_code),
  365. item_code=VALUES(item_code),
  366. item_name=VALUES(item_name),
  367. order_qty=VALUES(order_qty),
  368. received_qty=VALUES(received_qty),
  369. returned_qty=VALUES(returned_qty),
  370. due_date=VALUES(due_date),
  371. need_date=VALUES(need_date),
  372. order_date=VALUES(order_date),
  373. status=VALUES(status),
  374. buyer=VALUES(buyer),
  375. work_order=VALUES(work_order),
  376. sync_batch_id=VALUES(sync_batch_id),
  377. sync_time=VALUES(sync_time),
  378. update_time=CURRENT_TIMESTAMP
  379. """,
  380. new SugarParameter("@PurOrd", purOrd),
  381. new SugarParameter("@BatchId", batchId),
  382. new SugarParameter("@Now", now));
  383. await _db.Ado.ExecuteCommandAsync(
  384. """
  385. INSERT INTO mdp_std_process_outsource_order
  386. (tenant_id, source_system, work_order, op_code, routing_code, supplier_code, po_no, po_line, order_qty, completed_qty, due_date, status, source_biz_key, sync_batch_id, sync_time)
  387. SELECT
  388. COALESCE(m.tenant_id, 0),
  389. 'AIDOP',
  390. IFNULL(m.WorkOrd, ''),
  391. CAST(IFNULL(d.Op, 0) AS CHAR),
  392. IFNULL(d.ItemNum, ''),
  393. IFNULL(m.Supp, ''),
  394. IFNULL(m.PurOrd, ''),
  395. CAST(IFNULL(d.Line, 0) AS CHAR),
  396. IFNULL(d.QtyOrded, 0),
  397. IFNULL(d.QtyReceived, IFNULL(d.RctQty, 0)),
  398. d.DueDate,
  399. CASE WHEN IFNULL(LENGTH(m.Status), 0) = 0 THEN 'R' ELSE m.Status END,
  400. CONCAT(IFNULL(m.PurOrd,''), '|', IFNULL(d.Line, 0)),
  401. @BatchId,
  402. @Now
  403. FROM PurOrdMaster m
  404. JOIN PurOrdDetail d ON m.RecID = d.PurOrdRecID
  405. WHERE m.PurOrd = @PurOrd
  406. AND m.Potype = 'PW'
  407. AND IFNULL(m.WorkOrd, '') <> ''
  408. AND IFNULL(d.ItemNum, '') <> ''
  409. ON DUPLICATE KEY UPDATE
  410. supplier_code=VALUES(supplier_code),
  411. po_no=VALUES(po_no),
  412. po_line=VALUES(po_line),
  413. order_qty=VALUES(order_qty),
  414. completed_qty=VALUES(completed_qty),
  415. due_date=VALUES(due_date),
  416. status=VALUES(status),
  417. sync_batch_id=VALUES(sync_batch_id),
  418. sync_time=VALUES(sync_time),
  419. update_time=CURRENT_TIMESTAMP
  420. """,
  421. new SugarParameter("@PurOrd", purOrd),
  422. new SugarParameter("@BatchId", batchId),
  423. new SugarParameter("@Now", now));
  424. await _db.Ado.ExecuteCommandAsync(
  425. """
  426. INSERT INTO dwd_supplier_delivery
  427. (tenant_id, stat_date, po_no, po_line, po_type, supplier_code, supplier_name, item_code, item_name, order_qty, schedule_qty, delivery_qty, receipt_qty, return_qty, remaining_qty, due_date, need_date, delivery_status, source_system, sync_batch_id, calc_time)
  428. SELECT
  429. COALESCE(m.tenant_id, 0),
  430. @StatDate,
  431. IFNULL(m.PurOrd, ''),
  432. CAST(IFNULL(d.Line, 0) AS CHAR),
  433. IFNULL(m.Potype, ''),
  434. IFNULL(m.Supp, ''),
  435. IFNULL(s.SortName, ''),
  436. IFNULL(d.ItemNum, ''),
  437. IFNULL(i.Descr, ''),
  438. IFNULL(d.QtyOrded, 0),
  439. 0,
  440. 0,
  441. IFNULL(d.QtyReceived, IFNULL(d.RctQty, 0)),
  442. IFNULL(d.QtyReturned, 0),
  443. GREATEST(IFNULL(d.QtyOrded, 0) - IFNULL(d.QtyReceived, IFNULL(d.RctQty, 0)) - IFNULL(d.QtyReturned, 0), 0),
  444. d.DueDate,
  445. d.NeedDate,
  446. 'OPEN',
  447. 'AIDOP',
  448. @BatchId,
  449. @Now
  450. FROM PurOrdMaster m
  451. JOIN PurOrdDetail d ON m.RecID = d.PurOrdRecID
  452. LEFT JOIN ItemMaster i ON d.ItemNum = i.ItemNum
  453. LEFT JOIN SuppMaster s ON m.Domain = s.Domain AND m.Supp = s.Supp
  454. WHERE m.PurOrd = @PurOrd
  455. AND IFNULL(d.ItemNum, '') <> ''
  456. ON DUPLICATE KEY UPDATE
  457. po_type=VALUES(po_type),
  458. supplier_code=VALUES(supplier_code),
  459. supplier_name=VALUES(supplier_name),
  460. item_code=VALUES(item_code),
  461. item_name=VALUES(item_name),
  462. order_qty=VALUES(order_qty),
  463. receipt_qty=VALUES(receipt_qty),
  464. return_qty=VALUES(return_qty),
  465. remaining_qty=VALUES(remaining_qty),
  466. due_date=VALUES(due_date),
  467. need_date=VALUES(need_date),
  468. delivery_status=VALUES(delivery_status),
  469. sync_batch_id=VALUES(sync_batch_id),
  470. calc_time=VALUES(calc_time),
  471. update_time=CURRENT_TIMESTAMP
  472. """,
  473. new SugarParameter("@PurOrd", purOrd),
  474. new SugarParameter("@StatDate", now.Date),
  475. new SugarParameter("@BatchId", batchId),
  476. new SugarParameter("@Now", now));
  477. }
  478. [DisplayName("保存工序外协订单")]
  479. [HttpPost("process-outsource-order/save")]
  480. public async Task<object> Save([FromBody] ProcessOutsourceOrderSaveInput input)
  481. {
  482. if (input.Id <= 0) throw Oops.Oh("缺少主键");
  483. if (string.IsNullOrWhiteSpace(input.PurOrd)) throw Oops.Oh("采购单号不能为空");
  484. if (string.IsNullOrWhiteSpace(input.Supp)) throw Oops.Oh("供应商不能为空");
  485. if (string.IsNullOrWhiteSpace(input.Department)) throw Oops.Oh("部门不能为空");
  486. var exists = await _db.Ado.GetIntAsync("SELECT COUNT(1) FROM PurOrdMaster WHERE RecID=@Id" + (_userManager.TenantId > 0 ? " AND tenant_id=@TenantId" : ""),
  487. _userManager.TenantId > 0
  488. ? new[] { new SugarParameter("@Id", input.Id), new SugarParameter("@TenantId", _userManager.TenantId) }
  489. : new[] { new SugarParameter("@Id", input.Id) });
  490. if (exists <= 0) throw Oops.Oh("记录不存在");
  491. try
  492. {
  493. _db.Ado.BeginTran();
  494. await _db.Ado.ExecuteCommandAsync(
  495. """
  496. UPDATE PurOrdMaster
  497. SET OrdDate=@OrdDate,Supp=@Supp,ReqBy=@ReqBy,WorkOrd=@WorkOrd,Buyer=@Buyer,Department=@Department,`Usage`=@Usage,Curr=@Curr,Remark=@Remark,UpdateUser=@UpdateUser,UpdateTime=@UpdateTime
  498. WHERE RecID=@Id
  499. """,
  500. new SugarParameter("@Id", input.Id),
  501. new SugarParameter("@OrdDate", ParseDate(input.OrdDate)),
  502. new SugarParameter("@Supp", input.Supp!.Trim()),
  503. new SugarParameter("@ReqBy", string.IsNullOrWhiteSpace(input.ReqBy) ? "PO" : input.ReqBy.Trim()),
  504. new SugarParameter("@WorkOrd", input.WorkOrd?.Trim()),
  505. new SugarParameter("@Buyer", string.IsNullOrWhiteSpace(input.Buyer) ? "110" : input.Buyer.Trim()),
  506. new SugarParameter("@Department", input.Department!.Trim()),
  507. new SugarParameter("@Usage", string.IsNullOrWhiteSpace(input.Usage) ? "外协" : input.Usage.Trim()),
  508. new SugarParameter("@Curr", input.Curr?.Trim()),
  509. new SugarParameter("@Remark", input.Remark?.Trim()),
  510. new SugarParameter("@UpdateUser", _userManager.Account),
  511. new SugarParameter("@UpdateTime", DateTime.Now)
  512. );
  513. await SaveDetailsAsync(input.Id, input.PurOrd!.Trim(), input.WorkOrd, input.Details);
  514. _db.Ado.CommitTran();
  515. }
  516. catch
  517. {
  518. _db.Ado.RollbackTran();
  519. throw;
  520. }
  521. return new { id = input.Id, message = "保存成功" };
  522. }
  523. /// <summary>
  524. /// 明细三路合并:
  525. /// ① DB有且入参有(按 Id 匹配)→ 更新
  526. /// ② DB无但入参有 → 新增
  527. /// ③ DB有但入参无 → 删除
  528. /// </summary>
  529. private async Task SaveDetailsAsync(int masterId, string purOrd, string? workOrd, List<ProcessOutsourceOrderDetailInput> details)
  530. {
  531. var dbDetails = await _db.Ado.SqlQueryAsync<DbDetailRow>(
  532. "SELECT RecID AS Id FROM PurOrdDetail WHERE PurOrdRecID=@PurOrdRecID",
  533. new SugarParameter("@PurOrdRecID", masterId));
  534. var dbById = dbDetails.ToDictionary(x => x.Id);
  535. var inputIds = new HashSet<int>(details.Where(x => x.Id.HasValue && x.Id.Value > 0).Select(x => x.Id!.Value));
  536. var maxLine = await _db.Ado.GetIntAsync("SELECT IFNULL(MAX(Line),0) FROM PurOrdDetail WHERE PurOrdRecID=@PurOrdRecID", new SugarParameter("@PurOrdRecID", masterId));
  537. var lineSeed = maxLine;
  538. for (var i = 0; i < details.Count; i++)
  539. {
  540. var item = details[i];
  541. if (string.IsNullOrWhiteSpace(item.ItemNum)) continue;
  542. var line = item.Line ?? (i + 1);
  543. if (line <= 0)
  544. {
  545. lineSeed++;
  546. line = lineSeed;
  547. }
  548. var itemInfo = (await _db.Ado.SqlQueryAsync<ItemLookupRow>(
  549. """
  550. SELECT ItemNum,Descr,UM,Location
  551. FROM ItemMaster
  552. WHERE ItemNum=@ItemNum
  553. LIMIT 1
  554. """,
  555. new SugarParameter("@ItemNum", item.ItemNum.Trim()))).FirstOrDefault();
  556. if (item.Id.HasValue && item.Id.Value > 0 && dbById.ContainsKey(item.Id.Value))
  557. {
  558. await _db.Ado.ExecuteCommandAsync(
  559. """
  560. UPDATE PurOrdDetail
  561. SET Line=@Line,ItemNum=@ItemNum,Descr=@Descr,Op=@Op,UM=@UM,Location=@Location,QtyOrded=@QtyOrded,QtyReceived=@QtyReceived,ReceiptQty=@ReceiptQty,DueDate=@DueDate,LotSerial=@LotSerial,Potype='PW',PurOrd=@PurOrd,PurOrdRecID=@PurOrdRecID,WorkOrd=@WorkOrd,UpdateUser=@UpdateUser,UpdateTime=@UpdateTime
  562. WHERE RecID=@Id
  563. """,
  564. new SugarParameter("@Id", item.Id.Value),
  565. new SugarParameter("@Line", line),
  566. new SugarParameter("@ItemNum", item.ItemNum.Trim()),
  567. new SugarParameter("@Descr", itemInfo?.Descr),
  568. new SugarParameter("@Op", item.Op ?? 0),
  569. new SugarParameter("@UM", string.IsNullOrWhiteSpace(item.UM) ? itemInfo?.UM : item.UM!.Trim()),
  570. new SugarParameter("@Location", string.IsNullOrWhiteSpace(item.Location) ? itemInfo?.Location : item.Location!.Trim()),
  571. new SugarParameter("@QtyOrded", item.QtyOrded ?? 0),
  572. new SugarParameter("@QtyReceived", item.QtyReceived ?? 0),
  573. new SugarParameter("@ReceiptQty", item.ReceiptQty ?? 0),
  574. new SugarParameter("@DueDate", ParseDate(item.DueDate)),
  575. new SugarParameter("@LotSerial", item.LotSerial?.Trim()),
  576. new SugarParameter("@PurOrd", purOrd),
  577. new SugarParameter("@PurOrdRecID", masterId),
  578. new SugarParameter("@WorkOrd", workOrd?.Trim()),
  579. new SugarParameter("@UpdateUser", _userManager.Account),
  580. new SugarParameter("@UpdateTime", DateTime.Now)
  581. );
  582. }
  583. else
  584. {
  585. await _db.Ado.ExecuteCommandAsync(
  586. """
  587. INSERT INTO PurOrdDetail
  588. (PurOrd,Line,ItemNum,Descr,Op,UM,Location,QtyOrded,QtyReceived,ReceiptQty,DueDate,LotSerial,Potype,PurOrdRecID,WorkOrd,Status,CreateUser,CreateTime,UpdateUser,UpdateTime,tenant_id)
  589. VALUES
  590. (@PurOrd,@Line,@ItemNum,@Descr,@Op,@UM,@Location,@QtyOrded,@QtyReceived,@ReceiptQty,@DueDate,@LotSerial,'PW',@PurOrdRecID,@WorkOrd,'R',@CreateUser,@CreateTime,@UpdateUser,@UpdateTime,@TenantId)
  591. """,
  592. new SugarParameter("@PurOrd", purOrd),
  593. new SugarParameter("@Line", line),
  594. new SugarParameter("@ItemNum", item.ItemNum.Trim()),
  595. new SugarParameter("@Descr", itemInfo?.Descr),
  596. new SugarParameter("@Op", item.Op ?? 0),
  597. new SugarParameter("@UM", string.IsNullOrWhiteSpace(item.UM) ? itemInfo?.UM : item.UM!.Trim()),
  598. new SugarParameter("@Location", string.IsNullOrWhiteSpace(item.Location) ? itemInfo?.Location : item.Location!.Trim()),
  599. new SugarParameter("@QtyOrded", item.QtyOrded ?? 0),
  600. new SugarParameter("@QtyReceived", item.QtyReceived ?? 0),
  601. new SugarParameter("@ReceiptQty", item.ReceiptQty ?? 0),
  602. new SugarParameter("@DueDate", ParseDate(item.DueDate)),
  603. new SugarParameter("@LotSerial", item.LotSerial?.Trim()),
  604. new SugarParameter("@PurOrdRecID", masterId),
  605. new SugarParameter("@WorkOrd", workOrd?.Trim()),
  606. new SugarParameter("@CreateUser", _userManager.Account),
  607. new SugarParameter("@CreateTime", DateTime.Now),
  608. new SugarParameter("@UpdateUser", _userManager.Account),
  609. new SugarParameter("@UpdateTime", DateTime.Now),
  610. new SugarParameter("@TenantId", _userManager.TenantId <= 0 ? null : _userManager.TenantId)
  611. );
  612. }
  613. }
  614. foreach (var toDelete in dbDetails.Where(x => !inputIds.Contains(x.Id)))
  615. {
  616. await _db.Ado.ExecuteCommandAsync("DELETE FROM PurOrdDetail WHERE RecID=@Id", new SugarParameter("@Id", toDelete.Id));
  617. }
  618. }
  619. [DisplayName("删除工序外协订单")]
  620. [HttpPost("process-outsource-order/delete/{id:int}")]
  621. public async Task<object> Delete(int id, [FromQuery] string purOrd)
  622. {
  623. if (string.IsNullOrWhiteSpace(purOrd)) throw Oops.Oh("缺少采购单号");
  624. var ordNo = purOrd.Trim();
  625. var hasReceipt = await _db.Ado.GetIntAsync(
  626. "SELECT COUNT(1) FROM PurOrdRctDetail WHERE OrdNbr=@PurOrd AND IFNULL(QtyReceived,0)>0" + (_userManager.TenantId > 0 ? " AND tenant_id=@TenantId" : ""),
  627. _userManager.TenantId > 0
  628. ? new[] { new SugarParameter("@PurOrd", ordNo), new SugarParameter("@TenantId", _userManager.TenantId) }
  629. : new[] { new SugarParameter("@PurOrd", ordNo) });
  630. if (hasReceipt > 0) throw Oops.Oh("存在收货数量,不允许删除");
  631. var hasShip = await _db.Ado.GetIntAsync(
  632. "SELECT COUNT(1) FROM scm_shdzb WHERE po_bill=@PurOrd" + (_userManager.TenantId > 0 ? " AND tenant_id=@TenantId" : ""),
  633. _userManager.TenantId > 0
  634. ? new[] { new SugarParameter("@PurOrd", ordNo), new SugarParameter("@TenantId", _userManager.TenantId) }
  635. : new[] { new SugarParameter("@PurOrd", ordNo) });
  636. if (hasShip > 0) throw Oops.Oh("存在发货单,不允许删除");
  637. try
  638. {
  639. _db.Ado.BeginTran();
  640. await _db.Ado.ExecuteCommandAsync("DELETE FROM PurOrdDetail WHERE PurOrd=@PurOrd", new SugarParameter("@PurOrd", ordNo));
  641. await _db.Ado.ExecuteCommandAsync("DELETE FROM PurOrdMaster WHERE RecID=@Id AND PurOrd=@PurOrd", new SugarParameter("@Id", id), new SugarParameter("@PurOrd", ordNo));
  642. _db.Ado.CommitTran();
  643. }
  644. catch
  645. {
  646. _db.Ado.RollbackTran();
  647. throw;
  648. }
  649. return new { message = "删除成功" };
  650. }
  651. [DisplayName("采购组下拉")]
  652. [HttpGet("process-outsource-order/options/buyers")]
  653. public async Task<object> GetBuyerOptions()
  654. {
  655. var list = await _db.Ado.SqlQueryAsync<OptionRow>(
  656. """
  657. SELECT Employee AS Value, CONCAT(TRIM(Employee),' ',TRIM(IFNULL(Name,''))) AS Label
  658. FROM EmployeeMaster
  659. ORDER BY Employee
  660. """);
  661. return new { list };
  662. }
  663. [DisplayName("供应商下拉")]
  664. [HttpGet("process-outsource-order/options/suppliers")]
  665. public async Task<object> GetSupplierOptions()
  666. {
  667. var list = await _db.Ado.SqlQueryAsync<OptionRow>(
  668. """
  669. SELECT Supp AS Value, CONCAT(TRIM(Supp),' ',TRIM(IFNULL(SortName,''))) AS Label
  670. FROM SuppMaster
  671. ORDER BY Supp
  672. """);
  673. return new { list };
  674. }
  675. [DisplayName("部门下拉")]
  676. [HttpGet("process-outsource-order/options/departments")]
  677. public async Task<object> GetDepartmentOptions()
  678. {
  679. var list = await _db.Ado.SqlQueryAsync<OptionRow>(
  680. """
  681. SELECT Department AS Value, CONCAT(TRIM(Department),' ',TRIM(IFNULL(Descr,''))) AS Label
  682. FROM DepartmentMaster
  683. ORDER BY Department
  684. """);
  685. return new { list };
  686. }
  687. [DisplayName("币别下拉")]
  688. [HttpGet("process-outsource-order/options/currencies")]
  689. public async Task<object> GetCurrencyOptions()
  690. {
  691. var list = await _db.Ado.SqlQueryAsync<OptionRow>(
  692. """
  693. SELECT Val AS Value, CONCAT(TRIM(Val),' ',TRIM(IFNULL(Comments,''))) AS Label
  694. FROM GeneralizedCodeMaster
  695. WHERE FldName='Curr'
  696. ORDER BY Val
  697. """);
  698. return new { list };
  699. }
  700. [DisplayName("库位下拉")]
  701. [HttpGet("process-outsource-order/options/locations")]
  702. public async Task<object> GetLocationOptions()
  703. {
  704. var list = await _db.Ado.SqlQueryAsync<OptionRow>(
  705. """
  706. SELECT Location AS Value, CONCAT(TRIM(Location),' ',TRIM(IFNULL(Descr,''))) AS Label
  707. FROM LocationMaster
  708. WHERE IFNULL(Typed,'')<>'Supp'
  709. ORDER BY Location
  710. """);
  711. return new { list };
  712. }
  713. [DisplayName("工序下拉")]
  714. [HttpGet("process-outsource-order/options/ops")]
  715. public async Task<object> GetOpOptions([FromQuery] int purOrdRecId)
  716. {
  717. if (purOrdRecId <= 0) return new { list = new List<OptionRow>() };
  718. var list = await _db.Ado.SqlQueryAsync<OptionRow>(
  719. """
  720. SELECT DISTINCT CAST(Op AS CHAR(50)) AS Value, CONCAT(TRIM(CAST(Op AS CHAR(50))),' ',TRIM(IFNULL(Descr,''))) AS Label
  721. FROM RoutingOpDetail
  722. WHERE UDeci5!=0
  723. AND RoutingCode IN (SELECT ItemNum FROM PurOrdDetail WHERE PurOrdRecID=@PurOrdRecID)
  724. ORDER BY Value
  725. """,
  726. new SugarParameter("@PurOrdRecID", purOrdRecId));
  727. return new { list };
  728. }
  729. private static DateTime? ParseDate(string? v) => DateTime.TryParse(v, out var d) ? d : null;
  730. private static string BuildOrderBy(string? sortField, string? sortOrder)
  731. {
  732. var dir = string.Equals(sortOrder, "asc", StringComparison.OrdinalIgnoreCase) ? "ASC" : "DESC";
  733. return sortField?.ToLowerInvariant() switch
  734. {
  735. "purord" => $"t.PurOrd {dir}",
  736. "workord" => $"t.WorkOrd {dir}",
  737. "suppname" => $"t.SuppName {dir}",
  738. "buyer" => $"t.Buyer {dir}",
  739. "itemnum" => $"t.ItemNum {dir}",
  740. "itemname" => $"t.ItemName {dir}",
  741. "op" => $"t.Op {dir}",
  742. "orddate" => $"t.OrdDate {dir}",
  743. "qtyorded" => $"t.QtyOrded {dir}",
  744. "cumqtybo" => $"t.CumQtyBO {dir}",
  745. "duedate" => $"t.DueDate {dir}",
  746. "fldate" => $"t.FlDate {dir}",
  747. "status" => $"t.Status {dir}",
  748. _ => "t.Id DESC"
  749. };
  750. }
  751. private sealed class ProcessOutsourceOrderListRow
  752. {
  753. public int Id { get; set; }
  754. public string? PurOrd { get; set; }
  755. public string? WorkOrd { get; set; }
  756. public string? ERPWorkOrd { get; set; }
  757. public string? SuppName { get; set; }
  758. public string? Buyer { get; set; }
  759. public string? BuyerCode { get; set; }
  760. public string? ItemNum { get; set; }
  761. public string? ItemName { get; set; }
  762. public string? Op { get; set; }
  763. public DateTime? OrdDate { get; set; }
  764. public decimal? QtyOrded { get; set; }
  765. public decimal? CumQtyBO { get; set; }
  766. public DateTime? DueDate { get; set; }
  767. public DateTime? FlDate { get; set; }
  768. public string? Status { get; set; }
  769. public string? Supp { get; set; }
  770. public string? ReqBy { get; set; }
  771. public string? Department { get; set; }
  772. public string? Usage { get; set; }
  773. public string? Curr { get; set; }
  774. public string? Remark { get; set; }
  775. public int HasReceived { get; set; }
  776. }
  777. private sealed class ProcessOutsourceOrderMasterRow
  778. {
  779. public int Id { get; set; }
  780. public string? PurOrd { get; set; }
  781. public DateTime? OrdDate { get; set; }
  782. public string? Supp { get; set; }
  783. public string? ReqBy { get; set; }
  784. public string? WorkOrd { get; set; }
  785. public string? Buyer { get; set; }
  786. public string? Department { get; set; }
  787. public string? Usage { get; set; }
  788. public string? Curr { get; set; }
  789. public string? Remark { get; set; }
  790. public string? Status { get; set; }
  791. }
  792. private sealed class ProcessOutsourceOrderDetailRow
  793. {
  794. public int Id { get; set; }
  795. public int? Line { get; set; }
  796. public string? ItemNum { get; set; }
  797. public string? ItemName { get; set; }
  798. public int? Op { get; set; }
  799. public string? UM { get; set; }
  800. public string? Location { get; set; }
  801. public decimal? QtyOrded { get; set; }
  802. public decimal? QtyReceived { get; set; }
  803. public decimal? ReceiptQty { get; set; }
  804. public DateTime? DueDate { get; set; }
  805. public string? LotSerial { get; set; }
  806. public string? Potype { get; set; }
  807. public string? PurOrd { get; set; }
  808. public int? PurOrdRecID { get; set; }
  809. }
  810. private sealed class OptionRow
  811. {
  812. public string? Value { get; set; }
  813. public string? Label { get; set; }
  814. }
  815. private sealed class IdPurOrdRow
  816. {
  817. public int Id { get; set; }
  818. public string? PurOrd { get; set; }
  819. }
  820. private sealed class SuppRow
  821. {
  822. public string? Supp { get; set; }
  823. }
  824. private sealed class CreateLineRow
  825. {
  826. public string? PMBOM { get; set; }
  827. public int Op { get; set; }
  828. public string? ItemNum { get; set; }
  829. public string? UM { get; set; }
  830. public decimal QtyOrded { get; set; }
  831. public string? Rev { get; set; }
  832. public string? Department { get; set; }
  833. public string? WorkOrd { get; set; }
  834. public string? Drawing { get; set; }
  835. public long WorkOrdID { get; set; }
  836. public int ProcessOutDay { get; set; }
  837. }
  838. private sealed class DbDetailRow
  839. {
  840. public int Id { get; set; }
  841. }
  842. private sealed class ItemLookupRow
  843. {
  844. public string? ItemNum { get; set; }
  845. public string? Descr { get; set; }
  846. public string? UM { get; set; }
  847. public string? Location { get; set; }
  848. }
  849. }