DeliveryScheduleService.cs 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711
  1. using Yitter.IdGenerator;
  2. namespace Admin.NET.Plugin.AiDOP.Supply;
  3. /// <summary>
  4. /// 物料交货计划服务
  5. /// </summary>
  6. [ApiDescriptionSettings(Order = 306, Description = "物料交货计划")]
  7. [Route("api/Supply")]
  8. [AllowAnonymous]
  9. [NonUnify]
  10. public class DeliveryScheduleService : IDynamicApiController, ITransient
  11. {
  12. private readonly ISqlSugarClient _db;
  13. private readonly SqlSugarRepository<PolistDeliverySchedule> _rep;
  14. private readonly UserManager _userManager;
  15. private readonly NumberRuleService _numberRuleService;
  16. public DeliveryScheduleService(
  17. ISqlSugarClient db,
  18. SqlSugarRepository<PolistDeliverySchedule> rep,
  19. UserManager userManager,
  20. NumberRuleService numberRuleService)
  21. {
  22. _db = db;
  23. _rep = rep;
  24. _userManager = userManager;
  25. _numberRuleService = numberRuleService;
  26. }
  27. [DisplayName("物料交货计划列表")]
  28. [HttpGet("delivery-schedule/list")]
  29. public async Task<object> GetList([FromQuery] DeliveryScheduleListInput input)
  30. {
  31. var page = input.Page <= 0 ? 1 : input.Page;
  32. var pageSize = input.PageSize <= 0 ? 10 : input.PageSize;
  33. var offset = (page - 1) * pageSize;
  34. if (await ShouldUseStdAsync())
  35. {
  36. return await GetStdListAsync(input, page, pageSize, offset);
  37. }
  38. var pars = new List<SugarParameter>();
  39. var where = new List<string> { "ss.isactive = 1" };
  40. if (_userManager.TenantId > 0)
  41. {
  42. where.Add("ss.tenant_id = @TenantId");
  43. pars.Add(new SugarParameter("@TenantId", _userManager.TenantId));
  44. }
  45. if (!string.IsNullOrWhiteSpace(input.PoNumber))
  46. {
  47. where.Add("ss.ponumber LIKE @PoNumber");
  48. pars.Add(new SugarParameter("@PoNumber", $"%{input.PoNumber.Trim()}%"));
  49. }
  50. if (!string.IsNullOrWhiteSpace(input.DsNum))
  51. {
  52. where.Add("ss.dsnum LIKE @DsNum");
  53. pars.Add(new SugarParameter("@DsNum", $"%{input.DsNum.Trim()}%"));
  54. }
  55. if (!string.IsNullOrWhiteSpace(input.ItemNum))
  56. {
  57. where.Add("ss.itemnum LIKE @ItemNum");
  58. pars.Add(new SugarParameter("@ItemNum", $"%{input.ItemNum.Trim()}%"));
  59. }
  60. if (!string.IsNullOrWhiteSpace(input.Supplier))
  61. {
  62. where.Add("(ss.suppliercode LIKE @Supplier OR ss.supplier LIKE @Supplier)");
  63. pars.Add(new SugarParameter("@Supplier", $"%{input.Supplier.Trim()}%"));
  64. }
  65. if (!string.IsNullOrWhiteSpace(input.Status))
  66. {
  67. where.Add("ss.status = @Status");
  68. pars.Add(new SugarParameter("@Status", input.Status.Trim()));
  69. }
  70. var orderBy = BuildListOrderBy(input.SortField, input.SortOrder);
  71. var fromSql = $"""
  72. FROM srm_polist_ds ss
  73. LEFT JOIN ItemMaster im ON ss.domain = im.Domain AND ss.itemnum = im.ItemNum
  74. WHERE {string.Join(" AND ", where)}
  75. """;
  76. var total = await _db.Ado.GetIntAsync($"SELECT COUNT(1) {fromSql}", pars);
  77. var list = await _db.Ado.SqlQueryAsync<DeliveryScheduleListRow>(
  78. $"""
  79. SELECT
  80. ss.Id AS Id,
  81. ss.domain AS Domain,
  82. ss.icdsid AS IcdsId,
  83. ss.dsnum AS DsNum,
  84. ss.status AS Status,
  85. ss.itemnum AS ItemNum,
  86. im.Descr AS Descr,
  87. ss.um AS Um,
  88. ss.purgroup AS PurGroup,
  89. ss.suppliercode AS SupplierCode,
  90. ss.supplier AS Supplier,
  91. ss.submitdate AS SubmitDate,
  92. ss.requestdate AS RequestDate,
  93. ss.needdate AS NeedDate,
  94. ss.ponumber AS PoNumber,
  95. ss.poline AS PoLine,
  96. ss.schedqty AS SchedQty,
  97. ss.lastsentdate AS LastSentDate,
  98. ss.lastsentqty AS LastSentQty,
  99. ss.sentqty AS SentQty,
  100. ss.restqty AS RestQty
  101. {fromSql}
  102. ORDER BY {orderBy}
  103. LIMIT {pageSize} OFFSET {offset}
  104. """,
  105. pars);
  106. return new { total, page, pageSize, list };
  107. }
  108. private async Task<bool> ShouldUseStdAsync()
  109. {
  110. return await _db.Ado.GetIntAsync("SELECT COUNT(1) FROM mdp_std_delivery_schedule LIMIT 1") > 0;
  111. }
  112. private async Task<object> GetStdListAsync(DeliveryScheduleListInput input, int page, int pageSize, int offset)
  113. {
  114. var pars = new List<SugarParameter>();
  115. var where = new List<string> { "1=1" };
  116. if (_userManager.TenantId > 0)
  117. {
  118. where.Add("ss.tenant_id = @TenantId");
  119. pars.Add(new SugarParameter("@TenantId", _userManager.TenantId));
  120. }
  121. if (!string.IsNullOrWhiteSpace(input.PoNumber))
  122. {
  123. where.Add("ss.po_no LIKE @PoNumber");
  124. pars.Add(new SugarParameter("@PoNumber", $"%{input.PoNumber.Trim()}%"));
  125. }
  126. if (!string.IsNullOrWhiteSpace(input.DsNum))
  127. {
  128. where.Add("ss.delivery_plan_no LIKE @DsNum");
  129. pars.Add(new SugarParameter("@DsNum", $"%{input.DsNum.Trim()}%"));
  130. }
  131. if (!string.IsNullOrWhiteSpace(input.ItemNum))
  132. {
  133. where.Add("ss.item_code LIKE @ItemNum");
  134. pars.Add(new SugarParameter("@ItemNum", $"%{input.ItemNum.Trim()}%"));
  135. }
  136. if (!string.IsNullOrWhiteSpace(input.Supplier))
  137. {
  138. where.Add("(ss.supplier_code LIKE @Supplier OR ss.supplier_name LIKE @Supplier)");
  139. pars.Add(new SugarParameter("@Supplier", $"%{input.Supplier.Trim()}%"));
  140. }
  141. if (!string.IsNullOrWhiteSpace(input.Status))
  142. {
  143. where.Add("ss.status = @Status");
  144. pars.Add(new SugarParameter("@Status", input.Status.Trim()));
  145. }
  146. var orderBy = BuildStdListOrderBy(input.SortField, input.SortOrder);
  147. var fromSql = $"FROM mdp_std_delivery_schedule ss LEFT JOIN mdp_std_item im ON ss.tenant_id=im.tenant_id AND ss.item_code=im.item_code WHERE {string.Join(" AND ", where)}";
  148. var total = await _db.Ado.GetIntAsync($"SELECT COUNT(1) {fromSql}", pars);
  149. var list = await _db.Ado.SqlQueryAsync<DeliveryScheduleListRow>(
  150. $"""
  151. SELECT
  152. ss.id AS Id,
  153. NULL AS Domain,
  154. NULL AS IcdsId,
  155. ss.delivery_plan_no AS DsNum,
  156. ss.status AS Status,
  157. ss.item_code AS ItemNum,
  158. im.item_name AS Descr,
  159. NULL AS Um,
  160. NULL AS PurGroup,
  161. ss.supplier_code AS SupplierCode,
  162. ss.supplier_name AS Supplier,
  163. ss.submit_date AS SubmitDate,
  164. ss.request_date AS RequestDate,
  165. ss.need_date AS NeedDate,
  166. ss.po_no AS PoNumber,
  167. CAST(ss.po_line AS SIGNED) AS PoLine,
  168. ss.schedule_qty AS SchedQty,
  169. ss.last_sent_date AS LastSentDate,
  170. ss.sent_qty AS LastSentQty,
  171. ss.sent_qty AS SentQty,
  172. ss.rest_qty AS RestQty
  173. {fromSql}
  174. ORDER BY {orderBy}
  175. LIMIT {pageSize} OFFSET {offset}
  176. """,
  177. pars);
  178. return new { total, page, pageSize, list, source = "mdp_std_delivery_schedule" };
  179. }
  180. private static string BuildStdListOrderBy(string? sortField, string? sortOrder)
  181. {
  182. var dir = string.Equals(sortOrder, "asc", StringComparison.OrdinalIgnoreCase) ? "ASC" : "DESC";
  183. return sortField?.ToLowerInvariant() switch
  184. {
  185. "dsnum" => $"ss.delivery_plan_no {dir}",
  186. "status" => $"ss.status {dir}",
  187. "itemnum" => $"ss.item_code {dir}",
  188. "suppliercode" => $"ss.supplier_code {dir}",
  189. "supplier" => $"ss.supplier_name {dir}",
  190. "submitdate" => $"ss.submit_date {dir}",
  191. "requestdate" => $"ss.request_date {dir}",
  192. "needdate" => $"ss.need_date {dir}",
  193. "ponumber" => $"ss.po_no {dir}",
  194. "poline" => $"ss.po_line {dir}",
  195. "schedqty" => $"ss.schedule_qty {dir}",
  196. "sentqty" => $"ss.sent_qty {dir}",
  197. "restqty" => $"ss.rest_qty {dir}",
  198. _ => "ss.need_date DESC, ss.delivery_plan_no DESC"
  199. };
  200. }
  201. [DisplayName("生成交货单")]
  202. [HttpPost("delivery-schedule/generate")]
  203. public async Task<object> Generate()
  204. {
  205. return await GenerateDeliverySchedulesAsync(string.Empty);
  206. }
  207. [DisplayName("批量添加交货单")]
  208. [HttpPost("delivery-schedule/batch-generate")]
  209. public async Task<object> BatchGenerate([FromBody] DeliveryScheduleGenerateInput input)
  210. {
  211. if (string.IsNullOrWhiteSpace(input.PoNumber))
  212. throw Oops.Oh("采购单号不能为空");
  213. return await GenerateDeliverySchedulesAsync(input.PoNumber.Trim());
  214. }
  215. [DisplayName("发布交货单")]
  216. [HttpPost("delivery-schedule/publish")]
  217. public async Task<object> Publish([FromBody] DeliveryScheduleBatchIdsInput input)
  218. {
  219. var ids = ParseIds(input.Ids);
  220. if (!ids.Any()) throw Oops.Oh("请勾选要发布的数据");
  221. var account = _userManager.Account ?? "system";
  222. var affected = await _db.Updateable<PolistDeliverySchedule>()
  223. .SetColumns(x => new PolistDeliverySchedule
  224. {
  225. Status = "P",
  226. SubmitDate = DateTime.Now,
  227. UpdateUser = account,
  228. UpdateTime = DateTime.Now
  229. })
  230. .Where(x => ids.Contains(x.Id) && x.IsActive == 1 && x.Status == "N")
  231. .ExecuteCommandAsync();
  232. return new { affected, message = affected > 0 ? "发布成功" : "无可发布数据" };
  233. }
  234. [DisplayName("取消发布交货单")]
  235. [HttpPost("delivery-schedule/unpublish/{id:long}")]
  236. public async Task<object> UnPublish(long id)
  237. {
  238. var row = await _rep.GetFirstAsync(x => x.Id == id && x.IsActive == 1) ?? throw Oops.Oh("交货单不存在");
  239. row.Status = "N";
  240. row.UpdateUser = _userManager.Account;
  241. row.UpdateTime = DateTime.Now;
  242. await _rep.UpdateAsync(row);
  243. return new { message = "取消发布成功" };
  244. }
  245. [DisplayName("作废交货单")]
  246. [HttpPost("delivery-schedule/cancel/{id:long}")]
  247. public async Task<object> Cancel(long id)
  248. {
  249. var row = await _rep.GetFirstAsync(x => x.Id == id && x.IsActive == 1) ?? throw Oops.Oh("交货单不存在");
  250. row.Status = "C";
  251. row.UpdateUser = _userManager.Account;
  252. row.UpdateTime = DateTime.Now;
  253. await _rep.UpdateAsync(row);
  254. return new { message = "作废成功" };
  255. }
  256. [DisplayName("选择采购单分页")]
  257. [HttpGet("delivery-schedule/purchase-order/page")]
  258. public async Task<object> GetPurchaseOrderPage([FromQuery] PurchaseOrderSelectInput input)
  259. {
  260. var page = input.Page <= 0 ? 1 : input.Page;
  261. var pageSize = input.PageSize <= 0 ? 10 : input.PageSize;
  262. var offset = (page - 1) * pageSize;
  263. var pars = new List<SugarParameter>();
  264. var where = new List<string>
  265. {
  266. "IFNULL(m.Status,'') <> 'C'",
  267. "IFNULL(d.Status,'') <> 'C'",
  268. "(IFNULL(ds.dsnum, '') = '' OR (ds.status = 'C' AND d.QtyOrded - d.RctQty > 0))"
  269. };
  270. if (!string.IsNullOrWhiteSpace(input.PurOrd))
  271. {
  272. where.Add("m.PurOrd LIKE @PurOrd");
  273. pars.Add(new SugarParameter("@PurOrd", $"%{input.PurOrd.Trim()}%"));
  274. }
  275. if (!string.IsNullOrWhiteSpace(input.ItemNum))
  276. {
  277. where.Add("d.ItemNum LIKE @ItemNum");
  278. pars.Add(new SugarParameter("@ItemNum", $"%{input.ItemNum.Trim()}%"));
  279. }
  280. if (!string.IsNullOrWhiteSpace(input.Supp))
  281. {
  282. where.Add("(m.Supp LIKE @Supp OR s.Name LIKE @Supp)");
  283. pars.Add(new SugarParameter("@Supp", $"%{input.Supp.Trim()}%"));
  284. }
  285. var orderBy = BuildPurchaseOrderOrderBy(input.SortField, input.SortOrder);
  286. var fromSql = $"""
  287. FROM PurOrdMaster m
  288. INNER JOIN PurOrdDetail d ON m.RecID = d.PurOrdRecID
  289. LEFT JOIN srm_polist_ds ds ON d.PurOrd = ds.ponumber AND d.Line = ds.poline AND ds.isactive = 1
  290. LEFT JOIN ConsigneeAddressMaster s ON m.Domain = s.Domain AND m.Supp = s.Address AND s.Typed = 'Supp'
  291. WHERE {string.Join(" AND ", where)}
  292. """;
  293. var distinctSql = $"SELECT DISTINCT m.PurOrd, d.Line, d.ItemNum {fromSql}";
  294. var total = await _db.Ado.GetIntAsync($"SELECT COUNT(1) FROM ({distinctSql}) t", pars);
  295. var list = await _db.Ado.SqlQueryAsync<PurchaseOrderSelectRow>(
  296. $"""
  297. SELECT DISTINCT
  298. m.PurOrd AS PurOrd,
  299. d.Line AS Line,
  300. d.ItemNum AS ItemNum,
  301. d.UM AS Um,
  302. m.Supp AS Supp,
  303. s.Name AS Name,
  304. d.DueDate AS DueDate,
  305. d.QtyOrded AS QtyOrded,
  306. m.Buyer AS Buyer
  307. {fromSql}
  308. ORDER BY {orderBy}
  309. LIMIT {pageSize} OFFSET {offset}
  310. """,
  311. pars);
  312. return new { total, page, pageSize, list };
  313. }
  314. private async Task<DeliveryScheduleGenerateResult> GenerateDeliverySchedulesAsync(string poNumber)
  315. {
  316. var account = _userManager.Account ?? "system";
  317. var pars = new List<SugarParameter>();
  318. var where = new List<string>
  319. {
  320. "IFNULL(m.Status,'') <> 'C'",
  321. "IFNULL(d.Status,'') <> 'C'",
  322. "(IFNULL(d.QtyOrded,0) - IFNULL(d.RctQty,0)) > 0",
  323. """
  324. NOT EXISTS (
  325. SELECT 1
  326. FROM srm_polist_ds ds
  327. WHERE ds.ponumber = d.PurOrd
  328. AND ds.poline = d.Line
  329. AND ds.isactive = 1
  330. AND IFNULL(ds.status,'') <> 'C'
  331. )
  332. """
  333. };
  334. if (_userManager.TenantId > 0)
  335. {
  336. where.Add("m.tenant_id = @TenantId");
  337. pars.Add(new SugarParameter("@TenantId", _userManager.TenantId));
  338. }
  339. if (!string.IsNullOrWhiteSpace(poNumber))
  340. {
  341. where.Add("m.PurOrd = @PoNumber");
  342. pars.Add(new SugarParameter("@PoNumber", poNumber.Trim()));
  343. }
  344. var candidates = await _db.Ado.SqlQueryAsync<DeliveryScheduleCandidateRow>(
  345. $"""
  346. SELECT
  347. IFNULL(TRIM(m.Domain),'') AS Domain,
  348. d.RecID AS IcdsId,
  349. IFNULL(TRIM(d.PurOrd),'') AS PoNumber,
  350. IFNULL(d.Line,0) AS PoLine,
  351. IFNULL(TRIM(d.ItemNum),'') AS ItemNum,
  352. IFNULL(TRIM(d.UM),'') AS Um,
  353. IFNULL(TRIM(m.Buyer),'') AS PurGroup,
  354. IFNULL(TRIM(m.Supp),'') AS SupplierCode,
  355. IFNULL(TRIM(s.Name),'') AS Supplier,
  356. d.DueDate AS NeedDate,
  357. (IFNULL(d.QtyOrded,0) - IFNULL(d.RctQty,0)) AS RestQty,
  358. m.tenant_id AS TenantId
  359. FROM PurOrdMaster m
  360. INNER JOIN PurOrdDetail d ON m.RecID = d.PurOrdRecID
  361. LEFT JOIN ConsigneeAddressMaster s ON m.Domain = s.Domain AND m.Supp = s.Address AND s.Typed = 'Supp'
  362. WHERE {string.Join(" AND ", where)}
  363. ORDER BY m.PurOrd, d.Line
  364. """,
  365. pars);
  366. if (candidates.Count == 0)
  367. {
  368. return new DeliveryScheduleGenerateResult
  369. {
  370. CandidateCount = 0,
  371. CreatedCount = 0,
  372. Message = string.IsNullOrWhiteSpace(poNumber)
  373. ? "无可生成交货单的采购行"
  374. : "当前采购单无可生成交货单的采购行"
  375. };
  376. }
  377. try
  378. {
  379. _db.Ado.BeginTran();
  380. var dsNumbersByKey = new Dictionary<string, string>();
  381. foreach (var group in candidates.GroupBy(x => x.Domain ?? string.Empty))
  382. {
  383. var groupRows = group.ToList();
  384. var dsNumbers = await _numberRuleService.NextBatchInCurrentTransactionAsync("M8", group.Key, groupRows.Count, account);
  385. if (dsNumbers.Count < groupRows.Count || dsNumbers.Any(string.IsNullOrWhiteSpace))
  386. throw Oops.Oh($"当前交货计划号生成失败,请检查交货计划编号规则维护。Domain={group.Key}");
  387. for (var index = 0; index < groupRows.Count; index++)
  388. {
  389. dsNumbersByKey[BuildCandidateKey(groupRows[index])] = dsNumbers[index].Trim();
  390. }
  391. }
  392. var now = DateTime.Now;
  393. var schedules = candidates.Select(row => new PolistDeliverySchedule
  394. {
  395. Id = YitIdHelper.NextId(),
  396. Domain = row.Domain ?? string.Empty,
  397. IcdsId = row.IcdsId,
  398. DsNum = dsNumbersByKey[BuildCandidateKey(row)],
  399. Status = "N",
  400. ItemNum = row.ItemNum ?? string.Empty,
  401. Um = row.Um ?? string.Empty,
  402. PurGroup = row.PurGroup ?? string.Empty,
  403. SupplierCode = row.SupplierCode ?? string.Empty,
  404. Supplier = row.Supplier ?? string.Empty,
  405. RequestDate = now,
  406. NeedDate = row.NeedDate,
  407. PoNumber = row.PoNumber ?? string.Empty,
  408. PoLine = row.PoLine,
  409. SchedQty = row.RestQty,
  410. LastSentQty = 0,
  411. SentQty = 0,
  412. RestQty = row.RestQty,
  413. CreateUser = account,
  414. UpdateUser = account,
  415. CreateTime = now,
  416. UpdateTime = now,
  417. Remarks = "由系统按采购行自动生成",
  418. IsActive = 1,
  419. ReturnQty = 0,
  420. TenantId = _userManager.TenantId > 0 ? _userManager.TenantId : row.TenantId
  421. }).ToList();
  422. var createdCount = await _db.Insertable(schedules).ExecuteCommandAsync();
  423. await UpsertDeliveryScheduleMdpAsync(schedules, now);
  424. _db.Ado.CommitTran();
  425. return new DeliveryScheduleGenerateResult
  426. {
  427. CandidateCount = candidates.Count,
  428. CreatedCount = createdCount,
  429. DsNums = schedules.Select(x => x.DsNum).Take(20).ToList(),
  430. Message = createdCount > 0
  431. ? $"生成交货单成功,共生成 {createdCount} 条"
  432. : "未生成交货单,请检查采购行是否已存在有效交货计划"
  433. };
  434. }
  435. catch
  436. {
  437. _db.Ado.RollbackTran();
  438. throw;
  439. }
  440. }
  441. private async Task UpsertDeliveryScheduleMdpAsync(List<PolistDeliverySchedule> schedules, DateTime now)
  442. {
  443. if (schedules.Count == 0) return;
  444. var batchId = $"S3_DELIVERY_{now:yyyyMMddHHmmss}";
  445. foreach (var schedule in schedules)
  446. {
  447. await _db.Ado.ExecuteCommandAsync(
  448. """
  449. INSERT INTO mdp_std_delivery_schedule
  450. (tenant_id, source_system, delivery_plan_no, po_no, po_line, item_code, supplier_code, supplier_name, schedule_qty, sent_qty, rest_qty, return_qty, request_date, need_date, submit_date, last_sent_date, status, source_biz_key, sync_batch_id, sync_time)
  451. VALUES
  452. (@TenantId, 'AIDOP', @DsNum, @PoNumber, @PoLine, @ItemNum, @SupplierCode, @Supplier, @SchedQty, @SentQty, @RestQty, @ReturnQty, @RequestDate, @NeedDate, @SubmitDate, @LastSentDate, @Status, @DsNum, @BatchId, @Now)
  453. ON DUPLICATE KEY UPDATE
  454. po_no=VALUES(po_no),
  455. po_line=VALUES(po_line),
  456. item_code=VALUES(item_code),
  457. supplier_code=VALUES(supplier_code),
  458. supplier_name=VALUES(supplier_name),
  459. schedule_qty=VALUES(schedule_qty),
  460. sent_qty=VALUES(sent_qty),
  461. rest_qty=VALUES(rest_qty),
  462. return_qty=VALUES(return_qty),
  463. request_date=VALUES(request_date),
  464. need_date=VALUES(need_date),
  465. submit_date=VALUES(submit_date),
  466. last_sent_date=VALUES(last_sent_date),
  467. status=VALUES(status),
  468. sync_batch_id=VALUES(sync_batch_id),
  469. sync_time=VALUES(sync_time),
  470. update_time=CURRENT_TIMESTAMP
  471. """,
  472. new SugarParameter("@TenantId", schedule.TenantId ?? 0),
  473. new SugarParameter("@DsNum", schedule.DsNum),
  474. new SugarParameter("@PoNumber", schedule.PoNumber),
  475. new SugarParameter("@PoLine", schedule.PoLine.ToString()),
  476. new SugarParameter("@ItemNum", schedule.ItemNum),
  477. new SugarParameter("@SupplierCode", schedule.SupplierCode),
  478. new SugarParameter("@Supplier", schedule.Supplier),
  479. new SugarParameter("@SchedQty", schedule.SchedQty),
  480. new SugarParameter("@SentQty", schedule.SentQty),
  481. new SugarParameter("@RestQty", schedule.RestQty),
  482. new SugarParameter("@ReturnQty", schedule.ReturnQty),
  483. new SugarParameter("@RequestDate", schedule.RequestDate),
  484. new SugarParameter("@NeedDate", schedule.NeedDate),
  485. new SugarParameter("@SubmitDate", schedule.SubmitDate),
  486. new SugarParameter("@LastSentDate", schedule.LastSentDate),
  487. new SugarParameter("@Status", schedule.Status),
  488. new SugarParameter("@BatchId", batchId),
  489. new SugarParameter("@Now", now));
  490. await _db.Ado.ExecuteCommandAsync(
  491. """
  492. INSERT INTO dwd_supplier_delivery
  493. (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, last_delivery_date, delivery_status, source_system, sync_batch_id, calc_time)
  494. SELECT
  495. @TenantId,
  496. @StatDate,
  497. IFNULL(m.PurOrd, @PoNumber),
  498. CAST(IFNULL(d.Line, @PoLineInt) AS CHAR),
  499. IFNULL(m.Potype, ''),
  500. IFNULL(m.Supp, @SupplierCode),
  501. @Supplier,
  502. IFNULL(d.ItemNum, @ItemNum),
  503. IFNULL(i.Descr, ''),
  504. IFNULL(d.QtyOrded, @SchedQty),
  505. @SchedQty,
  506. @SentQty,
  507. IFNULL(d.QtyReceived, IFNULL(d.RctQty, 0)),
  508. @ReturnQty,
  509. @RestQty,
  510. d.DueDate,
  511. @NeedDate,
  512. @LastSentDate,
  513. CASE
  514. WHEN IFNULL(@Status, '') = 'C' THEN 'CANCELLED'
  515. WHEN IFNULL(@RestQty, 0) <= 0 THEN 'CLOSED'
  516. WHEN IFNULL(@SentQty, 0) > 0 THEN 'PARTIAL'
  517. ELSE 'OPEN'
  518. END,
  519. 'AIDOP',
  520. @BatchId,
  521. @Now
  522. FROM PurOrdMaster m
  523. JOIN PurOrdDetail d ON m.RecID = d.PurOrdRecID
  524. LEFT JOIN ItemMaster i ON d.ItemNum = i.ItemNum
  525. WHERE m.PurOrd = @PoNumber AND d.Line = @PoLineInt
  526. LIMIT 1
  527. ON DUPLICATE KEY UPDATE
  528. supplier_code=VALUES(supplier_code),
  529. supplier_name=VALUES(supplier_name),
  530. item_code=VALUES(item_code),
  531. item_name=VALUES(item_name),
  532. order_qty=VALUES(order_qty),
  533. schedule_qty=VALUES(schedule_qty),
  534. delivery_qty=VALUES(delivery_qty),
  535. receipt_qty=VALUES(receipt_qty),
  536. return_qty=VALUES(return_qty),
  537. remaining_qty=VALUES(remaining_qty),
  538. due_date=VALUES(due_date),
  539. need_date=VALUES(need_date),
  540. last_delivery_date=VALUES(last_delivery_date),
  541. delivery_status=VALUES(delivery_status),
  542. sync_batch_id=VALUES(sync_batch_id),
  543. calc_time=VALUES(calc_time),
  544. update_time=CURRENT_TIMESTAMP
  545. """,
  546. new SugarParameter("@TenantId", schedule.TenantId ?? 0),
  547. new SugarParameter("@StatDate", now.Date),
  548. new SugarParameter("@PoNumber", schedule.PoNumber),
  549. new SugarParameter("@PoLineInt", schedule.PoLine),
  550. new SugarParameter("@SupplierCode", schedule.SupplierCode),
  551. new SugarParameter("@Supplier", schedule.Supplier),
  552. new SugarParameter("@ItemNum", schedule.ItemNum),
  553. new SugarParameter("@SchedQty", schedule.SchedQty),
  554. new SugarParameter("@SentQty", schedule.SentQty),
  555. new SugarParameter("@ReturnQty", schedule.ReturnQty),
  556. new SugarParameter("@RestQty", schedule.RestQty),
  557. new SugarParameter("@NeedDate", schedule.NeedDate),
  558. new SugarParameter("@LastSentDate", schedule.LastSentDate),
  559. new SugarParameter("@Status", schedule.Status),
  560. new SugarParameter("@BatchId", batchId),
  561. new SugarParameter("@Now", now));
  562. }
  563. }
  564. private static List<long> ParseIds(string ids)
  565. {
  566. return ids.Split(',', StringSplitOptions.RemoveEmptyEntries)
  567. .Select(x => long.TryParse(x.Trim(), out var id) ? id : 0)
  568. .Where(x => x > 0)
  569. .Distinct()
  570. .ToList();
  571. }
  572. private static string BuildCandidateKey(DeliveryScheduleCandidateRow row)
  573. {
  574. return $"{row.Domain}|{row.PoNumber}|{row.PoLine}|{row.IcdsId}";
  575. }
  576. private static string BuildListOrderBy(string? sortField, string? sortOrder)
  577. {
  578. var dir = string.Equals(sortOrder, "asc", StringComparison.OrdinalIgnoreCase) ? "ASC" : "DESC";
  579. return sortField?.ToLowerInvariant() switch
  580. {
  581. "dsnum" => $"ss.dsnum {dir}",
  582. "status" => $"ss.status {dir}",
  583. "itemnum" => $"ss.itemnum {dir}",
  584. "requestdate" => $"ss.requestdate {dir}",
  585. "needdate" => $"ss.needdate {dir}",
  586. "schedqty" => $"ss.schedqty {dir}",
  587. "sentqty" => $"ss.sentqty {dir}",
  588. "restqty" => $"ss.restqty {dir}",
  589. "submitdate" => $"ss.submitdate {dir}",
  590. "ponumber" => $"ss.ponumber {dir}",
  591. _ => "ss.Id DESC"
  592. };
  593. }
  594. private static string BuildPurchaseOrderOrderBy(string? sortField, string? sortOrder)
  595. {
  596. var dir = string.Equals(sortOrder, "asc", StringComparison.OrdinalIgnoreCase) ? "ASC" : "DESC";
  597. return sortField?.ToLowerInvariant() switch
  598. {
  599. "purord" => $"m.PurOrd {dir}",
  600. "line" => $"d.Line {dir}",
  601. "itemnum" => $"d.ItemNum {dir}",
  602. "supp" => $"m.Supp {dir}",
  603. "name" => $"s.Name {dir}",
  604. "duedate" => $"d.DueDate {dir}",
  605. "qtyorded" => $"d.QtyOrded {dir}",
  606. "buyer" => $"m.Buyer {dir}",
  607. _ => "m.PurOrd DESC, d.Line ASC"
  608. };
  609. }
  610. private sealed class DeliveryScheduleListRow
  611. {
  612. public long Id { get; set; }
  613. public string? Domain { get; set; }
  614. public long? IcdsId { get; set; }
  615. public string? DsNum { get; set; }
  616. public string? Status { get; set; }
  617. public string? ItemNum { get; set; }
  618. public string? Descr { get; set; }
  619. public string? Um { get; set; }
  620. public string? PurGroup { get; set; }
  621. public string? SupplierCode { get; set; }
  622. public string? Supplier { get; set; }
  623. public DateTime? SubmitDate { get; set; }
  624. public DateTime? RequestDate { get; set; }
  625. public DateTime? NeedDate { get; set; }
  626. public string? PoNumber { get; set; }
  627. public int? PoLine { get; set; }
  628. public decimal? SchedQty { get; set; }
  629. public DateTime? LastSentDate { get; set; }
  630. public decimal? LastSentQty { get; set; }
  631. public decimal? SentQty { get; set; }
  632. public decimal? RestQty { get; set; }
  633. }
  634. private sealed class PurchaseOrderSelectRow
  635. {
  636. public string? PurOrd { get; set; }
  637. public int? Line { get; set; }
  638. public string? ItemNum { get; set; }
  639. public string? Um { get; set; }
  640. public string? Supp { get; set; }
  641. public string? Name { get; set; }
  642. public DateTime? DueDate { get; set; }
  643. public decimal? QtyOrded { get; set; }
  644. public string? Buyer { get; set; }
  645. }
  646. private sealed class DeliveryScheduleGenerateResult
  647. {
  648. public int CandidateCount { get; set; }
  649. public int CreatedCount { get; set; }
  650. public List<string> DsNums { get; set; } = new();
  651. public string Message { get; set; } = string.Empty;
  652. }
  653. private sealed class DeliveryScheduleCandidateRow
  654. {
  655. public string? Domain { get; set; }
  656. public long IcdsId { get; set; }
  657. public string? PoNumber { get; set; }
  658. public int PoLine { get; set; }
  659. public string? ItemNum { get; set; }
  660. public string? Um { get; set; }
  661. public string? PurGroup { get; set; }
  662. public string? SupplierCode { get; set; }
  663. public string? Supplier { get; set; }
  664. public DateTime? NeedDate { get; set; }
  665. public decimal RestQty { get; set; }
  666. public long? TenantId { get; set; }
  667. }
  668. }