DeliveryScheduleService.cs 65 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493
  1. using Admin.NET.Plugin.AiDOP.Infrastructure;
  2. using Admin.NET.Plugin.AiDOP.Rule;
  3. using Yitter.IdGenerator;
  4. namespace Admin.NET.Plugin.AiDOP.Supply;
  5. /// <summary>
  6. /// 物料交货计划服务
  7. /// </summary>
  8. [ApiDescriptionSettings(Order = 306, Description = "物料交货计划")]
  9. [Route("api/Supply")]
  10. [AllowAnonymous]
  11. [NonUnify]
  12. public class DeliveryScheduleService : IDynamicApiController, ITransient
  13. {
  14. private readonly ISqlSugarClient _db;
  15. private readonly SqlSugarRepository<PolistDeliverySchedule> _rep;
  16. private readonly UserManager _userManager;
  17. private readonly NumberRuleService _numberRuleService;
  18. private readonly PurchaseRequestMergeService _purchaseRequestMergeService;
  19. private readonly PurchaseOrderTransferService _purchaseOrderTransferService;
  20. private readonly PurchaseRequestExternalPushService _purchaseRequestExternalPushService;
  21. private readonly S3OperationLogService _operationLogService;
  22. private readonly RuleConfigService _ruleConfigService;
  23. private readonly AidopActionRunLogWriter _runLog;
  24. private readonly DataPlatform.Wms.PurOrdWmsPushService _purOrdWmsPush;
  25. public DeliveryScheduleService(
  26. ISqlSugarClient db,
  27. SqlSugarRepository<PolistDeliverySchedule> rep,
  28. UserManager userManager,
  29. NumberRuleService numberRuleService,
  30. PurchaseRequestMergeService purchaseRequestMergeService,
  31. PurchaseOrderTransferService purchaseOrderTransferService,
  32. PurchaseRequestExternalPushService purchaseRequestExternalPushService,
  33. S3OperationLogService operationLogService,
  34. RuleConfigService ruleConfigService,
  35. AidopActionRunLogWriter runLog,
  36. DataPlatform.Wms.PurOrdWmsPushService purOrdWmsPush)
  37. {
  38. _purOrdWmsPush = purOrdWmsPush;
  39. _db = db;
  40. _rep = rep;
  41. _userManager = userManager;
  42. _numberRuleService = numberRuleService;
  43. _purchaseRequestMergeService = purchaseRequestMergeService;
  44. _purchaseOrderTransferService = purchaseOrderTransferService;
  45. _purchaseRequestExternalPushService = purchaseRequestExternalPushService;
  46. _operationLogService = operationLogService;
  47. _ruleConfigService = ruleConfigService;
  48. _runLog = runLog;
  49. }
  50. private long ResolveTenantOrThrow() => Infrastructure.AidopTenantScope.ResolveOrThrow(_userManager);
  51. private static long ResolveScheduleTenant(PolistDeliverySchedule schedule)
  52. {
  53. if (schedule.TenantId is > 0) return schedule.TenantId.Value;
  54. throw Oops.Oh($"交货计划无法确定租户(单号:{schedule.DsNum ?? schedule.PoNumber ?? "?"})");
  55. }
  56. [DisplayName("物料交货计划列表")]
  57. [HttpGet("delivery-schedule/list")]
  58. public async Task<object> GetList([FromQuery] DeliveryScheduleListInput input)
  59. {
  60. var page = input.Page <= 0 ? 1 : input.Page;
  61. var pageSize = input.PageSize <= 0 ? 10 : input.PageSize;
  62. var offset = (page - 1) * pageSize;
  63. //if (await ShouldUseStdAsync())
  64. //{
  65. //return await GetStdListAsync(input, page, pageSize, offset);
  66. //}
  67. var pars = new List<SugarParameter>();
  68. var where = new List<string> { "ss.isactive = 1" };
  69. var tenantId = ResolveTenantOrThrow();
  70. where.Add("ss.tenant_id = @TenantId");
  71. pars.Add(new SugarParameter("@TenantId", tenantId));
  72. if (!string.IsNullOrWhiteSpace(input.PoNumber))
  73. {
  74. where.Add("ss.ponumber LIKE @PoNumber");
  75. pars.Add(new SugarParameter("@PoNumber", $"%{input.PoNumber.Trim()}%"));
  76. }
  77. if (!string.IsNullOrWhiteSpace(input.DsNum))
  78. {
  79. where.Add("ss.dsnum LIKE @DsNum");
  80. pars.Add(new SugarParameter("@DsNum", $"%{input.DsNum.Trim()}%"));
  81. }
  82. if (!string.IsNullOrWhiteSpace(input.ItemNum))
  83. {
  84. where.Add("ss.itemnum LIKE @ItemNum");
  85. pars.Add(new SugarParameter("@ItemNum", $"%{input.ItemNum.Trim()}%"));
  86. }
  87. if (!string.IsNullOrWhiteSpace(input.Supplier))
  88. {
  89. where.Add("(ss.suppliercode LIKE @Supplier OR ss.supplier LIKE @Supplier)");
  90. pars.Add(new SugarParameter("@Supplier", $"%{input.Supplier.Trim()}%"));
  91. }
  92. if (!string.IsNullOrWhiteSpace(input.Status))
  93. {
  94. where.Add("ss.status = @Status");
  95. pars.Add(new SugarParameter("@Status", input.Status.Trim()));
  96. }
  97. var orderBy = BuildListOrderBy(input.SortField, input.SortOrder);
  98. var fromSql = $"""
  99. FROM srm_polist_ds ss
  100. LEFT JOIN ItemMaster im ON ss.itemnum = im.ItemNum AND (ss.tenant_id = im.tenant_id OR ss.tenant_id IS NULL)
  101. WHERE {string.Join(" AND ", where)}
  102. """;
  103. var total = await _db.Ado.GetIntAsync($"SELECT COUNT(1) {fromSql}", pars);
  104. var list = await _db.Ado.SqlQueryAsync<DeliveryScheduleListRow>(
  105. $"""
  106. SELECT
  107. ss.Id AS Id,
  108. ss.domain AS Domain,
  109. ss.icdsid AS IcdsId,
  110. ss.dsnum AS DsNum,
  111. ss.status AS Status,
  112. ss.itemnum AS ItemNum,
  113. im.Descr AS Descr,
  114. ss.um AS Um,
  115. ss.purgroup AS PurGroup,
  116. ss.suppliercode AS SupplierCode,
  117. ss.supplier AS Supplier,
  118. ss.submitdate AS SubmitDate,
  119. ss.requestdate AS RequestDate,
  120. ss.needdate AS NeedDate,
  121. ss.ponumber AS PoNumber,
  122. ss.poline AS PoLine,
  123. ss.schedqty AS SchedQty,
  124. ss.lastsentdate AS LastSentDate,
  125. ss.lastsentqty AS LastSentQty,
  126. ss.sentqty AS SentQty,
  127. ss.restqty AS RestQty
  128. {fromSql}
  129. ORDER BY {orderBy}
  130. LIMIT {pageSize} OFFSET {offset}
  131. """,
  132. pars);
  133. return new { total, page, pageSize, list };
  134. }
  135. private async Task<bool> ShouldUseStdAsync()
  136. {
  137. return await _db.Ado.GetIntAsync("SELECT COUNT(1) FROM mdp_std_delivery_schedule LIMIT 1") > 0;
  138. }
  139. private async Task<object> GetStdListAsync(DeliveryScheduleListInput input, int page, int pageSize, int offset)
  140. {
  141. var pars = new List<SugarParameter>();
  142. var where = new List<string> { "1=1" };
  143. var tenantId = ResolveTenantOrThrow();
  144. where.Add("ss.tenant_id = @TenantId");
  145. pars.Add(new SugarParameter("@TenantId", tenantId));
  146. if (!string.IsNullOrWhiteSpace(input.PoNumber))
  147. {
  148. where.Add("ss.po_no LIKE @PoNumber");
  149. pars.Add(new SugarParameter("@PoNumber", $"%{input.PoNumber.Trim()}%"));
  150. }
  151. if (!string.IsNullOrWhiteSpace(input.DsNum))
  152. {
  153. where.Add("ss.delivery_plan_no LIKE @DsNum");
  154. pars.Add(new SugarParameter("@DsNum", $"%{input.DsNum.Trim()}%"));
  155. }
  156. if (!string.IsNullOrWhiteSpace(input.ItemNum))
  157. {
  158. where.Add("ss.item_code LIKE @ItemNum");
  159. pars.Add(new SugarParameter("@ItemNum", $"%{input.ItemNum.Trim()}%"));
  160. }
  161. if (!string.IsNullOrWhiteSpace(input.Supplier))
  162. {
  163. where.Add("(ss.supplier_code LIKE @Supplier OR ss.supplier_name LIKE @Supplier)");
  164. pars.Add(new SugarParameter("@Supplier", $"%{input.Supplier.Trim()}%"));
  165. }
  166. if (!string.IsNullOrWhiteSpace(input.Status))
  167. {
  168. where.Add("ss.status = @Status");
  169. pars.Add(new SugarParameter("@Status", input.Status.Trim()));
  170. }
  171. var orderBy = BuildStdListOrderBy(input.SortField, input.SortOrder);
  172. 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)}";
  173. var total = await _db.Ado.GetIntAsync($"SELECT COUNT(1) {fromSql}", pars);
  174. var list = await _db.Ado.SqlQueryAsync<DeliveryScheduleListRow>(
  175. $"""
  176. SELECT
  177. ss.id AS Id,
  178. NULL AS Domain,
  179. NULL AS IcdsId,
  180. ss.delivery_plan_no AS DsNum,
  181. ss.status AS Status,
  182. ss.item_code AS ItemNum,
  183. im.item_name AS Descr,
  184. NULL AS Um,
  185. NULL AS PurGroup,
  186. ss.supplier_code AS SupplierCode,
  187. ss.supplier_name AS Supplier,
  188. ss.submit_date AS SubmitDate,
  189. ss.request_date AS RequestDate,
  190. ss.need_date AS NeedDate,
  191. ss.po_no AS PoNumber,
  192. CAST(ss.po_line AS SIGNED) AS PoLine,
  193. ss.schedule_qty AS SchedQty,
  194. ss.last_sent_date AS LastSentDate,
  195. ss.sent_qty AS LastSentQty,
  196. ss.sent_qty AS SentQty,
  197. ss.rest_qty AS RestQty
  198. {fromSql}
  199. ORDER BY {orderBy}
  200. LIMIT {pageSize} OFFSET {offset}
  201. """,
  202. pars);
  203. return new { total, page, pageSize, list, source = "mdp_std_delivery_schedule" };
  204. }
  205. private static string BuildStdListOrderBy(string? sortField, string? sortOrder)
  206. {
  207. var dir = string.Equals(sortOrder, "asc", StringComparison.OrdinalIgnoreCase) ? "ASC" : "DESC";
  208. return sortField?.ToLowerInvariant() switch
  209. {
  210. "dsnum" => $"ss.delivery_plan_no {dir}",
  211. "status" => $"ss.status {dir}",
  212. "itemnum" => $"ss.item_code {dir}",
  213. "suppliercode" => $"ss.supplier_code {dir}",
  214. "supplier" => $"ss.supplier_name {dir}",
  215. "submitdate" => $"ss.submit_date {dir}",
  216. "requestdate" => $"ss.request_date {dir}",
  217. "needdate" => $"ss.need_date {dir}",
  218. "ponumber" => $"ss.po_no {dir}",
  219. "poline" => $"ss.po_line {dir}",
  220. "schedqty" => $"ss.schedule_qty {dir}",
  221. "sentqty" => $"ss.sent_qty {dir}",
  222. "restqty" => $"ss.rest_qty {dir}",
  223. _ => "ss.need_date DESC, ss.delivery_plan_no DESC"
  224. };
  225. }
  226. [DisplayName("生成交货单")]
  227. [HttpPost("delivery-schedule/generate")]
  228. public async Task<object> Generate()
  229. {
  230. var tenantId = ResolveTenantOrThrow();
  231. var logId = await _runLog.StartAsync("S3_DELIVERY_GENERATE", tenantId, "tenant", tenantId, null);
  232. try
  233. {
  234. var result = await GenerateDeliverySchedulesFromDemandAsync();
  235. await _operationLogService.WriteAsync(new S3OperationLogDto
  236. {
  237. Action = "S3_DELIVERY_GENERATE",
  238. DisplayTitle = "S3 生成交货单",
  239. Message = result.Message,
  240. Result = result,
  241. Success = true
  242. });
  243. await _runLog.SuccessAsync(logId, result.Message, new
  244. {
  245. result.DemandCount,
  246. result.CandidateCount,
  247. result.CreatedCount,
  248. result.ExceptionCount,
  249. result.PurchaseRequestCount,
  250. result.PurchaseOrderCount,
  251. result.PurchaseOrderLineCount
  252. });
  253. return result;
  254. }
  255. catch (Exception ex)
  256. {
  257. await _operationLogService.WriteAsync(new S3OperationLogDto
  258. {
  259. Action = "S3_DELIVERY_GENERATE",
  260. DisplayTitle = "S3 生成交货单失败",
  261. Message = ex.Message,
  262. Exception = ex.ToString(),
  263. Success = false
  264. });
  265. await _runLog.FailedAsync(logId, ex.Message);
  266. throw;
  267. }
  268. }
  269. [DisplayName("批量添加交货单")]
  270. [HttpPost("delivery-schedule/batch-generate")]
  271. public async Task<object> BatchGenerate([FromBody] DeliveryScheduleGenerateInput input)
  272. {
  273. if (string.IsNullOrWhiteSpace(input.PoNumber))
  274. throw Oops.Oh("采购单号不能为空");
  275. return await GenerateDeliverySchedulesFromPurchaseOrdersAsync(input.PoNumber.Trim());
  276. }
  277. [DisplayName("发布交货单")]
  278. [HttpPost("delivery-schedule/publish")]
  279. public async Task<object> Publish([FromBody] DeliveryScheduleBatchIdsInput input)
  280. {
  281. var ids = ParseIds(input.Ids);
  282. if (!ids.Any()) throw Oops.Oh("请勾选要发布的数据");
  283. var account = _userManager.Account ?? "system";
  284. var tenantId = ResolveTenantOrThrow();
  285. var affected = await _db.Updateable<PolistDeliverySchedule>()
  286. .SetColumns(x => new PolistDeliverySchedule
  287. {
  288. Status = "P",
  289. SubmitDate = DateTime.Now,
  290. UpdateUser = account,
  291. UpdateTime = DateTime.Now
  292. })
  293. .Where(x => ids.Contains(x.Id) && x.IsActive == 1 && x.Status == "N" && x.TenantId == tenantId)
  294. .ExecuteCommandAsync();
  295. var warnings = new List<string>();
  296. if (affected > 0)
  297. await _purOrdWmsPush.TryEnqueueSchedulesSafeAsync(tenantId, ids, warnings);
  298. return new { affected, message = affected > 0 ? "发布成功" : "无可发布数据", warnings };
  299. }
  300. [DisplayName("取消发布交货单")]
  301. [HttpPost("delivery-schedule/unpublish/{id:long}")]
  302. public async Task<object> UnPublish(long id)
  303. {
  304. var tenantId = ResolveTenantOrThrow();
  305. var row = await _rep.GetFirstAsync(x => x.Id == id && x.IsActive == 1 && x.TenantId == tenantId) ?? throw Oops.Oh("交货单不存在");
  306. row.Status = "N";
  307. row.UpdateUser = _userManager.Account;
  308. row.UpdateTime = DateTime.Now;
  309. await _rep.UpdateAsync(row);
  310. return new { message = "取消发布成功" };
  311. }
  312. [DisplayName("作废交货单")]
  313. [HttpPost("delivery-schedule/cancel/{id:long}")]
  314. public async Task<object> Cancel(long id)
  315. {
  316. var tenantId = ResolveTenantOrThrow();
  317. var row = await _rep.GetFirstAsync(x => x.Id == id && x.IsActive == 1 && x.TenantId == tenantId) ?? throw Oops.Oh("交货单不存在");
  318. row.Status = "C";
  319. row.UpdateUser = _userManager.Account;
  320. row.UpdateTime = DateTime.Now;
  321. await _rep.UpdateAsync(row);
  322. return new { message = "作废成功" };
  323. }
  324. [DisplayName("选择采购单分页")]
  325. [HttpGet("delivery-schedule/purchase-order/page")]
  326. public async Task<object> GetPurchaseOrderPage([FromQuery] PurchaseOrderSelectInput input)
  327. {
  328. var page = input.Page <= 0 ? 1 : input.Page;
  329. var pageSize = input.PageSize <= 0 ? 10 : input.PageSize;
  330. var offset = (page - 1) * pageSize;
  331. var pars = new List<SugarParameter>();
  332. var where = new List<string>
  333. {
  334. "IFNULL(m.Status,'') <> 'C'",
  335. "IFNULL(d.Status,'') <> 'C'",
  336. "(IFNULL(ds.dsnum, '') = '' OR (ds.status = 'C' AND d.QtyOrded - d.RctQty > 0))"
  337. };
  338. var tenantId = ResolveTenantOrThrow();
  339. where.Add("m.tenant_id = @TenantId");
  340. pars.Add(new SugarParameter("@TenantId", tenantId));
  341. if (!string.IsNullOrWhiteSpace(input.PurOrd))
  342. {
  343. where.Add("m.PurOrd LIKE @PurOrd");
  344. pars.Add(new SugarParameter("@PurOrd", $"%{input.PurOrd.Trim()}%"));
  345. }
  346. if (!string.IsNullOrWhiteSpace(input.ItemNum))
  347. {
  348. where.Add("d.ItemNum LIKE @ItemNum");
  349. pars.Add(new SugarParameter("@ItemNum", $"%{input.ItemNum.Trim()}%"));
  350. }
  351. if (!string.IsNullOrWhiteSpace(input.Supp))
  352. {
  353. where.Add("(m.Supp LIKE @Supp OR s.Name LIKE @Supp)");
  354. pars.Add(new SugarParameter("@Supp", $"%{input.Supp.Trim()}%"));
  355. }
  356. var orderBy = BuildPurchaseOrderOrderBy(input.SortField, input.SortOrder);
  357. var fromSql = $"""
  358. FROM PurOrdMaster m
  359. INNER JOIN PurOrdDetail d ON m.RecID = d.PurOrdRecID
  360. LEFT JOIN srm_polist_ds ds ON d.PurOrd = ds.ponumber AND d.Line = ds.poline AND ds.isactive = 1
  361. LEFT JOIN ConsigneeAddressMaster s ON m.Supp = s.Address AND s.Typed = 'Supp'
  362. WHERE {string.Join(" AND ", where)}
  363. """;
  364. var distinctSql = $"SELECT DISTINCT m.PurOrd, d.Line, d.ItemNum {fromSql}";
  365. var total = await _db.Ado.GetIntAsync($"SELECT COUNT(1) FROM ({distinctSql}) t", pars);
  366. var list = await _db.Ado.SqlQueryAsync<PurchaseOrderSelectRow>(
  367. $"""
  368. SELECT DISTINCT
  369. m.PurOrd AS PurOrd,
  370. d.Line AS Line,
  371. d.ItemNum AS ItemNum,
  372. d.UM AS Um,
  373. m.Supp AS Supp,
  374. s.Name AS Name,
  375. d.DueDate AS DueDate,
  376. d.QtyOrded AS QtyOrded,
  377. m.Buyer AS Buyer
  378. {fromSql}
  379. ORDER BY {orderBy}
  380. LIMIT {pageSize} OFFSET {offset}
  381. """,
  382. pars);
  383. return new { total, page, pageSize, list };
  384. }
  385. private async Task<DeliveryScheduleGenerateResult> GenerateDeliverySchedulesFromDemandAsync()
  386. {
  387. var account = _userManager.Account ?? "system";
  388. var now = DateTime.Now;
  389. var optTime = now;
  390. var ruleResult = await _ruleConfigService.ResolveS3DeliveryGenerateOptionsAsync(ResolveTenantOrThrow(), null);
  391. if (!ruleResult.Success)
  392. {
  393. throw Oops.Oh($"S3交货单生成规则配置无效:{string.Join(";", ruleResult.Errors)}");
  394. }
  395. var rules = ruleResult.Options;
  396. var ruleSnapshot = _ruleConfigService.CreateSnapshot("S3", "DELIVERY_GENERATE", ResolveTenantOrThrow(), null, ruleResult, now);
  397. var pars = new List<SugarParameter>();
  398. var where = new List<string>
  399. {
  400. "IFNULL(a.status,'') = 'P'",
  401. "IFNULL(a.tosechedqty,0) > 0",
  402. "(IFNULL(a.ishistoryversion,'') = '' OR a.ishistoryversion = 'N')",
  403. "IFNULL(a.IsDeleted,0)=0"
  404. };
  405. var tenantId = ResolveTenantOrThrow();
  406. where.Add("a.tenant_id = @TenantId");
  407. pars.Add(new SugarParameter("@TenantId", tenantId));
  408. var demands = await _db.Ado.SqlQueryAsync<DemandScheduleCandidateRow>(
  409. $"""
  410. SELECT
  411. a.Id AS IcdsId,
  412. IFNULL(TRIM(a.itemnum),'') AS ItemNum,
  413. a.requestdate AS RequestDate,
  414. a.arrivaldate AS ArrivalDate,
  415. IFNULL(a.tosechedqty,0) AS DemandQty,
  416. a.tenant_id AS TenantId,
  417. a.factory_id AS FactoryId,
  418. a.company_id AS CompanyId,
  419. IFNULL(TRIM(im.PurMfg),'') AS PurMfg,
  420. IFNULL(TRIM(im.UM),'') AS Um,
  421. IFNULL(TRIM(im.Descr),'') AS ItemName,
  422. ic.Id AS IcItemId
  423. FROM ic_demandschedule a
  424. LEFT JOIN ItemMaster im ON a.itemnum = im.ItemNum AND (a.tenant_id = im.tenant_id OR a.tenant_id IS NULL)
  425. LEFT JOIN ic_item ic ON a.itemnum = ic.number AND (a.tenant_id = ic.tenant_id OR a.tenant_id IS NULL)
  426. WHERE {string.Join(" AND ", where)}
  427. ORDER BY a.arrivaldate, a.requestdate, a.Id
  428. """,
  429. pars);
  430. if (demands.Count == 0)
  431. {
  432. return new DeliveryScheduleGenerateResult
  433. {
  434. DemandCount = 0,
  435. CandidateCount = 0,
  436. CreatedCount = 0,
  437. RuleSnapshot = ruleSnapshot,
  438. Message = "无已发布且待生成交货单的需求计划"
  439. };
  440. }
  441. var demandIds = demands.Select(x => x.IcdsId).Distinct().ToList();
  442. var existingUsages = await QueryExistingScheduleUsagesAsync(demandIds);
  443. var itemNums = demands.Select(x => x.ItemNum).Where(x => !string.IsNullOrWhiteSpace(x)).Distinct().ToList();
  444. var sourceLists = await QuerySourceListsAsync(itemNums);
  445. var supplierCodes = sourceLists.Select(x => x.SupplierCode).Where(x => !string.IsNullOrWhiteSpace(x)).Distinct().ToList();
  446. var poLines = supplierCodes.Count == 0
  447. ? new List<PurchaseOrderAvailableRow>()
  448. : await QueryAvailablePurchaseOrderLinesAsync(itemNums, supplierCodes, rules);
  449. var exceptions = new List<DeliveryExceptionMaster>();
  450. var schedules = new List<PolistDeliverySchedule>();
  451. var purchaseRequests = new List<PurchaseRequestMain>();
  452. var prMergeReducedCount = 0;
  453. var purchaseOrderTransferResult = new PurchaseOrderTransferResult();
  454. var allocatedByPoLine = new Dictionary<string, decimal>();
  455. var skippedCount = 0;
  456. foreach (var demand in demands)
  457. {
  458. if (string.IsNullOrWhiteSpace(demand.ItemNum))
  459. {
  460. skippedCount++;
  461. AddDeliveryException(exceptions, demand, optTime, "需求计划缺少物料编码,无法生成交货单", demand.DemandQty);
  462. continue;
  463. }
  464. if (!string.Equals(demand.PurMfg, "P", StringComparison.OrdinalIgnoreCase))
  465. {
  466. skippedCount++;
  467. AddDeliveryException(exceptions, demand, optTime, $"物料[{demand.ItemNum}]不是采购件,不需要生成交货单", demand.DemandQty);
  468. continue;
  469. }
  470. var usedQty = existingUsages.TryGetValue(demand.IcdsId, out var used) ? used : 0m;
  471. var demandRestQty = demand.DemandQty - usedQty;
  472. if (demandRestQty <= 0)
  473. {
  474. skippedCount++;
  475. continue;
  476. }
  477. var itemSources = sourceLists
  478. .Where(x => string.Equals(x.ItemNum, demand.ItemNum, StringComparison.OrdinalIgnoreCase))
  479. .OrderByDescending(x => x.QuotaRate)
  480. .ToList();
  481. if (itemSources.Count == 0)
  482. {
  483. skippedCount++;
  484. AddDeliveryException(exceptions, demand, optTime, $"物料交货计划[{demand.ItemNum}:{FormatDate(demand.RequestDate)}]没有维护货源清单,无法转交货单", demandRestQty);
  485. continue;
  486. }
  487. var quotaTotal = itemSources.Sum(x => x.QuotaRate);
  488. if (Math.Abs(quotaTotal - rules.QuotaRequiredTotal) > rules.QuotaTolerance)
  489. {
  490. skippedCount++;
  491. AddDeliveryException(exceptions, demand, optTime, $"物料交货计划[{demand.ItemNum}:{FormatDate(demand.RequestDate)}]的货源清单供应商配额之和不为{rules.QuotaRequiredTotal:0.####}%,无法转交货单", demandRestQty);
  492. continue;
  493. }
  494. var allocatedForDemand = 0m;
  495. for (var i = 0; i < itemSources.Count; i++)
  496. {
  497. var source = itemSources[i];
  498. var remainingDemand = demandRestQty - allocatedForDemand;
  499. if (remainingDemand <= 0) break;
  500. var supplierNeedQty = i == itemSources.Count - 1
  501. ? remainingDemand
  502. : Math.Min(remainingDemand, Math.Ceiling(demandRestQty * source.QuotaRate / 100m));
  503. if (supplierNeedQty <= 0) continue;
  504. var supplierAllocated = AllocateDemandToPurchaseLines(
  505. demand,
  506. source,
  507. poLines,
  508. allocatedByPoLine,
  509. supplierNeedQty,
  510. remainingDemand,
  511. schedules,
  512. now,
  513. account,
  514. rules);
  515. allocatedForDemand += supplierAllocated;
  516. var shortageQty = supplierNeedQty - supplierAllocated;
  517. if (shortageQty > 0)
  518. {
  519. var prCreated = rules.ShortageCreatePr && TryAddPurchaseRequest(purchaseRequests, demand, source, shortageQty, now, account, rules);
  520. AddDeliveryException(
  521. exceptions,
  522. demand,
  523. optTime,
  524. prCreated
  525. ? $"物料交货计划[{demand.ItemNum}:{FormatDate(demand.RequestDate)}]对应供应商[{source.SupplierName}]采购订单不足,已生成采购申请,后续待合并/转单/推送"
  526. : rules.ShortageCreatePr
  527. ? $"物料交货计划[{demand.ItemNum}:{FormatDate(demand.RequestDate)}]对应供应商[{source.SupplierName}]采购订单不足,需补充 {shortageQty:0.####};因物料或供应商主键缺失,未自动生成PR"
  528. : $"物料交货计划[{demand.ItemNum}:{FormatDate(demand.RequestDate)}]对应供应商[{source.SupplierName}]采购订单不足,需补充 {shortageQty:0.####};规则配置关闭自动生成PR",
  529. shortageQty);
  530. }
  531. }
  532. }
  533. if (schedules.Count == 0 && exceptions.Count == 0)
  534. {
  535. return new DeliveryScheduleGenerateResult
  536. {
  537. DemandCount = demands.Count,
  538. CandidateCount = 0,
  539. CreatedCount = 0,
  540. SkippedCount = skippedCount,
  541. RuleSnapshot = ruleSnapshot,
  542. Message = "没有可生成交货单的需求计划"
  543. };
  544. }
  545. try
  546. {
  547. _db.Ado.BeginTran();
  548. if (purchaseRequests.Count > 0 && rules.EnablePrMerge)
  549. {
  550. var mergeResult = _purchaseRequestMergeService.MergeGeneratedRequests(purchaseRequests);
  551. purchaseRequests = mergeResult.Requests;
  552. prMergeReducedCount = mergeResult.ReducedCount;
  553. }
  554. if (purchaseRequests.Count > 0)
  555. {
  556. await AssignPurchaseRequestNumbersAsync(purchaseRequests, account);
  557. }
  558. if (purchaseRequests.Count > 0)
  559. {
  560. purchaseOrderTransferResult = await _purchaseOrderTransferService.TransferGeneratedRequireGoodsAsync(purchaseRequests, account);
  561. }
  562. if (schedules.Count > 0)
  563. {
  564. var dsNumbersByKey = new Dictionary<string, string>();
  565. foreach (var group in schedules.GroupBy(x => x.Domain ?? string.Empty))
  566. {
  567. var groupRows = group.ToList();
  568. var dsNumbers = await _numberRuleService.NextBatchInCurrentTransactionAsync("M8", group.Key, groupRows.Count, account);
  569. if (dsNumbers.Count < groupRows.Count || dsNumbers.Any(string.IsNullOrWhiteSpace))
  570. throw Oops.Oh($"当前交货计划号生成失败,请检查交货计划编号规则维护。Domain={group.Key}");
  571. for (var index = 0; index < groupRows.Count; index++)
  572. {
  573. dsNumbersByKey[BuildScheduleTempKey(groupRows[index])] = dsNumbers[index].Trim();
  574. }
  575. }
  576. foreach (var schedule in schedules)
  577. {
  578. schedule.DsNum = dsNumbersByKey[BuildScheduleTempKey(schedule)];
  579. }
  580. await _db.Insertable(schedules).ExecuteCommandAsync();
  581. await UpsertDeliveryScheduleMdpAsync(schedules, now);
  582. }
  583. if (purchaseRequests.Count > 0)
  584. {
  585. await InsertPurchaseRequestsAsync(purchaseRequests);
  586. }
  587. if (exceptions.Count > 0)
  588. {
  589. await _db.Insertable(exceptions).ExecuteCommandAsync();
  590. }
  591. _db.Ado.CommitTran();
  592. }
  593. catch
  594. {
  595. _db.Ado.RollbackTran();
  596. throw;
  597. }
  598. return new DeliveryScheduleGenerateResult
  599. {
  600. DemandCount = demands.Count,
  601. CandidateCount = schedules.Count,
  602. CreatedCount = schedules.Count,
  603. ExceptionCount = exceptions.Count,
  604. PurchaseRequestCount = purchaseRequests.Count,
  605. PurchaseRequestMergeReducedCount = prMergeReducedCount,
  606. PurchaseOrderCount = purchaseOrderTransferResult.CreatedOrderCount,
  607. PurchaseOrderLineCount = purchaseOrderTransferResult.TransferredPrCount,
  608. SkippedCount = skippedCount,
  609. DsNums = schedules.Select(x => x.DsNum).Where(x => !string.IsNullOrWhiteSpace(x)).Take(20).ToList(),
  610. RuleSnapshot = ruleSnapshot,
  611. Message = schedules.Count > 0
  612. ? $"生成交货单成功,共生成 {schedules.Count} 条;自动生成PR {purchaseRequests.Count} 条;合并减少 {prMergeReducedCount} 条;生成DO/PO {purchaseOrderTransferResult.CreatedOrderCount} 单;异常 {exceptions.Count} 条"
  613. : $"未生成交货单,自动生成PR {purchaseRequests.Count} 条,合并减少 {prMergeReducedCount} 条,生成DO/PO {purchaseOrderTransferResult.CreatedOrderCount} 单,已记录异常 {exceptions.Count} 条"
  614. };
  615. }
  616. private async Task<DeliveryScheduleGenerateResult> GenerateDeliverySchedulesFromPurchaseOrdersAsync(string poNumber)
  617. {
  618. var account = _userManager.Account ?? "system";
  619. var pars = new List<SugarParameter>();
  620. var where = new List<string>
  621. {
  622. "IFNULL(m.Status,'') <> 'C'",
  623. "IFNULL(d.Status,'') <> 'C'",
  624. "(IFNULL(d.QtyOrded,0) - IFNULL(d.RctQty,0)) > 0",
  625. """
  626. NOT EXISTS (
  627. SELECT 1
  628. FROM srm_polist_ds ds
  629. WHERE ds.ponumber = d.PurOrd
  630. AND ds.poline = d.Line
  631. AND ds.isactive = 1
  632. AND IFNULL(ds.status,'') <> 'C'
  633. )
  634. """
  635. };
  636. var tenantId = ResolveTenantOrThrow();
  637. where.Add("m.tenant_id = @TenantId");
  638. pars.Add(new SugarParameter("@TenantId", tenantId));
  639. if (!string.IsNullOrWhiteSpace(poNumber))
  640. {
  641. where.Add("m.PurOrd = @PoNumber");
  642. pars.Add(new SugarParameter("@PoNumber", poNumber.Trim()));
  643. }
  644. var candidates = await _db.Ado.SqlQueryAsync<DeliveryScheduleCandidateRow>(
  645. $"""
  646. SELECT
  647. IFNULL(TRIM(m.Domain),'') AS Domain,
  648. d.RecID AS IcdsId,
  649. IFNULL(TRIM(d.PurOrd),'') AS PoNumber,
  650. IFNULL(d.Line,0) AS PoLine,
  651. IFNULL(TRIM(d.ItemNum),'') AS ItemNum,
  652. IFNULL(TRIM(d.UM),'') AS Um,
  653. IFNULL(TRIM(m.Buyer),'') AS PurGroup,
  654. IFNULL(TRIM(m.Supp),'') AS SupplierCode,
  655. IFNULL(TRIM(s.Name),'') AS Supplier,
  656. d.DueDate AS NeedDate,
  657. (IFNULL(d.QtyOrded,0) - IFNULL(d.RctQty,0)) AS RestQty,
  658. m.tenant_id AS TenantId
  659. FROM PurOrdMaster m
  660. INNER JOIN PurOrdDetail d ON m.RecID = d.PurOrdRecID
  661. LEFT JOIN ConsigneeAddressMaster s ON m.Supp = s.Address AND s.Typed = 'Supp'
  662. WHERE {string.Join(" AND ", where)}
  663. ORDER BY m.PurOrd, d.Line
  664. """,
  665. pars);
  666. if (candidates.Count == 0)
  667. {
  668. return new DeliveryScheduleGenerateResult
  669. {
  670. CandidateCount = 0,
  671. CreatedCount = 0,
  672. Message = string.IsNullOrWhiteSpace(poNumber)
  673. ? "无可生成交货单的采购行"
  674. : "当前采购单无可生成交货单的采购行"
  675. };
  676. }
  677. try
  678. {
  679. _db.Ado.BeginTran();
  680. var dsNumbersByKey = new Dictionary<string, string>();
  681. foreach (var group in candidates.GroupBy(x => x.Domain ?? string.Empty))
  682. {
  683. var groupRows = group.ToList();
  684. var dsNumbers = await _numberRuleService.NextBatchInCurrentTransactionAsync("M8", group.Key, groupRows.Count, account);
  685. if (dsNumbers.Count < groupRows.Count || dsNumbers.Any(string.IsNullOrWhiteSpace))
  686. throw Oops.Oh($"当前交货计划号生成失败,请检查交货计划编号规则维护。Domain={group.Key}");
  687. for (var index = 0; index < groupRows.Count; index++)
  688. {
  689. dsNumbersByKey[BuildCandidateKey(groupRows[index])] = dsNumbers[index].Trim();
  690. }
  691. }
  692. var now = DateTime.Now;
  693. var schedules = candidates.Select(row => new PolistDeliverySchedule
  694. {
  695. Id = YitIdHelper.NextId(),
  696. Domain = row.Domain ?? string.Empty,
  697. IcdsId = row.IcdsId,
  698. DsNum = dsNumbersByKey[BuildCandidateKey(row)],
  699. Status = "N",
  700. ItemNum = row.ItemNum ?? string.Empty,
  701. Um = row.Um ?? string.Empty,
  702. PurGroup = row.PurGroup ?? string.Empty,
  703. SupplierCode = row.SupplierCode ?? string.Empty,
  704. Supplier = row.Supplier ?? string.Empty,
  705. RequestDate = now,
  706. NeedDate = row.NeedDate,
  707. PoNumber = row.PoNumber ?? string.Empty,
  708. PoLine = row.PoLine,
  709. SchedQty = row.RestQty,
  710. LastSentQty = 0,
  711. SentQty = 0,
  712. RestQty = row.RestQty,
  713. CreateUser = account,
  714. UpdateUser = account,
  715. CreateTime = now,
  716. UpdateTime = now,
  717. Remarks = "由系统按采购行自动生成",
  718. IsActive = 1,
  719. ReturnQty = 0,
  720. TenantId = ResolveTenantOrThrow()
  721. }).ToList();
  722. var createdCount = await _db.Insertable(schedules).ExecuteCommandAsync();
  723. await UpsertDeliveryScheduleMdpAsync(schedules, now);
  724. _db.Ado.CommitTran();
  725. return new DeliveryScheduleGenerateResult
  726. {
  727. CandidateCount = candidates.Count,
  728. CreatedCount = createdCount,
  729. DsNums = schedules.Select(x => x.DsNum).Take(20).ToList(),
  730. Message = createdCount > 0
  731. ? $"生成交货单成功,共生成 {createdCount} 条"
  732. : "未生成交货单,请检查采购行是否已存在有效交货计划"
  733. };
  734. }
  735. catch
  736. {
  737. _db.Ado.RollbackTran();
  738. throw;
  739. }
  740. }
  741. private async Task UpsertDeliveryScheduleMdpAsync(List<PolistDeliverySchedule> schedules, DateTime now)
  742. {
  743. if (schedules.Count == 0) return;
  744. var batchId = $"S3_DELIVERY_{now:yyyyMMddHHmmss}";
  745. foreach (var schedule in schedules)
  746. {
  747. await _db.Ado.ExecuteCommandAsync(
  748. """
  749. INSERT INTO mdp_std_delivery_schedule
  750. (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)
  751. VALUES
  752. (@TenantId, 'AIDOP', @DsNum, @PoNumber, @PoLine, @ItemNum, @SupplierCode, @Supplier, @SchedQty, @SentQty, @RestQty, @ReturnQty, @RequestDate, @NeedDate, @SubmitDate, @LastSentDate, @Status, @DsNum, @BatchId, @Now)
  753. ON DUPLICATE KEY UPDATE
  754. po_no=VALUES(po_no),
  755. po_line=VALUES(po_line),
  756. item_code=VALUES(item_code),
  757. supplier_code=VALUES(supplier_code),
  758. supplier_name=VALUES(supplier_name),
  759. schedule_qty=VALUES(schedule_qty),
  760. sent_qty=VALUES(sent_qty),
  761. rest_qty=VALUES(rest_qty),
  762. return_qty=VALUES(return_qty),
  763. request_date=VALUES(request_date),
  764. need_date=VALUES(need_date),
  765. submit_date=VALUES(submit_date),
  766. last_sent_date=VALUES(last_sent_date),
  767. status=VALUES(status),
  768. sync_batch_id=VALUES(sync_batch_id),
  769. sync_time=VALUES(sync_time),
  770. update_time=CURRENT_TIMESTAMP
  771. """,
  772. new SugarParameter("@TenantId", ResolveScheduleTenant(schedule)),
  773. new SugarParameter("@DsNum", schedule.DsNum),
  774. new SugarParameter("@PoNumber", schedule.PoNumber),
  775. new SugarParameter("@PoLine", schedule.PoLine.ToString()),
  776. new SugarParameter("@ItemNum", schedule.ItemNum),
  777. new SugarParameter("@SupplierCode", schedule.SupplierCode),
  778. new SugarParameter("@Supplier", schedule.Supplier),
  779. new SugarParameter("@SchedQty", schedule.SchedQty),
  780. new SugarParameter("@SentQty", schedule.SentQty),
  781. new SugarParameter("@RestQty", schedule.RestQty),
  782. new SugarParameter("@ReturnQty", schedule.ReturnQty),
  783. new SugarParameter("@RequestDate", schedule.RequestDate),
  784. new SugarParameter("@NeedDate", schedule.NeedDate),
  785. new SugarParameter("@SubmitDate", schedule.SubmitDate),
  786. new SugarParameter("@LastSentDate", schedule.LastSentDate),
  787. new SugarParameter("@Status", schedule.Status),
  788. new SugarParameter("@BatchId", batchId),
  789. new SugarParameter("@Now", now));
  790. await _db.Ado.ExecuteCommandAsync(
  791. """
  792. INSERT INTO dwd_supplier_delivery
  793. (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)
  794. SELECT
  795. @TenantId,
  796. @StatDate,
  797. IFNULL(m.PurOrd, @PoNumber),
  798. CAST(IFNULL(d.Line, @PoLineInt) AS CHAR),
  799. IFNULL(m.Potype, ''),
  800. IFNULL(m.Supp, @SupplierCode),
  801. @Supplier,
  802. IFNULL(d.ItemNum, @ItemNum),
  803. IFNULL(i.Descr, ''),
  804. IFNULL(d.QtyOrded, @SchedQty),
  805. @SchedQty,
  806. @SentQty,
  807. IFNULL(d.QtyReceived, IFNULL(d.RctQty, 0)),
  808. @ReturnQty,
  809. @RestQty,
  810. d.DueDate,
  811. @NeedDate,
  812. @LastSentDate,
  813. CASE
  814. WHEN IFNULL(@Status, '') = 'C' THEN 'CANCELLED'
  815. WHEN IFNULL(@RestQty, 0) <= 0 THEN 'CLOSED'
  816. WHEN IFNULL(@SentQty, 0) > 0 THEN 'PARTIAL'
  817. ELSE 'OPEN'
  818. END,
  819. 'AIDOP',
  820. @BatchId,
  821. @Now
  822. FROM PurOrdMaster m
  823. JOIN PurOrdDetail d ON m.RecID = d.PurOrdRecID
  824. LEFT JOIN ItemMaster i ON d.ItemNum = i.ItemNum AND (m.tenant_id = i.tenant_id OR m.tenant_id IS NULL)
  825. WHERE m.PurOrd = @PoNumber AND d.Line = @PoLineInt
  826. LIMIT 1
  827. ON DUPLICATE KEY UPDATE
  828. supplier_code=VALUES(supplier_code),
  829. supplier_name=VALUES(supplier_name),
  830. item_code=VALUES(item_code),
  831. item_name=VALUES(item_name),
  832. order_qty=VALUES(order_qty),
  833. schedule_qty=VALUES(schedule_qty),
  834. delivery_qty=VALUES(delivery_qty),
  835. receipt_qty=VALUES(receipt_qty),
  836. return_qty=VALUES(return_qty),
  837. remaining_qty=VALUES(remaining_qty),
  838. due_date=VALUES(due_date),
  839. need_date=VALUES(need_date),
  840. last_delivery_date=VALUES(last_delivery_date),
  841. delivery_status=VALUES(delivery_status),
  842. sync_batch_id=VALUES(sync_batch_id),
  843. calc_time=VALUES(calc_time),
  844. update_time=CURRENT_TIMESTAMP
  845. """,
  846. new SugarParameter("@TenantId", ResolveScheduleTenant(schedule)),
  847. new SugarParameter("@StatDate", now.Date),
  848. new SugarParameter("@PoNumber", schedule.PoNumber),
  849. new SugarParameter("@PoLineInt", schedule.PoLine),
  850. new SugarParameter("@SupplierCode", schedule.SupplierCode),
  851. new SugarParameter("@Supplier", schedule.Supplier),
  852. new SugarParameter("@ItemNum", schedule.ItemNum),
  853. new SugarParameter("@SchedQty", schedule.SchedQty),
  854. new SugarParameter("@SentQty", schedule.SentQty),
  855. new SugarParameter("@ReturnQty", schedule.ReturnQty),
  856. new SugarParameter("@RestQty", schedule.RestQty),
  857. new SugarParameter("@NeedDate", schedule.NeedDate),
  858. new SugarParameter("@LastSentDate", schedule.LastSentDate),
  859. new SugarParameter("@Status", schedule.Status),
  860. new SugarParameter("@BatchId", batchId),
  861. new SugarParameter("@Now", now));
  862. }
  863. }
  864. private async Task<Dictionary<long, decimal>> QueryExistingScheduleUsagesAsync(List<long> demandIds)
  865. {
  866. if (demandIds.Count == 0) return new Dictionary<long, decimal>();
  867. var pars = new List<SugarParameter>();
  868. var inClause = BuildInClause(demandIds.Select(x => x.ToString()).ToList(), "DemandId", pars);
  869. var tenantWhere = " AND tenant_id = @TenantId";
  870. pars.Add(new SugarParameter("@TenantId", ResolveTenantOrThrow()));
  871. var rows = await _db.Ado.SqlQueryAsync<ExistingScheduleUsageRow>(
  872. $"""
  873. SELECT
  874. icdsid AS IcdsId,
  875. SUM(
  876. CASE
  877. WHEN IFNULL(status,'') = 'C' THEN IFNULL(sentqty,0)
  878. WHEN IFNULL(schedqty,0) >= IFNULL(sentqty,0) THEN IFNULL(schedqty,0)
  879. ELSE IFNULL(sentqty,0)
  880. END
  881. ) AS UsedQty
  882. FROM srm_polist_ds
  883. WHERE isactive = 1 AND icdsid IN ({inClause}){tenantWhere}
  884. GROUP BY icdsid
  885. """,
  886. pars);
  887. return rows.ToDictionary(x => x.IcdsId, x => x.UsedQty);
  888. }
  889. private async Task<List<SourceListRow>> QuerySourceListsAsync(List<string> itemNums)
  890. {
  891. if (itemNums.Count == 0) return new List<SourceListRow>();
  892. var pars = new List<SugarParameter>();
  893. var where = new List<string>
  894. {
  895. "IFNULL(sp.IsDeleted,0)=0",
  896. "IFNULL(sp.supplier_number,'') <> ''",
  897. "IFNULL(sp.quota_rate,0) > 0",
  898. // 复用 srm_purchase.is_active 统一启用口径(与 UniversalSourceListService / Controller 同源),避免各自解释分裂。
  899. AdoS0SrmPurchaseIsActive.EnabledSql("sp.is_active")
  900. };
  901. var tenantId = ResolveTenantOrThrow();
  902. where.Add("sp.tenant_id = @TenantId");
  903. pars.Add(new SugarParameter("@TenantId", tenantId));
  904. var inClause = BuildInClause(itemNums, "ItemNum", pars);
  905. where.Add($"sp.number IN ({inClause})");
  906. return await _db.Ado.SqlQueryAsync<SourceListRow>(
  907. $"""
  908. SELECT
  909. IFNULL(TRIM(sp.number),'') AS ItemNum,
  910. IFNULL(TRIM(sp.supplier_number),'') AS SupplierCode,
  911. IFNULL(TRIM(sp.supplier_name),'') AS SupplierName,
  912. IFNULL(TRIM(sp.supplier_type),'') AS SupplierType,
  913. IFNULL(sp.quota_rate,0) AS QuotaRate,
  914. IFNULL(sp.packaging_qty,0) AS PackagingQty,
  915. IFNULL(sp.lead_time,0) AS LeadTime,
  916. IFNULL(sp.IsRequireGoods,0) AS IsRequireGoods,
  917. sp.supplier_id AS SupplierId,
  918. IFNULL(TRIM(sp.order_rector_name),'') AS OrderRectorName,
  919. IFNULL(TRIM(sp.order_rector_num),'') AS OrderRectorNum,
  920. IFNULL(TRIM(sp.purchase_unit),'') AS PurchaseUnit,
  921. sp.currency_type AS CurrencyType
  922. FROM srm_purchase sp
  923. WHERE {string.Join(" AND ", where)}
  924. ORDER BY sp.number, IFNULL(sp.quota_priority, 999999), sp.quota_rate DESC
  925. """,
  926. pars);
  927. }
  928. private async Task<List<PurchaseOrderAvailableRow>> QueryAvailablePurchaseOrderLinesAsync(
  929. List<string> itemNums,
  930. List<string> supplierCodes,
  931. S3DeliveryGenerateRuleOptions rules)
  932. {
  933. var pars = new List<SugarParameter>();
  934. var poBuyerCodesClause = BuildInClause(rules.PoBuyerCodes, "PoBuyerCode", pars);
  935. var doUsagesClause = BuildInClause(rules.DoUsages, "DoUsage", pars);
  936. var where = new List<string>
  937. {
  938. "IFNULL(m.Status,'') <> 'C'",
  939. "IFNULL(d.Status,'') <> 'C'",
  940. "IFNULL(d.ItemNum,'') <> ''",
  941. "IFNULL(m.Supp,'') <> ''",
  942. "(IFNULL(d.QtyOrded,0) - IFNULL(d.RctQty,0)) > 0",
  943. """
  944. (
  945. (m.PurOrd NOT LIKE 'DO%' AND IFNULL(m.Buyer,'') IN ({0}))
  946. OR
  947. (m.PurOrd LIKE 'DO%' AND IFNULL(m.USAGE,'') IN ({1}))
  948. )
  949. """.Replace("{0}", poBuyerCodesClause).Replace("{1}", doUsagesClause)
  950. };
  951. var tenantId = ResolveTenantOrThrow();
  952. where.Add("m.tenant_id = @TenantId");
  953. pars.Add(new SugarParameter("@TenantId", tenantId));
  954. where.Add($"d.ItemNum IN ({BuildInClause(itemNums, "PoItemNum", pars)})");
  955. where.Add($"m.Supp IN ({BuildInClause(supplierCodes, "SupplierCode", pars)})");
  956. return await _db.Ado.SqlQueryAsync<PurchaseOrderAvailableRow>(
  957. $"""
  958. SELECT *
  959. FROM (
  960. SELECT
  961. IFNULL(TRIM(m.Domain),'') AS Domain,
  962. IFNULL(TRIM(m.PurOrd),'') AS PoNumber,
  963. IFNULL(d.Line,0) AS PoLine,
  964. d.RecID AS DetailRecId,
  965. IFNULL(TRIM(d.ItemNum),'') AS ItemNum,
  966. IFNULL(TRIM(d.UM),'') AS Um,
  967. IFNULL(TRIM(m.Buyer),'') AS PurGroup,
  968. IFNULL(TRIM(m.Supp),'') AS SupplierCode,
  969. IFNULL(TRIM(s.Name),'') AS SupplierName,
  970. d.DueDate AS DueDate,
  971. IFNULL(d.QtyOrded,0) AS OrderQty,
  972. IFNULL(d.RctQty,0) AS ReceiptQty,
  973. m.tenant_id AS TenantId,
  974. (
  975. IFNULL(d.QtyOrded,0)
  976. - IFNULL(d.RctQty,0)
  977. - IFNULL((
  978. SELECT SUM(
  979. CASE
  980. WHEN IFNULL(ds.status,'') = 'C' THEN IFNULL(ds.sentqty,0)
  981. WHEN IFNULL(ds.schedqty,0) >= IFNULL(ds.sentqty,0) THEN IFNULL(ds.schedqty,0)
  982. ELSE IFNULL(ds.sentqty,0)
  983. END)
  984. FROM srm_polist_ds ds
  985. WHERE ds.ponumber = d.PurOrd
  986. AND ds.poline = d.Line
  987. AND ds.itemnum = d.ItemNum
  988. AND ds.isactive = 1
  989. ), 0)
  990. ) AS AvailableQty
  991. FROM PurOrdMaster m
  992. INNER JOIN PurOrdDetail d ON m.RecID = d.PurOrdRecID
  993. LEFT JOIN ConsigneeAddressMaster s ON m.Supp = s.Address AND s.Typed = 'Supp'
  994. WHERE {string.Join(" AND ", where)}
  995. ) t
  996. WHERE t.AvailableQty > 0
  997. ORDER BY t.ItemNum, t.SupplierCode, CASE WHEN t.PoNumber LIKE 'DO%' THEN 1 ELSE 0 END, t.DueDate, t.DetailRecId
  998. """,
  999. pars);
  1000. }
  1001. private static decimal AllocateDemandToPurchaseLines(
  1002. DemandScheduleCandidateRow demand,
  1003. SourceListRow source,
  1004. List<PurchaseOrderAvailableRow> poLines,
  1005. Dictionary<string, decimal> allocatedByPoLine,
  1006. decimal supplierNeedQty,
  1007. decimal remainingDemand,
  1008. List<PolistDeliverySchedule> schedules,
  1009. DateTime now,
  1010. string account,
  1011. S3DeliveryGenerateRuleOptions rules)
  1012. {
  1013. var supplierAllocated = 0m;
  1014. var lines = poLines
  1015. .Where(x =>
  1016. string.Equals(x.ItemNum, demand.ItemNum, StringComparison.OrdinalIgnoreCase)
  1017. && string.Equals(x.SupplierCode, source.SupplierCode, StringComparison.OrdinalIgnoreCase))
  1018. .ToList();
  1019. foreach (var line in lines)
  1020. {
  1021. if (supplierAllocated >= supplierNeedQty) break;
  1022. var key = BuildPoLineKey(line);
  1023. var runAllocated = allocatedByPoLine.TryGetValue(key, out var used) ? used : 0m;
  1024. var availableQty = line.AvailableQty - runAllocated;
  1025. if (availableQty <= 0) continue;
  1026. var requiredQty = supplierNeedQty - supplierAllocated;
  1027. var qty = Math.Min(availableQty, requiredQty);
  1028. if (rules.EnablePackagingRoundUp && source.PackagingQty > 0 && availableQty > qty)
  1029. {
  1030. var roundedQty = Math.Ceiling(qty / source.PackagingQty) * source.PackagingQty;
  1031. if (roundedQty <= availableQty && roundedQty <= remainingDemand - supplierAllocated)
  1032. {
  1033. qty = roundedQty;
  1034. }
  1035. }
  1036. if (qty <= 0) continue;
  1037. allocatedByPoLine[key] = runAllocated + qty;
  1038. supplierAllocated += qty;
  1039. schedules.Add(new PolistDeliverySchedule
  1040. {
  1041. Id = YitIdHelper.NextId(),
  1042. Domain = line.Domain ?? string.Empty,
  1043. IcdsId = demand.IcdsId,
  1044. DsNum = string.Empty,
  1045. Status = "N",
  1046. ItemNum = demand.ItemNum ?? string.Empty,
  1047. Um = line.Um ?? demand.Um ?? string.Empty,
  1048. PurGroup = line.PurGroup ?? string.Empty,
  1049. SupplierCode = source.SupplierCode ?? line.SupplierCode ?? string.Empty,
  1050. Supplier = string.IsNullOrWhiteSpace(source.SupplierName) ? line.SupplierName ?? string.Empty : source.SupplierName,
  1051. RequestDate = demand.ArrivalDate ?? now,
  1052. NeedDate = demand.RequestDate,
  1053. PoNumber = line.PoNumber ?? string.Empty,
  1054. PoLine = line.PoLine,
  1055. SchedQty = qty,
  1056. LastSentQty = 0,
  1057. SentQty = 0,
  1058. RestQty = qty,
  1059. CreateUser = account,
  1060. UpdateUser = account,
  1061. CreateTime = now,
  1062. UpdateTime = now,
  1063. Remarks = "由系统按需求计划、货源配额和采购单可用量自动生成",
  1064. IsActive = 1,
  1065. ReturnQty = 0,
  1066. TenantId = demand.TenantId ?? line.TenantId
  1067. });
  1068. }
  1069. return supplierAllocated;
  1070. }
  1071. private static void AddDeliveryException(
  1072. List<DeliveryExceptionMaster> exceptions,
  1073. DemandScheduleCandidateRow demand,
  1074. DateTime optTime,
  1075. string remark,
  1076. decimal needQty)
  1077. {
  1078. exceptions.Add(new DeliveryExceptionMaster
  1079. {
  1080. RecID = YitIdHelper.NextId(),
  1081. Domain = demand.TenantId?.ToString() ?? string.Empty,
  1082. IcdsId = demand.IcdsId,
  1083. OptTime = optTime,
  1084. ItemNum = demand.ItemNum,
  1085. Remark = remark,
  1086. NeedQty = needQty,
  1087. TenantId = demand.TenantId
  1088. });
  1089. }
  1090. private static bool TryAddPurchaseRequest(
  1091. List<PurchaseRequestMain> purchaseRequests,
  1092. DemandScheduleCandidateRow demand,
  1093. SourceListRow source,
  1094. decimal shortageQty,
  1095. DateTime now,
  1096. string account,
  1097. S3DeliveryGenerateRuleOptions rules)
  1098. {
  1099. if (shortageQty <= 0) return false;
  1100. if (!demand.IcItemId.HasValue || demand.IcItemId.Value <= 0) return false;
  1101. if (!source.SupplierId.HasValue || source.SupplierId.Value <= 0) return false;
  1102. var arriveDate = demand.ArrivalDate ?? demand.RequestDate ?? now.Date;
  1103. var leadDays = source.LeadTime > 0 ? (int)Math.Ceiling(source.LeadTime) : rules.DefaultLeadDays;
  1104. if (leadDays < 0) leadDays = 0;
  1105. var sendDate = arriveDate.Date.AddDays(-leadDays);
  1106. if (sendDate < now.Date) sendDate = now.Date;
  1107. purchaseRequests.Add(new PurchaseRequestMain
  1108. {
  1109. Id = YitIdHelper.NextId(),
  1110. PrPurchaseId = source.SupplierId.Value,
  1111. PrPurchaseNumber = source.SupplierCode,
  1112. PrPurchaseName = source.SupplierName,
  1113. PrPurchaser = source.OrderRectorName,
  1114. PrPurchaserNum = source.OrderRectorNum,
  1115. PrRqty = shortageQty,
  1116. PrAqty = shortageQty,
  1117. PrSqty = shortageQty,
  1118. IcitemId = demand.IcItemId.Value,
  1119. IcitemName = string.IsNullOrWhiteSpace(demand.ItemName) ? demand.ItemNum : demand.ItemName,
  1120. PrSsendDate = sendDate,
  1121. PrSarriveDate = arriveDate,
  1122. PrUnit = string.IsNullOrWhiteSpace(source.PurchaseUnit) ? demand.Um : source.PurchaseUnit,
  1123. State = 1,
  1124. PrType = 3,
  1125. CurrencyType = source.CurrencyType ?? 1,
  1126. CreateBy = null,
  1127. CreateByName = account,
  1128. CreateTime = now,
  1129. UpdateBy = null,
  1130. UpdateByName = account,
  1131. UpdateTime = now,
  1132. TenantId = demand.TenantId ?? 0,
  1133. FactoryId = demand.FactoryId,
  1134. OrgId = demand.FactoryId,
  1135. IsDeleted = false,
  1136. CompanyId = demand.CompanyId ?? 1000,
  1137. IsRequireGoods = source.IsRequireGoods,
  1138. SupplierType = source.SupplierType
  1139. });
  1140. return true;
  1141. }
  1142. private async Task AssignPurchaseRequestNumbersAsync(List<PurchaseRequestMain> purchaseRequests, string account)
  1143. {
  1144. foreach (var group in purchaseRequests.GroupBy(x => x.TenantId.ToString()))
  1145. {
  1146. var groupRows = group.ToList();
  1147. var prNumbers = await _numberRuleService.NextBatchInCurrentTransactionAsync("PR", group.Key, groupRows.Count, account);
  1148. if (prNumbers.Count < groupRows.Count || prNumbers.Any(string.IsNullOrWhiteSpace))
  1149. throw Oops.Oh($"当前采购申请号生成失败,请检查采购申请编号规则维护。Domain={group.Key}");
  1150. for (var index = 0; index < groupRows.Count; index++)
  1151. {
  1152. groupRows[index].PrBillNo = prNumbers[index].Trim();
  1153. }
  1154. }
  1155. }
  1156. private async Task InsertPurchaseRequestsAsync(List<PurchaseRequestMain> purchaseRequests)
  1157. {
  1158. if (purchaseRequests.Count == 0) return;
  1159. foreach (var pr in purchaseRequests)
  1160. {
  1161. await _db.Ado.ExecuteCommandAsync(
  1162. """
  1163. INSERT INTO srm_pr_main
  1164. (Id,pr_billno,pr_mono,entity_id,pr_purchaseid,pr_purchasenumber,pr_purchasename,pr_purchaser,pr_purchaser_num,
  1165. pr_rqty,pr_aqty,pr_sqty,icitem_id,icitem_name,pr_ssend_date,pr_sarrive_date,pr_unit,state,pr_type,currencytype,
  1166. create_by,create_by_name,create_time,update_by,update_by_name,update_time,tenant_id,factory_id,org_id,IsDeleted,company_id,IsRequireGoods,supplier_type)
  1167. VALUES
  1168. (@Id,@PrBillNo,@PrMono,@EntityId,@PrPurchaseId,@PrPurchaseNumber,@PrPurchaseName,@PrPurchaser,@PrPurchaserNum,
  1169. @PrRqty,@PrAqty,@PrSqty,@IcitemId,@IcitemName,@PrSsendDate,@PrSarriveDate,@PrUnit,@State,@PrType,@CurrencyType,
  1170. @CreateBy,@CreateByName,@CreateTime,@UpdateBy,@UpdateByName,@UpdateTime,@TenantId,@FactoryId,@OrgId,@IsDeleted,@CompanyId,@IsRequireGoods,@SupplierType)
  1171. """,
  1172. new SugarParameter("@Id", pr.Id),
  1173. new SugarParameter("@PrBillNo", pr.PrBillNo),
  1174. new SugarParameter("@PrMono", pr.PrMono ?? (object)DBNull.Value),
  1175. new SugarParameter("@EntityId", pr.EntityId ?? (object)DBNull.Value),
  1176. new SugarParameter("@PrPurchaseId", pr.PrPurchaseId),
  1177. new SugarParameter("@PrPurchaseNumber", pr.PrPurchaseNumber),
  1178. new SugarParameter("@PrPurchaseName", pr.PrPurchaseName),
  1179. new SugarParameter("@PrPurchaser", pr.PrPurchaser),
  1180. new SugarParameter("@PrPurchaserNum", pr.PrPurchaserNum),
  1181. new SugarParameter("@PrRqty", pr.PrRqty),
  1182. new SugarParameter("@PrAqty", pr.PrAqty),
  1183. new SugarParameter("@PrSqty", pr.PrSqty),
  1184. new SugarParameter("@IcitemId", pr.IcitemId),
  1185. new SugarParameter("@IcitemName", pr.IcitemName),
  1186. new SugarParameter("@PrSsendDate", pr.PrSsendDate),
  1187. new SugarParameter("@PrSarriveDate", pr.PrSarriveDate),
  1188. new SugarParameter("@PrUnit", pr.PrUnit),
  1189. new SugarParameter("@State", pr.State),
  1190. new SugarParameter("@PrType", pr.PrType),
  1191. new SugarParameter("@CurrencyType", pr.CurrencyType),
  1192. new SugarParameter("@CreateBy", pr.CreateBy),
  1193. new SugarParameter("@CreateByName", pr.CreateByName),
  1194. new SugarParameter("@CreateTime", pr.CreateTime),
  1195. new SugarParameter("@UpdateBy", pr.UpdateBy),
  1196. new SugarParameter("@UpdateByName", pr.UpdateByName),
  1197. new SugarParameter("@UpdateTime", pr.UpdateTime),
  1198. new SugarParameter("@TenantId", pr.TenantId),
  1199. new SugarParameter("@FactoryId", pr.FactoryId),
  1200. new SugarParameter("@OrgId", pr.OrgId),
  1201. new SugarParameter("@IsDeleted", pr.IsDeleted ? 1 : 0),
  1202. new SugarParameter("@CompanyId", pr.CompanyId),
  1203. new SugarParameter("@IsRequireGoods", pr.IsRequireGoods),
  1204. new SugarParameter("@SupplierType", pr.SupplierType));
  1205. }
  1206. }
  1207. private static string BuildInClause(List<string> values, string prefix, List<SugarParameter> pars)
  1208. {
  1209. if (values.Count == 0) return "NULL";
  1210. var names = new List<string>();
  1211. for (var i = 0; i < values.Count; i++)
  1212. {
  1213. var name = $"@{prefix}{i}";
  1214. names.Add(name);
  1215. pars.Add(new SugarParameter(name, values[i]));
  1216. }
  1217. return string.Join(",", names);
  1218. }
  1219. private static string BuildPoLineKey(PurchaseOrderAvailableRow row)
  1220. {
  1221. return $"{row.PoNumber}|{row.PoLine}|{row.ItemNum}|{row.DetailRecId}";
  1222. }
  1223. private static string BuildScheduleTempKey(PolistDeliverySchedule row)
  1224. {
  1225. return $"{row.Id}|{row.PoNumber}|{row.PoLine}|{row.IcdsId}";
  1226. }
  1227. private static string FormatDate(DateTime? value) => value?.ToString("yyyy-MM-dd") ?? "";
  1228. private static List<long> ParseIds(string ids)
  1229. {
  1230. return ids.Split(',', StringSplitOptions.RemoveEmptyEntries)
  1231. .Select(x => long.TryParse(x.Trim(), out var id) ? id : 0)
  1232. .Where(x => x > 0)
  1233. .Distinct()
  1234. .ToList();
  1235. }
  1236. private static string BuildCandidateKey(DeliveryScheduleCandidateRow row)
  1237. {
  1238. return $"{row.Domain}|{row.PoNumber}|{row.PoLine}|{row.IcdsId}";
  1239. }
  1240. private static string BuildListOrderBy(string? sortField, string? sortOrder)
  1241. {
  1242. var dir = string.Equals(sortOrder, "asc", StringComparison.OrdinalIgnoreCase) ? "ASC" : "DESC";
  1243. return sortField?.ToLowerInvariant() switch
  1244. {
  1245. "dsnum" => $"ss.dsnum {dir}",
  1246. "status" => $"ss.status {dir}",
  1247. "itemnum" => $"ss.itemnum {dir}",
  1248. "requestdate" => $"ss.requestdate {dir}",
  1249. "needdate" => $"ss.needdate {dir}",
  1250. "schedqty" => $"ss.schedqty {dir}",
  1251. "sentqty" => $"ss.sentqty {dir}",
  1252. "restqty" => $"ss.restqty {dir}",
  1253. "submitdate" => $"ss.submitdate {dir}",
  1254. "ponumber" => $"ss.ponumber {dir}",
  1255. _ => "ss.dsnum DESC"
  1256. };
  1257. }
  1258. private static string BuildPurchaseOrderOrderBy(string? sortField, string? sortOrder)
  1259. {
  1260. var dir = string.Equals(sortOrder, "asc", StringComparison.OrdinalIgnoreCase) ? "ASC" : "DESC";
  1261. return sortField?.ToLowerInvariant() switch
  1262. {
  1263. "purord" => $"m.PurOrd {dir}",
  1264. "line" => $"d.Line {dir}",
  1265. "itemnum" => $"d.ItemNum {dir}",
  1266. "supp" => $"m.Supp {dir}",
  1267. "name" => $"s.Name {dir}",
  1268. "duedate" => $"d.DueDate {dir}",
  1269. "qtyorded" => $"d.QtyOrded {dir}",
  1270. "buyer" => $"m.Buyer {dir}",
  1271. _ => "m.PurOrd DESC, d.Line ASC"
  1272. };
  1273. }
  1274. private sealed class DeliveryScheduleListRow
  1275. {
  1276. public long Id { get; set; }
  1277. public string? Domain { get; set; }
  1278. public long? IcdsId { get; set; }
  1279. public string? DsNum { get; set; }
  1280. public string? Status { get; set; }
  1281. public string? ItemNum { get; set; }
  1282. public string? Descr { get; set; }
  1283. public string? Um { get; set; }
  1284. public string? PurGroup { get; set; }
  1285. public string? SupplierCode { get; set; }
  1286. public string? Supplier { get; set; }
  1287. public DateTime? SubmitDate { get; set; }
  1288. public DateTime? RequestDate { get; set; }
  1289. public DateTime? NeedDate { get; set; }
  1290. public string? PoNumber { get; set; }
  1291. public int? PoLine { get; set; }
  1292. public decimal? SchedQty { get; set; }
  1293. public DateTime? LastSentDate { get; set; }
  1294. public decimal? LastSentQty { get; set; }
  1295. public decimal? SentQty { get; set; }
  1296. public decimal? RestQty { get; set; }
  1297. }
  1298. private sealed class PurchaseOrderSelectRow
  1299. {
  1300. public string? PurOrd { get; set; }
  1301. public int? Line { get; set; }
  1302. public string? ItemNum { get; set; }
  1303. public string? Um { get; set; }
  1304. public string? Supp { get; set; }
  1305. public string? Name { get; set; }
  1306. public DateTime? DueDate { get; set; }
  1307. public decimal? QtyOrded { get; set; }
  1308. public string? Buyer { get; set; }
  1309. }
  1310. private sealed class DeliveryScheduleGenerateResult
  1311. {
  1312. public int DemandCount { get; set; }
  1313. public int CandidateCount { get; set; }
  1314. public int CreatedCount { get; set; }
  1315. public int ExceptionCount { get; set; }
  1316. public int PurchaseRequestCount { get; set; }
  1317. public int PurchaseRequestMergeReducedCount { get; set; }
  1318. public int PurchaseOrderCount { get; set; }
  1319. public int PurchaseOrderLineCount { get; set; }
  1320. public int QadTrackingCount { get; set; }
  1321. public int SkippedCount { get; set; }
  1322. public List<string> DsNums { get; set; } = new();
  1323. public RuleConfigSnapshot<S3DeliveryGenerateRuleOptions>? RuleSnapshot { get; set; }
  1324. public string Message { get; set; } = string.Empty;
  1325. }
  1326. private sealed class DeliveryScheduleCandidateRow
  1327. {
  1328. public string? Domain { get; set; }
  1329. public long IcdsId { get; set; }
  1330. public string? PoNumber { get; set; }
  1331. public int PoLine { get; set; }
  1332. public string? ItemNum { get; set; }
  1333. public string? Um { get; set; }
  1334. public string? PurGroup { get; set; }
  1335. public string? SupplierCode { get; set; }
  1336. public string? Supplier { get; set; }
  1337. public DateTime? NeedDate { get; set; }
  1338. public decimal RestQty { get; set; }
  1339. public long? TenantId { get; set; }
  1340. }
  1341. private sealed class DemandScheduleCandidateRow
  1342. {
  1343. public long IcdsId { get; set; }
  1344. public string? ItemNum { get; set; }
  1345. public DateTime? RequestDate { get; set; }
  1346. public DateTime? ArrivalDate { get; set; }
  1347. public decimal DemandQty { get; set; }
  1348. public long? TenantId { get; set; }
  1349. public long? FactoryId { get; set; }
  1350. public long? CompanyId { get; set; }
  1351. public string? PurMfg { get; set; }
  1352. public string? Um { get; set; }
  1353. public string? ItemName { get; set; }
  1354. public long? IcItemId { get; set; }
  1355. }
  1356. private sealed class ExistingScheduleUsageRow
  1357. {
  1358. public long IcdsId { get; set; }
  1359. public decimal UsedQty { get; set; }
  1360. }
  1361. private sealed class SourceListRow
  1362. {
  1363. public string? ItemNum { get; set; }
  1364. public string? SupplierCode { get; set; }
  1365. public string? SupplierName { get; set; }
  1366. public string? SupplierType { get; set; }
  1367. public decimal QuotaRate { get; set; }
  1368. public decimal PackagingQty { get; set; }
  1369. public decimal LeadTime { get; set; }
  1370. public int IsRequireGoods { get; set; }
  1371. public long? SupplierId { get; set; }
  1372. public string? OrderRectorName { get; set; }
  1373. public string? OrderRectorNum { get; set; }
  1374. public string? PurchaseUnit { get; set; }
  1375. public long? CurrencyType { get; set; }
  1376. }
  1377. private sealed class PurchaseOrderAvailableRow
  1378. {
  1379. public string? Domain { get; set; }
  1380. public string? PoNumber { get; set; }
  1381. public int PoLine { get; set; }
  1382. public long DetailRecId { get; set; }
  1383. public string? ItemNum { get; set; }
  1384. public string? Um { get; set; }
  1385. public string? PurGroup { get; set; }
  1386. public string? SupplierCode { get; set; }
  1387. public string? SupplierName { get; set; }
  1388. public DateTime? DueDate { get; set; }
  1389. public decimal OrderQty { get; set; }
  1390. public decimal ReceiptQty { get; set; }
  1391. public long? TenantId { get; set; }
  1392. public decimal AvailableQty { get; set; }
  1393. }
  1394. }