ProcessOutsourceOrderService.cs 39 KB

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