CreatePickBillService.cs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576
  1. using System.Text.Json;
  2. using Admin.NET.Plugin.AiDOP.DataPlatform.Executors;
  3. using Admin.NET.Plugin.AiDOP.DataPlatform.Sequence;
  4. using Admin.NET.Plugin.AiDOP.Entity.DataPlatform;
  5. using Admin.NET.Plugin.AiDOP.WorkOrder;
  6. using Microsoft.Extensions.Logging;
  7. using SqlSugar;
  8. namespace Admin.NET.Plugin.AiDOP.DataPlatform.Wms;
  9. /// <summary>
  10. /// WP4 · 下达建领料单。移植基准:旧 DOP ResourceExamineAppService.CreatePickBill / GenerateNbr。
  11. /// <list type="bullet">
  12. /// <item>本库更新工单状态;165 经 Outbox 回写 Status/Batch(及里程碑工序 Status→r)</item>
  13. /// <item>NbrMaster/NbrDetail 在 165 直写(需 identity RecID 回填明细 NbrRecID,与旧 UoW 一致;单号走 WP2)</item>
  14. /// <item>不做 MobileTask / QadTracking / 外协 PO / 库存占用(任务书本轮裁剪)</item>
  15. /// <item>D1:本动作不写 WorkOrdRouting C→W</item>
  16. /// </list>
  17. /// </summary>
  18. [ApiDescriptionSettings(Order = 327, Description = "下达建领料单")]
  19. [Route("api/aidop/wms-pick")]
  20. [AllowAnonymous]
  21. [NonUnify]
  22. public class CreatePickBillService : IDynamicApiController, ITransient
  23. {
  24. public const string TargetSource = "DOPDEMORQ_SQLSERVER";
  25. private const string NbrTypeBill = "SM";
  26. private const string NbrTypeAllocate = "sm";
  27. private readonly ISqlSugarClient _db;
  28. private readonly MdpSourceScopeFactory _scopeFactory;
  29. private readonly INbrSequenceService _seq;
  30. private readonly KeeperResolveService _keeper;
  31. private readonly MdpOutboxEnqueueService _enqueue;
  32. private readonly ILogger<CreatePickBillService> _logger;
  33. public CreatePickBillService(
  34. ISqlSugarClient db,
  35. MdpSourceScopeFactory scopeFactory,
  36. INbrSequenceService seq,
  37. KeeperResolveService keeper,
  38. MdpOutboxEnqueueService enqueue,
  39. ILogger<CreatePickBillService> logger)
  40. {
  41. _db = db;
  42. _scopeFactory = scopeFactory;
  43. _seq = seq;
  44. _keeper = keeper;
  45. _enqueue = enqueue;
  46. _logger = logger;
  47. }
  48. public sealed class CreatePickBillInput
  49. {
  50. /// <summary>工单号,逗号分隔;空则按 OrdDate 窗口取 Status=p</summary>
  51. public string? WorkOrd { get; set; }
  52. public string Domain { get; set; } = "8010";
  53. public string UserAccount { get; set; } = "aidop";
  54. public long TenantId { get; set; }
  55. }
  56. [DisplayName("下达生成领料单")]
  57. [HttpPost("create")]
  58. public async Task<object> Create([FromBody] CreatePickBillInput input, CancellationToken ct = default)
  59. {
  60. if (input == null) throw Oops.Oh("body 不能为空");
  61. var domain = string.IsNullOrWhiteSpace(input.Domain) ? "8010" : input.Domain.Trim();
  62. var user = string.IsNullOrWhiteSpace(input.UserAccount) ? "aidop" : input.UserAccount.Trim();
  63. var masters = await LoadCandidatesAsync(domain, input.WorkOrd, ct);
  64. if (masters.Count == 0)
  65. return new { ok = false, message = "没有需要下达的工单。" };
  66. var ss = await _scopeFactory.GetScopeAsync(TargetSource, ct);
  67. var already = await ss.Ado.SqlQueryAsync<string>(
  68. "SELECT DISTINCT WorkOrd FROM NbrMaster WHERE Domain=@d AND Type=@t",
  69. new SugarParameter("@d", domain),
  70. new SugarParameter("@t", NbrTypeBill));
  71. var alreadySet = new HashSet<string>(already.Where(x => !string.IsNullOrWhiteSpace(x)), StringComparer.OrdinalIgnoreCase);
  72. masters = masters.Where(m => !alreadySet.Contains(m.WorkOrd ?? "")).ToList();
  73. if (masters.Count == 0)
  74. return new { ok = false, message = "当前选择的工单包含已生成领料单。" };
  75. var workOrds = masters.Select(m => m.WorkOrd!).Distinct().ToList();
  76. var details = await _db.Queryable<WorkOrdDetail>()
  77. .Where(d => d.IsActive && workOrds.Contains(d.WorkOrd!))
  78. .ToListAsync(ct);
  79. if (details.Count == 0)
  80. return new { ok = false, message = "工单无物料明细,无法建领料单。" };
  81. var itemNums = details.Select(d => d.ItemNum!).Where(x => !string.IsNullOrWhiteSpace(x)).Distinct().ToList();
  82. var itemMap = new Dictionary<string, ItemLite>(StringComparer.OrdinalIgnoreCase);
  83. if (itemNums.Count > 0)
  84. {
  85. // Universal.ItemMaster 实体列与本库不完全一致,只取建单所需字段
  86. var inSql = string.Join(",", itemNums.Select((_, k) => "@i" + k));
  87. var pars = itemNums.Select((n, k) => new SugarParameter("@i" + k, n)).ToArray();
  88. var items = await _db.Ado.SqlQueryAsync<ItemLite>(
  89. $"SELECT ItemNum, Descr, UM, Location FROM ItemMaster WHERE ItemNum IN ({inSql})", pars);
  90. foreach (var it in items)
  91. {
  92. if (!string.IsNullOrWhiteSpace(it.ItemNum) && !itemMap.ContainsKey(it.ItemNum))
  93. itemMap[it.ItemNum] = it;
  94. }
  95. }
  96. var keeperHits = await _keeper.ResolveByItemsAsync(domain, itemNums, ct);
  97. var keeperByItem = keeperHits.GroupBy(h => h.ItemNum, StringComparer.OrdinalIgnoreCase)
  98. .ToDictionary(g => g.Key, g => g.First(), StringComparer.OrdinalIgnoreCase);
  99. var draftMasters = new List<DraftNbrMaster>();
  100. var draftDetails = new List<DraftNbrDetail>();
  101. foreach (var wo in masters)
  102. {
  103. var wods = details.Where(d => string.Equals(d.WorkOrd, wo.WorkOrd, StringComparison.OrdinalIgnoreCase)).ToList();
  104. if (wods.Count == 0) continue;
  105. BuildOne(wo, wods, itemMap, keeperByItem, user, draftMasters, draftDetails);
  106. }
  107. if (draftMasters.Count == 0)
  108. return new { ok = false, message = "未生成任何领料单(明细为空或物料无效)。" };
  109. var nbrs = await _seq.AllocateAsync(NbrTypeAllocate, draftMasters.Count, domain, user, ct);
  110. if (nbrs.Count < draftMasters.Count)
  111. throw Oops.Oh($"取号不足:需要 {draftMasters.Count},得到 {nbrs.Count}");
  112. for (var i = 0; i < draftMasters.Count; i++)
  113. {
  114. var nbr = nbrs[i];
  115. draftMasters[i].Nbr = nbr;
  116. var woTail = Tail(draftMasters[i].WorkOrd);
  117. draftMasters[i].Address = woTail;
  118. draftMasters[i].Ufld1 = woTail;
  119. foreach (var d in draftDetails.Where(x => x.TempKey == draftMasters[i].TempKey))
  120. {
  121. d.Nbr = nbr;
  122. d.Address = woTail;
  123. d.OrdNbr = woTail;
  124. d.ERPfld1 = woTail;
  125. d.OrdLine = d.Line;
  126. d.ERPfld2 = d.Line.ToString();
  127. }
  128. }
  129. // 165 直写领料主从(拿 RecID)
  130. await InsertNbrOn165Async(ss, draftMasters, draftDetails, ct);
  131. // 本库工单状态
  132. var touchedOrds = draftMasters.Select(x => x.WorkOrd).Distinct().ToList();
  133. var now = DateTime.Now;
  134. foreach (var wo in masters.Where(m => touchedOrds.Contains(m.WorkOrd!)))
  135. {
  136. var batch = Tail(wo.WorkOrd!);
  137. await _db.Updateable<WorkOrdMaster>()
  138. .SetColumns(m => new WorkOrdMaster
  139. {
  140. Status = "r",
  141. Batch = batch,
  142. UpdateUser = user,
  143. UpdateTime = now
  144. })
  145. .Where(m => m.WorkOrd == wo.WorkOrd)
  146. .ExecuteCommandAsync(ct);
  147. }
  148. await _db.Updateable<WorkOrdRouting>()
  149. .SetColumns(r => new WorkOrdRouting { Status = "r", UpdateUser = user, UpdateTime = now })
  150. .Where(r => touchedOrds.Contains(r.WorkOrd!) && r.IsActive
  151. && r.Status != null && r.Status.ToLower() != "c" && r.Status.ToUpper() != "W")
  152. .ExecuteCommandAsync(ct);
  153. // Outbox:165 工单头 UPSERT(无则插入计划侧最小集,有则只改 Status/Batch)
  154. var trace = $"pick|{domain}|{DateTime.Now:yyyyMMddHHmmss}|{Guid.NewGuid():N}"[..48];
  155. var enqueued = 0;
  156. var masterByOrd = masters.Where(m => touchedOrds.Contains(m.WorkOrd!))
  157. .GroupBy(m => m.WorkOrd!, StringComparer.OrdinalIgnoreCase)
  158. .ToDictionary(g => g.Key, g => g.First(), StringComparer.OrdinalIgnoreCase);
  159. foreach (var wo in touchedOrds)
  160. {
  161. var batch = Tail(wo);
  162. masterByOrd.TryGetValue(wo, out var local);
  163. var insert = BuildWorkOrdInsert(domain, wo, batch, user, now, local);
  164. var update = new Dictionary<string, object?>
  165. {
  166. ["Status"] = "r",
  167. ["Batch"] = batch,
  168. ["UpdateUser"] = Trunc(user, 24),
  169. ["UpdateTime"] = now.ToString("yyyy-MM-dd HH:mm:ss")
  170. };
  171. if (await EnqueueAsync(input.TenantId, $"{trace}|wom|{wo}", "PICK_WOM_UPSERT", new
  172. {
  173. op = "UPSERT",
  174. table = "WorkOrdMaster",
  175. keys = new Dictionary<string, object?> { ["Domain"] = domain, ["WorkOrd"] = wo },
  176. update,
  177. expect = new Dictionary<string, object?>(),
  178. insert
  179. }, ct)) enqueued++;
  180. }
  181. // WorkOrdRouting 多行 UPDATE:Outbox 执行器按全 keys 等值,不适合「WorkOrd 下多工序」。
  182. // 与 Nbr 相同,里程碑工序在 165 直写(仅 MilestoneOp=1 且 Status<>C)。
  183. await ss.Ado.ExecuteCommandAsync(
  184. @"UPDATE WorkOrdRouting
  185. SET Status='r', UpdateUser=@u, UpdateTime=@t
  186. WHERE Domain=@d AND WorkOrd IN (" + string.Join(",", touchedOrds.Select((_, i) => "@w" + i)) + @")
  187. AND ISNULL(MilestoneOp,0)=1
  188. AND ISNULL(Status,'') <> 'C'
  189. AND ISNULL(Status,'') <> 'c'",
  190. BuildInParams(domain, user, now, touchedOrds));
  191. _logger.LogInformation(
  192. "[CreatePickBill] domain={Domain} workOrds={Count} nbrs={Nbrs} outbox={Outbox} trace={Trace}",
  193. domain, touchedOrds.Count, draftMasters.Count, enqueued, trace);
  194. return new
  195. {
  196. ok = true,
  197. message = "ok",
  198. trace,
  199. workOrds = touchedOrds,
  200. nbrs = draftMasters.Select(m => new { m.WorkOrd, m.Nbr, m.User1, detailCount = draftDetails.Count(d => d.Nbr == m.Nbr) }),
  201. outboxEnqueued = enqueued
  202. };
  203. }
  204. private async Task<List<WorkOrdMaster>> LoadCandidatesAsync(string domain, string? workOrdCsv, CancellationToken ct)
  205. {
  206. if (string.IsNullOrWhiteSpace(workOrdCsv))
  207. {
  208. var from = DateTime.Now.Date.AddDays(1);
  209. var to = DateTime.Now.Date.AddDays(4);
  210. return await _db.Queryable<WorkOrdMaster>()
  211. .Where(m => m.Domain == domain
  212. && m.OrdDate >= from && m.OrdDate < to
  213. && m.Status != null && m.Status.ToLower() == "p")
  214. .ToListAsync(ct);
  215. }
  216. var list = workOrdCsv.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
  217. if (list.Length == 0) return new List<WorkOrdMaster>();
  218. // 显式工单号:不强制 Domain/本库仍为 p(本库 Domain 可能是租户号;下达可能已置 r)
  219. return await _db.Queryable<WorkOrdMaster>()
  220. .Where(m => list.Contains(m.WorkOrd!)
  221. && m.Status != null
  222. && m.Status.ToLower() != "c"
  223. && m.Status.ToLower() != "w")
  224. .ToListAsync(ct);
  225. }
  226. private static Dictionary<string, object?> BuildWorkOrdInsert(
  227. string domain, string workOrd, string batch, string user, DateTime now, WorkOrdMaster? local)
  228. {
  229. var insert = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase)
  230. {
  231. ["Domain"] = domain,
  232. ["WorkOrd"] = workOrd,
  233. ["ID"] = workOrd,
  234. ["Status"] = "r",
  235. ["Batch"] = batch,
  236. ["ItemNum"] = local?.ItemNum ?? "",
  237. ["Site"] = local?.Site ?? "",
  238. ["Typed"] = local?.Typed ?? "",
  239. ["QtyOrded"] = local?.QtyOrded ?? 0m,
  240. ["ProdLine"] = local?.ProdLine ?? "",
  241. ["Location"] = local?.Location ?? "",
  242. ["Priority"] = local?.Priority ?? 0m,
  243. ["Urgent"] = local?.Urgent ?? 0,
  244. ["ItemName"] = local?.ItemName ?? "",
  245. ["Department"] = local?.Department ?? "",
  246. ["RoutingCode"] = local?.RoutingCode ?? "",
  247. ["BOMFormula"] = local?.BOMFormula ?? "",
  248. ["Project"] = local?.Project ?? "",
  249. ["Drawing"] = local?.Drawing ?? "",
  250. ["Rev"] = local?.Rev ?? "",
  251. ["CustNo"] = local?.CustNo ?? "",
  252. ["BusinessID"] = local?.BusinessID ?? 0L,
  253. ["IsActive"] = true,
  254. ["IsConfirm"] = local?.IsConfirm ?? false,
  255. ["CreateUser"] = Trunc(local?.CreateUser ?? user, 24),
  256. ["UpdateUser"] = Trunc(user, 24),
  257. ["CreateTime"] = (local?.CreateTime ?? now).ToString("yyyy-MM-dd HH:mm:ss"),
  258. ["UpdateTime"] = now.ToString("yyyy-MM-dd HH:mm:ss")
  259. };
  260. if (local?.OrdDate != null)
  261. insert["OrdDate"] = local.OrdDate.Value.ToString("yyyy-MM-dd HH:mm:ss");
  262. if (local?.DueDate != null)
  263. insert["DueDate"] = local.DueDate.Value.ToString("yyyy-MM-dd HH:mm:ss");
  264. if (local?.ReleaseDate != null)
  265. insert["ReleaseDate"] = local.ReleaseDate.Value.ToString("yyyy-MM-dd HH:mm:ss");
  266. return insert;
  267. }
  268. private static void BuildOne(
  269. WorkOrdMaster wo,
  270. List<WorkOrdDetail> wods,
  271. Dictionary<string, ItemLite> itemMap,
  272. Dictionary<string, KeeperResolveService.KeeperHit> keeperByItem,
  273. string userAccount,
  274. List<DraftNbrMaster> masters,
  275. List<DraftNbrDetail> details)
  276. {
  277. var today = DateTime.Now.Date;
  278. var ord = (wo.OrdDate ?? today).Date;
  279. var eff = ord.AddDays(-1);
  280. if (eff < today) eff = today;
  281. var start = eff;
  282. var temp = Guid.NewGuid().ToString("N");
  283. var keepers = new List<string>();
  284. short line = 1;
  285. var agg = new Dictionary<string, DraftNbrDetail>(StringComparer.OrdinalIgnoreCase);
  286. foreach (var a in wods)
  287. {
  288. if (string.IsNullOrWhiteSpace(a.ItemNum)) continue;
  289. itemMap.TryGetValue(a.ItemNum, out var im);
  290. var locFrom = !string.IsNullOrWhiteSpace(a.Location)
  291. ? a.Location!
  292. : (im?.Location ?? "");
  293. if (keeperByItem.TryGetValue(a.ItemNum, out var kh) && !string.IsNullOrWhiteSpace(kh.Employee))
  294. keepers.Add(kh.Employee);
  295. if (agg.TryGetValue(a.ItemNum, out var exist))
  296. {
  297. exist.QtyOrd += a.QtyRequired;
  298. exist.CurrQtyOpened += a.QtyRequired;
  299. continue;
  300. }
  301. var d = new DraftNbrDetail
  302. {
  303. TempKey = temp,
  304. Domain = wo.Domain ?? "8010",
  305. Type = NbrTypeBill,
  306. ItemNum = a.ItemNum,
  307. QtyFrom = 0,
  308. QtyTo = 0,
  309. LocationFrom = locFrom,
  310. LocationTo = wo.Location ?? "",
  311. WorkOrd = wo.WorkOrd ?? "",
  312. QtyOrd = a.QtyRequired,
  313. CurrQtyOpened = a.QtyRequired,
  314. Line = line++,
  315. UM = im?.UM ?? "",
  316. LotSerial = a.LotSerial ?? "",
  317. ItemName = im?.Descr ?? "",
  318. Status = "",
  319. CreateUser = userAccount,
  320. UpdateUser = userAccount,
  321. CreateTime = DateTime.Now,
  322. UpdateTime = DateTime.Now
  323. };
  324. agg[a.ItemNum] = d;
  325. details.Add(d);
  326. }
  327. if (agg.Count == 0) return;
  328. var (user1, user2) = KeeperResolveService.FormatKeeperUsers(
  329. keepers.Distinct().Select(e => new KeeperResolveService.KeeperHit { Employee = e, ItemNum = "" }));
  330. masters.Add(new DraftNbrMaster
  331. {
  332. TempKey = temp,
  333. Domain = wo.Domain ?? "8010",
  334. Type = NbrTypeBill,
  335. Remark = "下达自动领料",
  336. ProdLine = wo.ProdLine ?? "",
  337. EffDate = eff,
  338. Date = start,
  339. Status = "",
  340. WorkOrd = wo.WorkOrd ?? "",
  341. QtyOrd = wo.QtyOrded,
  342. IsActive = true,
  343. IsChanged = true,
  344. Name = userAccount.Length > 12 ? userAccount[..12] : userAccount,
  345. Department = "101",
  346. CreateUser = userAccount,
  347. UpdateUser = userAccount,
  348. CreateTime = DateTime.Now,
  349. UpdateTime = DateTime.Now,
  350. TransType = "",
  351. User1 = user1,
  352. User2 = user2
  353. });
  354. }
  355. private static async Task InsertNbrOn165Async(
  356. ISqlSugarClient ss,
  357. List<DraftNbrMaster> masters,
  358. List<DraftNbrDetail> details,
  359. CancellationToken ct)
  360. {
  361. foreach (var m in masters)
  362. {
  363. var recIdObj = await ss.Ado.SqlQuerySingleAsync<int?>(
  364. @"INSERT INTO NbrMaster
  365. (Domain, Type, Nbr, [Print], Status, Remark, Date, User1, User2,
  366. ProdLine, EffDate, WorkOrd, QtyOrd, QtyRec, Address, BusinessID,
  367. CreateUser, UpdateUser, CreateTime, UpdateTime, IsActive, IsConfirm,
  368. IsChanged, Department, Name, TransType, Ufld1, IsGP12Checked, IsPlan, IsReturn, Op)
  369. OUTPUT INSERTED.RecID
  370. VALUES
  371. (@Domain, @Type, @Nbr, 0, @Status, @Remark, @Date, @User1, @User2,
  372. @ProdLine, @EffDate, @WorkOrd, @QtyOrd, 0, @Address, 0,
  373. @CreateUser, @UpdateUser, @CreateTime, @UpdateTime, 1, 0,
  374. 1, @Department, @Name, @TransType, @Ufld1, 0, 0, 0, 0)",
  375. new SugarParameter("@Domain", m.Domain),
  376. new SugarParameter("@Type", m.Type),
  377. new SugarParameter("@Nbr", m.Nbr),
  378. new SugarParameter("@Status", m.Status ?? ""),
  379. new SugarParameter("@Remark", m.Remark ?? ""),
  380. new SugarParameter("@Date", m.Date),
  381. new SugarParameter("@User1", m.User1 ?? ""),
  382. new SugarParameter("@User2", m.User2 ?? "[]"),
  383. new SugarParameter("@ProdLine", m.ProdLine ?? ""),
  384. new SugarParameter("@EffDate", m.EffDate),
  385. new SugarParameter("@WorkOrd", m.WorkOrd),
  386. new SugarParameter("@QtyOrd", m.QtyOrd),
  387. new SugarParameter("@Address", m.Address ?? ""),
  388. new SugarParameter("@CreateUser", Trunc(m.CreateUser, 24)),
  389. new SugarParameter("@UpdateUser", Trunc(m.UpdateUser, 24)),
  390. new SugarParameter("@CreateTime", m.CreateTime),
  391. new SugarParameter("@UpdateTime", m.UpdateTime),
  392. new SugarParameter("@Department", m.Department ?? ""),
  393. new SugarParameter("@Name", m.Name ?? ""),
  394. new SugarParameter("@TransType", m.TransType ?? ""),
  395. new SugarParameter("@Ufld1", m.Ufld1 ?? ""));
  396. if (recIdObj is null or 0)
  397. throw new InvalidOperationException($"NbrMaster 插入未返回 RecID:{m.Nbr}");
  398. var recId = recIdObj.Value;
  399. foreach (var d in details.Where(x => x.TempKey == m.TempKey))
  400. {
  401. await ss.Ado.ExecuteCommandAsync(
  402. @"INSERT INTO NbrDetail
  403. (Domain, Type, Nbr, Line, ItemNum, Dimension1, Dimension2,
  404. LocationFrom, LocationTo, QtyFrom, QtyTo, UM, [Print], Status, LotSerial,
  405. WorkOrd, QtyOrd, QtyRec, Address, BusinessID, CreateUser, UpdateUser,
  406. CreateTime, UpdateTime, IsActive, IsConfirm, QtyCache, CurrQtyOpened,
  407. IsChanged, NbrRecID, OrdNbr, ItemName, ERPfld1, ERPfld2, OrdLine,
  408. IsGP12Demand, IsGP12Checked, Material, SeqID)
  409. VALUES
  410. (@Domain, @Type, @Nbr, @Line, @ItemNum, '', '',
  411. @LocationFrom, @LocationTo, 0, 0, @UM, 0, @Status, @LotSerial,
  412. @WorkOrd, @QtyOrd, 0, @Address, 0, @CreateUser, @UpdateUser,
  413. @CreateTime, @UpdateTime, 1, 0, 0, @CurrQtyOpened,
  414. 1, @NbrRecID, @OrdNbr, @ItemName, @ERPfld1, @ERPfld2, @OrdLine,
  415. 0, 0, 0, 0)",
  416. new SugarParameter("@Domain", d.Domain),
  417. new SugarParameter("@Type", d.Type),
  418. new SugarParameter("@Nbr", d.Nbr),
  419. new SugarParameter("@Line", d.Line),
  420. new SugarParameter("@ItemNum", d.ItemNum),
  421. new SugarParameter("@LocationFrom", Trunc(d.LocationFrom, 8)),
  422. new SugarParameter("@LocationTo", Trunc(d.LocationTo, 8)),
  423. new SugarParameter("@UM", Trunc(d.UM, 8)),
  424. new SugarParameter("@Status", d.Status ?? ""),
  425. new SugarParameter("@LotSerial", d.LotSerial ?? ""),
  426. new SugarParameter("@WorkOrd", d.WorkOrd),
  427. new SugarParameter("@QtyOrd", d.QtyOrd),
  428. new SugarParameter("@Address", Trunc(d.Address, 60)),
  429. new SugarParameter("@CreateUser", Trunc(d.CreateUser, 24)),
  430. new SugarParameter("@UpdateUser", Trunc(d.UpdateUser, 24)),
  431. new SugarParameter("@CreateTime", d.CreateTime),
  432. new SugarParameter("@UpdateTime", d.UpdateTime),
  433. new SugarParameter("@CurrQtyOpened", d.CurrQtyOpened),
  434. new SugarParameter("@NbrRecID", recId),
  435. new SugarParameter("@OrdNbr", Trunc(d.OrdNbr, 48)),
  436. new SugarParameter("@ItemName", Trunc(d.ItemName, 1000)),
  437. new SugarParameter("@ERPfld1", Trunc(d.ERPfld1, 200)),
  438. new SugarParameter("@ERPfld2", Trunc(d.ERPfld2, 200)),
  439. new SugarParameter("@OrdLine", d.OrdLine));
  440. }
  441. }
  442. }
  443. private async Task<bool> EnqueueAsync(long tenantId, string idem, string action, object payload, CancellationToken ct)
  444. {
  445. var json = JsonSerializer.Serialize(payload);
  446. var item = new MdpOutbox
  447. {
  448. TenantId = tenantId,
  449. TargetSourceCode = TargetSource,
  450. ActionCode = action,
  451. IdemKey = idem.Length > 200 ? idem[..200] : idem,
  452. PayloadJson = json
  453. };
  454. return await _enqueue.TryEnqueueAsync(item, ct);
  455. }
  456. private static SugarParameter[] BuildInParams(string domain, string user, DateTime now, List<string> workOrds)
  457. {
  458. var pars = new List<SugarParameter>
  459. {
  460. new("@d", domain),
  461. new("@u", Trunc(user, 24)),
  462. new("@t", now)
  463. };
  464. for (var i = 0; i < workOrds.Count; i++)
  465. pars.Add(new SugarParameter("@w" + i, workOrds[i]));
  466. return pars.ToArray();
  467. }
  468. private static string Tail(string workOrd) =>
  469. workOrd.Length > 1 ? workOrd[1..] : workOrd;
  470. private sealed class ItemLite
  471. {
  472. public string ItemNum { get; set; } = "";
  473. public string? Descr { get; set; }
  474. public string? UM { get; set; }
  475. public string? Location { get; set; }
  476. }
  477. private static string Trunc(string? s, int max) =>
  478. string.IsNullOrEmpty(s) ? "" : (s.Length <= max ? s : s[..max]);
  479. private sealed class DraftNbrMaster
  480. {
  481. public string TempKey { get; set; } = "";
  482. public string Domain { get; set; } = "";
  483. public string Type { get; set; } = "";
  484. public string Nbr { get; set; } = "";
  485. public string? Remark { get; set; }
  486. public string? ProdLine { get; set; }
  487. public DateTime EffDate { get; set; }
  488. public DateTime Date { get; set; }
  489. public string? Status { get; set; }
  490. public string WorkOrd { get; set; } = "";
  491. public decimal QtyOrd { get; set; }
  492. public bool IsActive { get; set; }
  493. public bool IsChanged { get; set; }
  494. public string? Name { get; set; }
  495. public string? Department { get; set; }
  496. public string? CreateUser { get; set; }
  497. public string? UpdateUser { get; set; }
  498. public DateTime CreateTime { get; set; }
  499. public DateTime UpdateTime { get; set; }
  500. public string? TransType { get; set; }
  501. public string? User1 { get; set; }
  502. public string? User2 { get; set; }
  503. public string? Address { get; set; }
  504. public string? Ufld1 { get; set; }
  505. }
  506. private sealed class DraftNbrDetail
  507. {
  508. public string TempKey { get; set; } = "";
  509. public string Domain { get; set; } = "";
  510. public string Type { get; set; } = "";
  511. public string Nbr { get; set; } = "";
  512. public string ItemNum { get; set; } = "";
  513. public decimal QtyFrom { get; set; }
  514. public decimal QtyTo { get; set; }
  515. public string LocationFrom { get; set; } = "";
  516. public string LocationTo { get; set; } = "";
  517. public string WorkOrd { get; set; } = "";
  518. public decimal QtyOrd { get; set; }
  519. public decimal CurrQtyOpened { get; set; }
  520. public short Line { get; set; }
  521. public string UM { get; set; } = "";
  522. public string LotSerial { get; set; } = "";
  523. public string ItemName { get; set; } = "";
  524. public string Status { get; set; } = "";
  525. public string? CreateUser { get; set; }
  526. public string? UpdateUser { get; set; }
  527. public DateTime CreateTime { get; set; }
  528. public DateTime UpdateTime { get; set; }
  529. public string? Address { get; set; }
  530. public string? OrdNbr { get; set; }
  531. public string? ERPfld1 { get; set; }
  532. public string? ERPfld2 { get; set; }
  533. public short OrdLine { get; set; }
  534. }
  535. }