PurOrdWmsPushService.cs 47 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080
  1. using System.Text.Json;
  2. using Admin.NET.Plugin.AiDOP.DataPlatform.Executors;
  3. using Admin.NET.Plugin.AiDOP.Entity.DataPlatform;
  4. using Microsoft.AspNetCore.Http;
  5. using Microsoft.Extensions.Logging;
  6. using SqlSugar;
  7. namespace Admin.NET.Plugin.AiDOP.DataPlatform.Wms;
  8. /// <summary>
  9. /// WP4 · 采购链入 165:自建采购单 / 交货计划 / 送货单 → Outbox UPSERT,供 WMS 扫码收货读取。
  10. /// 旧 DOP 与 WMS 同库,建单即可见;新架构拆库后必须补这段回写。
  11. /// 范围:Potype='po' 且 ReqBy in ('PO','DO') 的自建单,SAP 下发单不回推。
  12. /// </summary>
  13. [ApiDescriptionSettings(Order = 327, Description = "采购链推送 WMS")]
  14. [Route("api/aidop/wms-purord")]
  15. [AllowAnonymous]
  16. [NonUnify]
  17. public class PurOrdWmsPushService : IDynamicApiController, ITransient
  18. {
  19. public const string TargetSource = "DOPDEMORQ_SQLSERVER";
  20. public const string ActionPom = "PO_WMS_POM";
  21. public const string ActionPod = "PO_WMS_POD";
  22. public const string ActionDs = "PO_WMS_DS";
  23. public const string ActionShd = "PO_WMS_SHD";
  24. public const string ActionShdzb = "PO_WMS_SHDZB";
  25. public const string ActionShph = "PO_WMS_SHPH";
  26. public const string ActionMp = "PO_WMS_MP";
  27. private readonly ISqlSugarClient _db;
  28. private readonly MdpOutboxEnqueueService _enqueue;
  29. private readonly MdpOutboxWakeSignal _wake;
  30. private readonly UserManager _userManager;
  31. private readonly IHttpContextAccessor _httpContextAccessor;
  32. private readonly ILogger<PurOrdWmsPushService> _logger;
  33. public PurOrdWmsPushService(
  34. ISqlSugarClient db,
  35. MdpOutboxEnqueueService enqueue,
  36. MdpOutboxWakeSignal wake,
  37. UserManager userManager,
  38. IHttpContextAccessor httpContextAccessor,
  39. ILogger<PurOrdWmsPushService> logger)
  40. {
  41. _db = db;
  42. _enqueue = enqueue;
  43. _wake = wake;
  44. _userManager = userManager;
  45. _httpContextAccessor = httpContextAccessor;
  46. _logger = logger;
  47. }
  48. public sealed class PushInput
  49. {
  50. /// <summary>采购单号。与 Shddh 二选一。</summary>
  51. public string? PurOrd { get; set; }
  52. /// <summary>送货单号。给定时按「PO + 交货计划 + 送货单」整链补推。</summary>
  53. public string? Shddh { get; set; }
  54. public long TenantId { get; set; }
  55. }
  56. public sealed class PushResult
  57. {
  58. public bool Ok { get; set; }
  59. public int Enqueued { get; set; }
  60. public int PurOrdCount { get; set; }
  61. public int DetailCount { get; set; }
  62. public int ScheduleCount { get; set; }
  63. public int ShipmentCount { get; set; }
  64. public int LabelCount { get; set; }
  65. public int BarcodeCount { get; set; }
  66. public List<string> Skipped { get; set; } = new();
  67. public string? Message { get; set; }
  68. }
  69. /// <summary>补偿 / 联调:按本库快照把采购链推 165。</summary>
  70. [DisplayName("推送采购链到 WMS")]
  71. [HttpPost("push")]
  72. public async Task<PushResult> Push([FromBody] PushInput input, CancellationToken ct = default)
  73. {
  74. if (input == null) throw Oops.Oh("请求体不能为空");
  75. if (string.IsNullOrWhiteSpace(input.PurOrd) && string.IsNullOrWhiteSpace(input.Shddh))
  76. throw Oops.Oh("采购单号与送货单号至少给一个");
  77. if (input.TenantId <= 0) throw Oops.Oh("租户号无效,请指定有效的 TenantId");
  78. var jwtTenantId = _userManager.TenantId;
  79. if (jwtTenantId > 0 && input.TenantId != jwtTenantId)
  80. throw Oops.Oh("请求租户与当前登录租户不一致");
  81. if (jwtTenantId <= 0)
  82. {
  83. var clientIp = _httpContextAccessor.HttpContext?.Connection?.RemoteIpAddress?.ToString() ?? "unknown";
  84. _logger.LogInformation(
  85. "[PurOrdWmsPush] 无登录态推送 audit ip={ClientIp} purOrd={PurOrd} shddh={Shddh} tenantId={TenantId}",
  86. clientIp, input.PurOrd, input.Shddh, input.TenantId);
  87. }
  88. var result = new PushResult { Ok = true };
  89. if (!string.IsNullOrWhiteSpace(input.Shddh))
  90. await EnqueueShipmentChainAsync(input.TenantId, input.Shddh.Trim(), result, ct);
  91. else
  92. await EnqueuePurchaseOrderAsync(input.TenantId, input.PurOrd!.Trim(), result, ct);
  93. _wake.Pulse();
  94. result.Message = $"已入队 {result.Enqueued} 条(采购单 {result.PurOrdCount}/明细 {result.DetailCount}/"
  95. + $"交货计划 {result.ScheduleCount}/送货单 {result.ShipmentCount}/装箱标签 {result.LabelCount}/"
  96. + $"箱码 {result.BarcodeCount})";
  97. return result;
  98. }
  99. /// <summary>业务挂接点用:失败只记日志与 warning,不拖垮本库事务。</summary>
  100. public async Task TryEnqueuePurchaseOrderSafeAsync(long tenantId, string purOrd, List<string>? warnings = null)
  101. {
  102. if (string.IsNullOrWhiteSpace(purOrd)) return;
  103. try
  104. {
  105. var r = new PushResult();
  106. await EnqueuePurchaseOrderAsync(tenantId, purOrd.Trim(), r, CancellationToken.None);
  107. _wake.Pulse();
  108. if (r.Enqueued > 0)
  109. warnings?.Add($"采购单 {purOrd} 已入队推送 WMS({r.Enqueued} 条)");
  110. }
  111. catch (Exception ex)
  112. {
  113. _logger.LogWarning(ex, "[PurOrdWmsPush] purOrd enqueue failed tenant={Tenant} po={PurOrd}", tenantId, purOrd);
  114. warnings?.Add($"采购单 {purOrd} 推送 WMS 入队失败:{ex.Message}(可调用 /api/aidop/wms-purord/push 补偿)");
  115. }
  116. }
  117. /// <summary>业务挂接点用:交货计划发布/取消后同步 165。</summary>
  118. public async Task TryEnqueueSchedulesSafeAsync(long tenantId, List<long> ids, List<string>? warnings = null)
  119. {
  120. if (ids == null || ids.Count == 0) return;
  121. try
  122. {
  123. var r = new PushResult();
  124. await EnqueueSchedulesByIdsAsync(tenantId, ids, r, CancellationToken.None);
  125. _wake.Pulse();
  126. if (r.Enqueued > 0)
  127. warnings?.Add($"交货计划已入队推送 WMS({r.Enqueued} 条)");
  128. }
  129. catch (Exception ex)
  130. {
  131. _logger.LogWarning(ex, "[PurOrdWmsPush] ds enqueue failed tenant={Tenant} ids={Count}", tenantId, ids.Count);
  132. warnings?.Add($"交货计划推送 WMS 入队失败:{ex.Message}");
  133. }
  134. }
  135. /// <summary>业务挂接点用:发货单生成标签后推整链(PO → 交货计划 → 送货单 → 装箱标签)。</summary>
  136. public async Task TryEnqueueShipmentChainSafeAsync(long tenantId, string shddh, List<string>? warnings = null)
  137. {
  138. if (string.IsNullOrWhiteSpace(shddh)) return;
  139. try
  140. {
  141. var r = new PushResult();
  142. await EnqueueShipmentChainAsync(tenantId, shddh.Trim(), r, CancellationToken.None);
  143. _wake.Pulse();
  144. if (r.Enqueued > 0)
  145. warnings?.Add($"送货单 {shddh} 及采购链已入队推送 WMS({r.Enqueued} 条)");
  146. foreach (var s in r.Skipped) warnings?.Add(s);
  147. }
  148. catch (Exception ex)
  149. {
  150. _logger.LogWarning(ex, "[PurOrdWmsPush] shipment enqueue failed tenant={Tenant} shddh={Shddh}", tenantId, shddh);
  151. warnings?.Add($"送货单 {shddh} 推送 WMS 入队失败:{ex.Message}(可调用 /api/aidop/wms-purord/push 补偿)");
  152. }
  153. }
  154. // ── 采购单主表 + 明细 ──────────────────────────────────────────────
  155. private async Task EnqueuePurchaseOrderAsync(
  156. long tenantId, string purOrd, PushResult result, CancellationToken ct)
  157. {
  158. var masters = await _db.Ado.SqlQueryAsync<PurOrdMasterRow>(
  159. """
  160. SELECT
  161. TRIM(IFNULL(`Domain`, '')) AS Domain,
  162. TRIM(IFNULL(PurOrd, '')) AS PurOrd,
  163. TRIM(IFNULL(Potype, '')) AS Potype,
  164. TRIM(IFNULL(Typed, '')) AS Typed,
  165. TRIM(IFNULL(Supp, '')) AS Supp,
  166. TRIM(IFNULL(Buyer, '')) AS Buyer,
  167. TRIM(IFNULL(ReqBy, '')) AS ReqBy,
  168. TRIM(IFNULL(`Usage`, '')) AS UsageText,
  169. TRIM(IFNULL(FSTID, '')) AS FSTID,
  170. TRIM(IFNULL(Remark, '')) AS Remark,
  171. OrdDate, DueDate,
  172. IFNULL(IsActive, 1) AS IsActive,
  173. IFNULL(IsConfirm, 0) AS IsConfirm,
  174. CreateUser, CreateTime, UpdateUser, UpdateTime
  175. FROM PurOrdMaster
  176. WHERE tenant_id = @TenantId AND PurOrd = @PurOrd
  177. LIMIT 1
  178. """,
  179. new SugarParameter("@TenantId", tenantId),
  180. new SugarParameter("@PurOrd", purOrd));
  181. if (masters.Count == 0)
  182. throw Oops.Oh($"本库未找到采购单 {purOrd}");
  183. var m = masters[0];
  184. if (!IsSelfBuilt(m.Potype, m.ReqBy))
  185. {
  186. result.Skipped.Add($"采购单 {purOrd} 非自建单(Potype={m.Potype} / ReqBy={m.ReqBy}),按约定不回推 165");
  187. return;
  188. }
  189. var domain = string.IsNullOrWhiteSpace(m.Domain) ? "8010" : m.Domain;
  190. var potype = string.IsNullOrWhiteSpace(m.Potype) ? "po" : m.Potype;
  191. var now = DateTime.Now;
  192. var keys = new Dictionary<string, object?>
  193. {
  194. ["Domain"] = domain,
  195. ["PurOrd"] = purOrd,
  196. ["Potype"] = potype
  197. };
  198. var insert = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase)
  199. {
  200. ["Domain"] = domain,
  201. ["PurOrd"] = Trunc(purOrd, 48),
  202. ["Potype"] = Trunc(potype, 8),
  203. ["Typed"] = Trunc(m.Typed, 18),
  204. ["Supp"] = Trunc(m.Supp, 20),
  205. ["Buyer"] = Trunc(m.Buyer, 30),
  206. ["ReqBy"] = Trunc(m.ReqBy, 8),
  207. // Status 只建行写空串;收货态由 WMS 维护
  208. ["Status"] = "",
  209. ["Usage"] = Trunc(m.UsageText, 30),
  210. ["FSTID"] = Trunc(m.FSTID, 24),
  211. ["Remark"] = Trunc(m.Remark, 200),
  212. ["IsActive"] = m.IsActive != 0,
  213. ["IsConfirm"] = m.IsConfirm != 0,
  214. ["CreateUser"] = Trunc(m.CreateUser ?? "aidop", 24),
  215. ["UpdateUser"] = Trunc(m.UpdateUser ?? m.CreateUser ?? "aidop", 24),
  216. ["CreateTime"] = Fmt(m.CreateTime ?? now),
  217. ["UpdateTime"] = Fmt(m.UpdateTime ?? now)
  218. };
  219. if (m.OrdDate != null) insert["OrdDate"] = Fmt(m.OrdDate);
  220. if (m.DueDate != null) insert["DueDate"] = Fmt(m.DueDate);
  221. var update = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase)
  222. {
  223. ["Typed"] = Trunc(m.Typed, 18),
  224. ["Supp"] = Trunc(m.Supp, 20),
  225. ["Buyer"] = Trunc(m.Buyer, 30),
  226. ["ReqBy"] = Trunc(m.ReqBy, 8),
  227. ["Usage"] = Trunc(m.UsageText, 30),
  228. ["FSTID"] = Trunc(m.FSTID, 24),
  229. ["Remark"] = Trunc(m.Remark, 200),
  230. ["IsActive"] = m.IsActive != 0,
  231. ["UpdateUser"] = Trunc(m.UpdateUser ?? "aidop", 24),
  232. ["UpdateTime"] = Fmt(now)
  233. };
  234. if (m.DueDate != null) update["DueDate"] = Fmt(m.DueDate);
  235. if (await EnqueueRowAsync(tenantId, $"po|{domain}|{potype}|{purOrd}|pom",
  236. ActionPom, "PurOrdMaster", keys, insert, update, null, ct))
  237. result.Enqueued++;
  238. result.PurOrdCount++;
  239. var details = await _db.Ado.SqlQueryAsync<PurOrdDetailRow>(
  240. """
  241. SELECT
  242. TRIM(IFNULL(`Domain`, '')) AS Domain,
  243. IFNULL(Line, 0) AS Line,
  244. TRIM(IFNULL(ItemNum, '')) AS ItemNum,
  245. TRIM(IFNULL(Descr, '')) AS Descr,
  246. TRIM(IFNULL(UM, '')) AS UM,
  247. TRIM(IFNULL(Rev, '')) AS Rev,
  248. TRIM(IFNULL(Drawing, '')) AS Drawing,
  249. TRIM(IFNULL(Location, '')) AS Location,
  250. TRIM(IFNULL(LotSerial, '')) AS LotSerial,
  251. TRIM(IFNULL(Req, '')) AS Req,
  252. IFNULL(QtyOrded, 0) AS QtyOrded,
  253. DueDate, NeedDate,
  254. IFNULL(IsActive, 1) AS IsActive,
  255. IFNULL(IsConfirm, 0) AS IsConfirm,
  256. CreateUser, CreateTime, UpdateUser, UpdateTime
  257. FROM PurOrdDetail
  258. WHERE tenant_id = @TenantId AND PurOrd = @PurOrd
  259. ORDER BY Line
  260. """,
  261. new SugarParameter("@TenantId", tenantId),
  262. new SugarParameter("@PurOrd", purOrd));
  263. foreach (var d in details)
  264. {
  265. var dKeys = new Dictionary<string, object?>
  266. {
  267. ["Domain"] = domain,
  268. ["PurOrd"] = purOrd,
  269. ["Potype"] = potype,
  270. ["Line"] = d.Line,
  271. ["BlanketLine"] = 0
  272. };
  273. var dInsert = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase)
  274. {
  275. ["Domain"] = domain,
  276. ["PurOrd"] = Trunc(purOrd, 48),
  277. ["Potype"] = Trunc(potype, 8),
  278. ["Line"] = d.Line,
  279. ["BlanketLine"] = 0,
  280. ["ItemNum"] = Trunc(d.ItemNum, 60),
  281. ["Descr"] = Trunc(d.Descr, 255),
  282. ["UM"] = Trunc(d.UM, 8),
  283. ["Rev"] = Trunc(d.Rev, 8),
  284. ["Drawing"] = Trunc(d.Drawing, 24),
  285. ["Location"] = Trunc(d.Location, 8),
  286. ["LotSerial"] = Trunc(d.LotSerial, 120),
  287. ["Req"] = Trunc(d.Req, 20),
  288. ["QtyOrded"] = d.QtyOrded,
  289. // 收货累计列建行给 0,之后归 WMS
  290. ["RctQty"] = 0m,
  291. ["ReceiptQty"] = 0m,
  292. ["QtyReturned"] = 0m,
  293. ["Status"] = "",
  294. ["IsActive"] = d.IsActive != 0,
  295. ["IsConfirm"] = d.IsConfirm != 0,
  296. ["CreateUser"] = Trunc(d.CreateUser ?? "aidop", 24),
  297. ["UpdateUser"] = Trunc(d.UpdateUser ?? d.CreateUser ?? "aidop", 24),
  298. ["CreateTime"] = Fmt(d.CreateTime ?? now),
  299. ["UpdateTime"] = Fmt(d.UpdateTime ?? now)
  300. };
  301. if (d.DueDate != null) dInsert["DueDate"] = Fmt(d.DueDate);
  302. if (d.NeedDate != null) dInsert["NeedDate"] = Fmt(d.NeedDate);
  303. var dUpdate = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase)
  304. {
  305. ["ItemNum"] = Trunc(d.ItemNum, 60),
  306. ["Descr"] = Trunc(d.Descr, 255),
  307. ["UM"] = Trunc(d.UM, 8),
  308. ["Rev"] = Trunc(d.Rev, 8),
  309. ["Drawing"] = Trunc(d.Drawing, 24),
  310. ["Location"] = Trunc(d.Location, 8),
  311. ["QtyOrded"] = d.QtyOrded,
  312. ["IsActive"] = d.IsActive != 0,
  313. ["UpdateUser"] = Trunc(d.UpdateUser ?? "aidop", 24),
  314. ["UpdateTime"] = Fmt(now)
  315. };
  316. if (d.DueDate != null) dUpdate["DueDate"] = Fmt(d.DueDate);
  317. if (d.NeedDate != null) dUpdate["NeedDate"] = Fmt(d.NeedDate);
  318. // 165 的 RecID 是 IDENTITY,明细外键必须在目标库现查主表 RecID
  319. var resolve = new Dictionary<string, object?>
  320. {
  321. ["PurOrdRecID"] = new Dictionary<string, object?>
  322. {
  323. ["table"] = "PurOrdMaster",
  324. ["column"] = "RecID",
  325. ["match"] = new Dictionary<string, object?>
  326. {
  327. ["Domain"] = domain,
  328. ["PurOrd"] = purOrd,
  329. ["Potype"] = potype
  330. }
  331. }
  332. };
  333. if (await EnqueueRowAsync(tenantId, $"po|{domain}|{potype}|{purOrd}|pod|{d.Line}",
  334. ActionPod, "PurOrdDetail", dKeys, dInsert, dUpdate, resolve, ct))
  335. result.Enqueued++;
  336. result.DetailCount++;
  337. }
  338. }
  339. // ── 交货计划 ──────────────────────────────────────────────────────
  340. private async Task EnqueueSchedulesByIdsAsync(
  341. long tenantId, List<long> ids, PushResult result, CancellationToken ct)
  342. {
  343. var rows = await _db.Ado.SqlQueryAsync<DeliveryScheduleRow>(
  344. $"""
  345. SELECT
  346. Id, TRIM(IFNULL(domain, '')) AS Domain, IFNULL(icdsid, 0) AS Icdsid,
  347. TRIM(IFNULL(dsnum, '')) AS Dsnum, TRIM(IFNULL(status, '')) AS Status,
  348. TRIM(IFNULL(itemnum, '')) AS Itemnum, TRIM(IFNULL(um, '')) AS Um,
  349. TRIM(IFNULL(purgroup, '')) AS Purgroup,
  350. TRIM(IFNULL(suppliercode, '')) AS Suppliercode, TRIM(IFNULL(supplier, '')) AS Supplier,
  351. submitdate, requestdate, needdate,
  352. TRIM(IFNULL(ponumber, '')) AS Ponumber, IFNULL(poline, 0) AS Poline,
  353. IFNULL(schedqty, 0) AS Schedqty,
  354. TRIM(IFNULL(remarks, '')) AS Remarks, IFNULL(isactive, 1) AS Isactive,
  355. createuser, createtime, updateuser, updatetime
  356. FROM srm_polist_ds
  357. WHERE tenant_id = @TenantId AND Id IN ({string.Join(",", ids.Select((_, i) => $"@i{i}"))})
  358. """,
  359. ids.Select((v, i) => new SugarParameter($"@i{i}", v))
  360. .Append(new SugarParameter("@TenantId", tenantId))
  361. .ToList());
  362. foreach (var ds in rows)
  363. await EnqueueScheduleRowAsync(tenantId, ds, result, ct);
  364. }
  365. private async Task EnqueueSchedulesByPurOrdAsync(
  366. long tenantId, string purOrd, PushResult result, CancellationToken ct)
  367. {
  368. var rows = await _db.Ado.SqlQueryAsync<DeliveryScheduleRow>(
  369. """
  370. SELECT
  371. Id, TRIM(IFNULL(domain, '')) AS Domain, IFNULL(icdsid, 0) AS Icdsid,
  372. TRIM(IFNULL(dsnum, '')) AS Dsnum, TRIM(IFNULL(status, '')) AS Status,
  373. TRIM(IFNULL(itemnum, '')) AS Itemnum, TRIM(IFNULL(um, '')) AS Um,
  374. TRIM(IFNULL(purgroup, '')) AS Purgroup,
  375. TRIM(IFNULL(suppliercode, '')) AS Suppliercode, TRIM(IFNULL(supplier, '')) AS Supplier,
  376. submitdate, requestdate, needdate,
  377. TRIM(IFNULL(ponumber, '')) AS Ponumber, IFNULL(poline, 0) AS Poline,
  378. IFNULL(schedqty, 0) AS Schedqty,
  379. TRIM(IFNULL(remarks, '')) AS Remarks, IFNULL(isactive, 1) AS Isactive,
  380. createuser, createtime, updateuser, updatetime
  381. FROM srm_polist_ds
  382. WHERE tenant_id = @TenantId AND ponumber = @PurOrd AND IFNULL(isactive, 1) = 1
  383. ORDER BY poline
  384. """,
  385. new SugarParameter("@TenantId", tenantId),
  386. new SugarParameter("@PurOrd", purOrd));
  387. foreach (var ds in rows)
  388. await EnqueueScheduleRowAsync(tenantId, ds, result, ct);
  389. }
  390. private async Task EnqueueScheduleRowAsync(
  391. long tenantId, DeliveryScheduleRow ds, PushResult result, CancellationToken ct)
  392. {
  393. var domain = string.IsNullOrWhiteSpace(ds.Domain) ? "8010" : ds.Domain;
  394. var now = DateTime.Now;
  395. var keys = new Dictionary<string, object?>
  396. {
  397. ["domain"] = domain,
  398. ["dsnum"] = ds.Dsnum
  399. };
  400. var insert = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase)
  401. {
  402. ["Id"] = ds.Id,
  403. ["domain"] = domain,
  404. ["icdsid"] = ds.Icdsid,
  405. ["dsnum"] = Trunc(ds.Dsnum, 128),
  406. ["status"] = Trunc(ds.Status, 10),
  407. ["itemnum"] = Trunc(ds.Itemnum, 128),
  408. ["um"] = Trunc(ds.Um, 124),
  409. ["purgroup"] = Trunc(ds.Purgroup, 50),
  410. ["suppliercode"] = Trunc(ds.Suppliercode, 50),
  411. ["supplier"] = Trunc(ds.Supplier, 50),
  412. ["ponumber"] = Trunc(ds.Ponumber, 50),
  413. ["poline"] = ds.Poline,
  414. ["schedqty"] = ds.Schedqty,
  415. // 收货累计列建行给 0 / 全额待交,之后归 WMS
  416. ["lastsentqty"] = 0m,
  417. ["sentqty"] = 0m,
  418. ["restqty"] = ds.Schedqty,
  419. ["remarks"] = Trunc(ds.Remarks, 500),
  420. ["isactive"] = ds.Isactive,
  421. ["createuser"] = Trunc(ds.Createuser ?? "aidop", 24),
  422. ["updateuser"] = Trunc(ds.Updateuser ?? ds.Createuser ?? "aidop", 24),
  423. ["createtime"] = Fmt(ds.Createtime ?? now),
  424. ["updatetime"] = Fmt(ds.Updatetime ?? now)
  425. };
  426. if (ds.Submitdate != null) insert["submitdate"] = Fmt(ds.Submitdate);
  427. if (ds.Requestdate != null) insert["requestdate"] = Fmt(ds.Requestdate);
  428. if (ds.Needdate != null) insert["needdate"] = Fmt(ds.Needdate);
  429. // sentqty / restqty / lastsentdate 归 WMS,更新时一律不碰
  430. var update = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase)
  431. {
  432. ["status"] = Trunc(ds.Status, 10),
  433. ["schedqty"] = ds.Schedqty,
  434. ["isactive"] = ds.Isactive,
  435. ["remarks"] = Trunc(ds.Remarks, 500),
  436. ["updateuser"] = Trunc(ds.Updateuser ?? "aidop", 24),
  437. ["updatetime"] = Fmt(now)
  438. };
  439. if (ds.Needdate != null) update["needdate"] = Fmt(ds.Needdate);
  440. if (ds.Submitdate != null) update["submitdate"] = Fmt(ds.Submitdate);
  441. if (await EnqueueRowAsync(tenantId, $"po|{domain}|ds|{ds.Dsnum}",
  442. ActionDs, "srm_polist_ds", keys, insert, update, null, ct))
  443. result.Enqueued++;
  444. result.ScheduleCount++;
  445. }
  446. // ── 送货单 + 明细 + 装箱标签 ───────────────────────────────────────
  447. private async Task EnqueueShipmentChainAsync(
  448. long tenantId, string shddh, PushResult result, CancellationToken ct)
  449. {
  450. var masters = await _db.Ado.SqlQueryAsync<ShipmentRow>(
  451. """
  452. SELECT
  453. id AS Id,
  454. IFNULL(sh_purchase_id, 0) AS ShPurchaseId,
  455. TRIM(IFNULL(sh_purchase_name, '')) AS ShPurchaseName,
  456. TRIM(IFNULL(sh_purchase_num, '')) AS ShPurchaseNum,
  457. TRIM(IFNULL(sh_purchase_address, '')) AS ShPurchaseAddress,
  458. TRIM(IFNULL(sh_purchase_lxr, '')) AS ShPurchaseLxr,
  459. TRIM(IFNULL(sh_purchase_phone, '')) AS ShPurchasePhone,
  460. TRIM(IFNULL(delivery_Address, '')) AS DeliveryAddress,
  461. TRIM(IFNULL(expected_consignee, '')) AS ExpectedConsignee,
  462. TRIM(IFNULL(consignee_phone, '')) AS ConsigneePhone,
  463. estimated_delivery_date AS EstimatedDeliveryDate,
  464. TRIM(IFNULL(po_billno, '')) AS PoBillno,
  465. TRIM(IFNULL(shddh, '')) AS Shddh,
  466. TRIM(IFNULL(jhshrq, '')) AS Jhshrq,
  467. TRIM(IFNULL(tjrid, '')) AS Tjrid,
  468. TRIM(IFNULL(tjrxm, '')) AS Tjrxm,
  469. TRIM(IFNULL(tjrq, '')) AS Tjrq,
  470. IFNULL(scbq, 0) AS Scbq,
  471. TRIM(IFNULL(chbg, '')) AS Chbg,
  472. IFNULL(sfpc, 0) AS Sfpc,
  473. TRIM(IFNULL(pcsm, '')) AS Pcsm,
  474. TRIM(IFNULL(wlsc, '')) AS Wlsc,
  475. TRIM(IFNULL(yjdhrq, '')) AS Yjdhrq,
  476. IFNULL(state, 0) AS State,
  477. TRIM(IFNULL(shzt, '')) AS Shzt,
  478. TRIM(IFNULL(wldh, '')) AS Wldh,
  479. IFNULL(dycs, 0) AS Dycs
  480. FROM scm_shd
  481. WHERE tenant_id = @TenantId AND shddh = @Shddh
  482. LIMIT 1
  483. """,
  484. new SugarParameter("@TenantId", tenantId),
  485. new SugarParameter("@Shddh", shddh));
  486. if (masters.Count == 0)
  487. throw Oops.Oh($"本库未找到送货单 {shddh}");
  488. var s = masters[0];
  489. var lines = await _db.Ado.SqlQueryAsync<ShipmentLineRow>(
  490. """
  491. SELECT
  492. id AS Id, TRIM(IFNULL(glid, '')) AS Glid,
  493. TRIM(IFNULL(sh_material_code, '')) AS ShMaterialCode,
  494. TRIM(IFNULL(sh_material_name, '')) AS ShMaterialName,
  495. TRIM(IFNULL(sh_material_ggxh, '')) AS ShMaterialGgxh,
  496. IFNULL(sh_delivery_quantity, 0) AS ShDeliveryQuantity,
  497. TRIM(IFNULL(sh_material_dw, '')) AS ShMaterialDw,
  498. TRIM(IFNULL(remarks, '')) AS Remarks,
  499. IFNULL(bzsl, 0) AS Bzsl, IFNULL(bqsl, 0) AS Bqsl,
  500. TRIM(IFNULL(order_type, '')) AS OrderType,
  501. TRIM(IFNULL(po_bill, '')) AS PoBill, TRIM(IFNULL(po_billline, '')) AS PoBillline,
  502. IFNULL(hh, 0) AS Hh,
  503. TRIM(IFNULL(scrq, '')) AS Scrq, TRIM(IFNULL(scph, '')) AS Scph,
  504. TRIM(IFNULL(th, '')) AS Th, TRIM(IFNULL(bbh, '')) AS Bbh,
  505. IFNULL(djsl, 0) AS Djsl,
  506. TRIM(IFNULL(ccrq, '')) AS Ccrq, TRIM(IFNULL(cgyt, '')) AS Cgyt,
  507. TRIM(IFNULL(jybb, '')) AS Jybb,
  508. TRIM(IFNULL(jhdbh, '')) AS Jhdbh, TRIM(IFNULL(jhdhh, '')) AS Jhdhh,
  509. TRIM(IFNULL(shpc, '')) AS Shpc, TRIM(IFNULL(shzt, '')) AS Shzt,
  510. IFNULL(rksl, 0) AS Rksl, IFNULL(thsl, 0) AS Thsl
  511. FROM scm_shdzb
  512. WHERE glid = @Glid
  513. ORDER BY hh, id
  514. """,
  515. new SugarParameter("@Glid", s.Id.ToString()));
  516. var labels = await _db.Ado.SqlQueryAsync<ShipmentLabelRow>(
  517. """
  518. SELECT
  519. id AS Id, TRIM(IFNULL(xh, '')) AS Xh, TRIM(IFNULL(wlbm, '')) AS Wlbm,
  520. TRIM(IFNULL(scph, '')) AS Scph, TRIM(IFNULL(shdh, '')) AS Shdh,
  521. TRIM(IFNULL(shpc, '')) AS Shpc, TRIM(IFNULL(gysbm, '')) AS Gysbm,
  522. TRIM(IFNULL(csrq, '')) AS Csrq
  523. FROM scm_shdshph
  524. WHERE shdh = @Shddh
  525. ORDER BY id
  526. """,
  527. new SugarParameter("@Shddh", shddh));
  528. // 先补上游:送货明细引用的采购单与交货计划,否则 WMS 查不到可收行
  529. var purOrds = lines.Select(x => (x.PoBill ?? "").Trim())
  530. .Where(x => x.Length > 0)
  531. .Distinct(StringComparer.OrdinalIgnoreCase)
  532. .ToList();
  533. foreach (var po in purOrds)
  534. {
  535. await EnqueuePurchaseOrderAsync(tenantId, po, result, ct);
  536. await EnqueueSchedulesByPurOrdAsync(tenantId, po, result, ct);
  537. }
  538. var now = DateTime.Now;
  539. var sInsert = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase)
  540. {
  541. ["id"] = s.Id,
  542. ["sh_purchase_id"] = s.ShPurchaseId,
  543. ["sh_purchase_name"] = Trunc(s.ShPurchaseName, 255),
  544. ["sh_purchase_num"] = Trunc(s.ShPurchaseNum, 255),
  545. ["sh_purchase_address"] = Trunc(s.ShPurchaseAddress, 255),
  546. ["sh_purchase_lxr"] = Trunc(s.ShPurchaseLxr, 255),
  547. ["sh_purchase_phone"] = Trunc(s.ShPurchasePhone, 255),
  548. ["delivery_Address"] = Trunc(s.DeliveryAddress, 255),
  549. ["expected_consignee"] = Trunc(s.ExpectedConsignee, 255),
  550. ["consignee_phone"] = Trunc(s.ConsigneePhone, 255),
  551. ["po_billno"] = Trunc(s.PoBillno, 255),
  552. ["shddh"] = Trunc(s.Shddh, 255),
  553. ["jhshrq"] = Trunc(s.Jhshrq, 50),
  554. ["tjrid"] = Trunc(s.Tjrid, 50),
  555. ["tjrxm"] = Trunc(s.Tjrxm, 50),
  556. ["tjrq"] = Trunc(s.Tjrq, 50),
  557. ["scbq"] = s.Scbq,
  558. ["chbg"] = Trunc(s.Chbg, 255),
  559. ["sfpc"] = s.Sfpc,
  560. ["pcsm"] = Trunc(s.Pcsm, 255),
  561. ["wlsc"] = Trunc(s.Wlsc, 500),
  562. ["yjdhrq"] = Trunc(s.Yjdhrq, 500),
  563. ["state"] = s.State,
  564. ["shzt"] = Trunc(s.Shzt, 50),
  565. ["wldh"] = Trunc(s.Wldh, 50),
  566. ["dycs"] = s.Dycs
  567. };
  568. if (s.EstimatedDeliveryDate != null) sInsert["estimated_delivery_date"] = Fmt(s.EstimatedDeliveryDate);
  569. // shzt / state 收货开始后归 WMS('收货中'),更新时不回写覆盖
  570. var sUpdate = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase)
  571. {
  572. ["sh_purchase_name"] = Trunc(s.ShPurchaseName, 255),
  573. ["sh_purchase_num"] = Trunc(s.ShPurchaseNum, 255),
  574. ["delivery_Address"] = Trunc(s.DeliveryAddress, 255),
  575. ["expected_consignee"] = Trunc(s.ExpectedConsignee, 255),
  576. ["consignee_phone"] = Trunc(s.ConsigneePhone, 255),
  577. ["po_billno"] = Trunc(s.PoBillno, 255),
  578. ["jhshrq"] = Trunc(s.Jhshrq, 50),
  579. ["wlsc"] = Trunc(s.Wlsc, 500),
  580. ["yjdhrq"] = Trunc(s.Yjdhrq, 500)
  581. };
  582. if (s.EstimatedDeliveryDate != null) sUpdate["estimated_delivery_date"] = Fmt(s.EstimatedDeliveryDate);
  583. if (await EnqueueRowAsync(tenantId, $"po|shd|{shddh}", ActionShd, "scm_shd",
  584. new Dictionary<string, object?> { ["id"] = s.Id }, sInsert, sUpdate, null, ct))
  585. result.Enqueued++;
  586. result.ShipmentCount++;
  587. foreach (var l in lines)
  588. {
  589. var lInsert = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase)
  590. {
  591. ["id"] = l.Id,
  592. ["glid"] = Trunc(l.Glid, 255),
  593. ["sh_material_code"] = Trunc(l.ShMaterialCode, 255),
  594. ["sh_material_name"] = Trunc(l.ShMaterialName, 255),
  595. ["sh_material_ggxh"] = Trunc(l.ShMaterialGgxh, 255),
  596. ["sh_delivery_quantity"] = l.ShDeliveryQuantity,
  597. ["sh_material_dw"] = Trunc(l.ShMaterialDw, 255),
  598. ["remarks"] = Trunc(l.Remarks, 255),
  599. ["bzsl"] = l.Bzsl,
  600. ["bqsl"] = l.Bqsl,
  601. ["order_type"] = Trunc(l.OrderType, 255),
  602. ["po_bill"] = Trunc(l.PoBill, 255),
  603. ["po_billline"] = Trunc(l.PoBillline, 50),
  604. ["hh"] = l.Hh,
  605. ["scrq"] = Trunc(l.Scrq, 255),
  606. ["scph"] = Trunc(l.Scph, 255),
  607. ["th"] = Trunc(l.Th, 255),
  608. ["bbh"] = Trunc(l.Bbh, 255),
  609. ["djsl"] = l.Djsl,
  610. ["ccrq"] = Trunc(l.Ccrq, 255),
  611. ["cgyt"] = Trunc(l.Cgyt, 255),
  612. ["jybb"] = Trunc(l.Jybb, 255),
  613. ["jhdbh"] = Trunc(l.Jhdbh, 50),
  614. ["jhdhh"] = Trunc(l.Jhdhh, 50),
  615. ["shpc"] = Trunc(l.Shpc, 50),
  616. ["shzt"] = Trunc(l.Shzt, 255),
  617. // 入库 / 退货数量归 WMS
  618. ["rksl"] = 0m,
  619. ["thsl"] = 0m
  620. };
  621. var lUpdate = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase)
  622. {
  623. ["sh_material_code"] = Trunc(l.ShMaterialCode, 255),
  624. ["sh_material_name"] = Trunc(l.ShMaterialName, 255),
  625. ["sh_material_ggxh"] = Trunc(l.ShMaterialGgxh, 255),
  626. ["sh_delivery_quantity"] = l.ShDeliveryQuantity,
  627. ["sh_material_dw"] = Trunc(l.ShMaterialDw, 255),
  628. ["bzsl"] = l.Bzsl,
  629. ["bqsl"] = l.Bqsl,
  630. ["po_bill"] = Trunc(l.PoBill, 255),
  631. ["po_billline"] = Trunc(l.PoBillline, 50),
  632. ["jhdbh"] = Trunc(l.Jhdbh, 50),
  633. ["jhdhh"] = Trunc(l.Jhdhh, 50),
  634. ["shpc"] = Trunc(l.Shpc, 50)
  635. };
  636. if (await EnqueueRowAsync(tenantId, $"po|shdzb|{shddh}|{l.Id}", ActionShdzb, "scm_shdzb",
  637. new Dictionary<string, object?> { ["id"] = l.Id }, lInsert, lUpdate, null, ct))
  638. result.Enqueued++;
  639. }
  640. foreach (var lb in labels)
  641. {
  642. // 165 的 scm_shdshph.id 是 IDENTITY,只能按 (shdh, xh) 自然键幂等
  643. var bKeys = new Dictionary<string, object?>
  644. {
  645. ["shdh"] = Trunc(lb.Shdh, 500),
  646. ["xh"] = Trunc(lb.Xh, 50)
  647. };
  648. var bInsert = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase)
  649. {
  650. ["xh"] = Trunc(lb.Xh, 50),
  651. ["wlbm"] = Trunc(lb.Wlbm, 500),
  652. ["scph"] = Trunc(lb.Scph, 500),
  653. ["shdh"] = Trunc(lb.Shdh, 500),
  654. ["shpc"] = Trunc(lb.Shpc, 500),
  655. ["gysbm"] = Trunc(lb.Gysbm, 500),
  656. ["csrq"] = Trunc(lb.Csrq, 50)
  657. };
  658. if (await EnqueueRowAsync(tenantId, $"po|shph|{shddh}|{lb.Xh}", ActionShph, "scm_shdshph",
  659. bKeys, bInsert, bInsert, null, ct))
  660. result.Enqueued++;
  661. result.LabelCount++;
  662. }
  663. await EnqueueBarcodesAsync(tenantId, shddh, result, ct);
  664. }
  665. /// <summary>
  666. /// 箱码入 165 MissedPrint:WMS 扫码就是拿箱码查这张表(pr_WMS_GetBarCodes),
  667. /// 查不到即报「该标签不存在」。唯一索引 IX_MissedPrint=(Domain,BarCode),RecID 是 IDENTITY 不推。
  668. /// </summary>
  669. private async Task EnqueueBarcodesAsync(
  670. long tenantId, string shddh, PushResult result, CancellationToken ct)
  671. {
  672. var rows = await _db.Ado.SqlQueryAsync<BarcodeRow>(
  673. """
  674. SELECT
  675. TRIM(IFNULL(`Domain`, '')) AS Domain,
  676. TRIM(IFNULL(Site, '')) AS Site,
  677. TRIM(IFNULL(BarCode, '')) AS BarCode,
  678. TRIM(IFNULL(ItemNum, '')) AS ItemNum,
  679. TRIM(IFNULL(Descr, '')) AS Descr,
  680. TRIM(IFNULL(Product, '')) AS Product,
  681. TRIM(IFNULL(Carton, '')) AS Carton,
  682. TRIM(IFNULL(OrdNbr, '')) AS OrdNbr,
  683. IFNULL(PackingQty, 0) AS PackingQty,
  684. IFNULL(Qty, 0) AS Qty,
  685. TRIM(IFNULL(Status, '')) AS Status,
  686. TRIM(IFNULL(Supply, '')) AS Supply,
  687. TRIM(IFNULL(LotSerial, '')) AS LotSerial,
  688. IFNULL(CartonQty, 1) AS CartonQty,
  689. TRIM(IFNULL(SuppLotSerial, '')) AS SuppLotSerial,
  690. TRIM(IFNULL(ShipperNbr, '')) AS ShipperNbr,
  691. IFNULL(ShipperLine, 0) AS ShipperLine,
  692. ProdDate, ExpireDate,
  693. TRIM(IFNULL(PurOrd, '')) AS PurOrd,
  694. IFNULL(PurLine, 0) AS PurLine,
  695. IFNULL(PurQty, 0) AS PurQty,
  696. TRIM(IFNULL(LabelFormat, '')) AS LabelFormat,
  697. TRIM(IFNULL(StandItem, '')) AS StandItem,
  698. TRIM(IFNULL(EffSize, '')) AS EffSize,
  699. IFNULL(GP12CheckedQty, 0) AS GP12CheckedQty,
  700. IFNULL(NetWeight, 0) AS NetWeight,
  701. TRIM(IFNULL(Remark, '')) AS Remark,
  702. TRIM(IFNULL(LevelChar, '')) AS LevelChar,
  703. TRIM(IFNULL(PurOrdDetBatchNbr, '')) AS PurOrdDetBatchNbr,
  704. TRIM(IFNULL(FirmString5, '')) AS FirmString5,
  705. CreateUser, CreateTime, UpdateUser, UpdateTime
  706. FROM MissedPrint
  707. WHERE ShipperNbr = @Shddh
  708. AND IFNULL(Status, '') = 'U'
  709. AND IFNULL(PurOrd, '') NOT LIKE '作废%'
  710. ORDER BY ShipperLine, Carton
  711. """,
  712. new SugarParameter("@Shddh", shddh));
  713. var now = DateTime.Now;
  714. foreach (var b in rows)
  715. {
  716. var domain = string.IsNullOrWhiteSpace(b.Domain) ? "8010" : b.Domain;
  717. var keys = new Dictionary<string, object?>
  718. {
  719. ["Domain"] = domain,
  720. ["BarCode"] = b.BarCode
  721. };
  722. var insert = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase)
  723. {
  724. ["Domain"] = domain,
  725. ["Site"] = Trunc(b.Site, 24),
  726. ["BarCode"] = Trunc(b.BarCode, 250),
  727. ["ItemNum"] = Trunc(b.ItemNum, 60),
  728. ["Descr"] = Trunc(b.Descr, 255),
  729. ["Product"] = Trunc(b.Product, 255),
  730. ["Carton"] = Trunc(b.Carton, 20),
  731. ["OrdNbr"] = Trunc(b.OrdNbr, 48),
  732. ["PackingQty"] = b.PackingQty,
  733. ["Qty"] = b.Qty,
  734. // 标签状态归 WMS:建行给待收货 U,收货后由 WMS 改,更新时不回写
  735. ["Status"] = "U",
  736. ["RelatedBarCode"] = "",
  737. ["Supply"] = Trunc(b.Supply, 40),
  738. ["LotSerial"] = Trunc(b.LotSerial, 120),
  739. ["CartonQty"] = b.CartonQty,
  740. ["SuppLotSerial"] = Trunc(b.SuppLotSerial, 120),
  741. ["ShipperNbr"] = Trunc(b.ShipperNbr, 60),
  742. ["ShipperLine"] = b.ShipperLine,
  743. ["PurOrd"] = Trunc(b.PurOrd, 48),
  744. ["PurLine"] = b.PurLine,
  745. ["PurQty"] = b.PurQty,
  746. ["LabelFormat"] = Trunc(b.LabelFormat, 20),
  747. ["StandItem"] = Trunc(b.StandItem, 60),
  748. ["EffSize"] = Trunc(b.EffSize, 20),
  749. ["GP12CheckedQty"] = b.GP12CheckedQty,
  750. ["NetWeight"] = b.NetWeight,
  751. ["Remark"] = Trunc(b.Remark, 200),
  752. ["LevelChar"] = Trunc(b.LevelChar, 20),
  753. ["PurOrdDetBatchNbr"] = Trunc(b.PurOrdDetBatchNbr, 48),
  754. ["FirmString5"] = Trunc(b.FirmString5, 48),
  755. ["CreateUser"] = Trunc(b.CreateUser ?? "aidop", 24),
  756. ["UpdateUser"] = Trunc(b.UpdateUser ?? b.CreateUser ?? "aidop", 24),
  757. ["CreateTime"] = Fmt(b.CreateTime ?? now),
  758. ["UpdateTime"] = Fmt(b.UpdateTime ?? now)
  759. };
  760. if (b.ProdDate != null) insert["ProdDate"] = Fmt(b.ProdDate);
  761. if (b.ExpireDate != null) insert["ExpireDate"] = Fmt(b.ExpireDate);
  762. var update = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase)
  763. {
  764. ["ItemNum"] = Trunc(b.ItemNum, 60),
  765. ["Descr"] = Trunc(b.Descr, 255),
  766. ["Product"] = Trunc(b.Product, 255),
  767. ["PackingQty"] = b.PackingQty,
  768. ["Qty"] = b.Qty,
  769. ["LotSerial"] = Trunc(b.LotSerial, 120),
  770. ["SuppLotSerial"] = Trunc(b.SuppLotSerial, 120),
  771. ["ShipperNbr"] = Trunc(b.ShipperNbr, 60),
  772. ["ShipperLine"] = b.ShipperLine,
  773. ["PurOrd"] = Trunc(b.PurOrd, 48),
  774. ["PurLine"] = b.PurLine,
  775. ["PurQty"] = b.PurQty,
  776. ["StandItem"] = Trunc(b.StandItem, 60),
  777. ["EffSize"] = Trunc(b.EffSize, 20),
  778. ["LevelChar"] = Trunc(b.LevelChar, 20),
  779. ["PurOrdDetBatchNbr"] = Trunc(b.PurOrdDetBatchNbr, 48),
  780. ["Remark"] = Trunc(b.Remark, 200),
  781. ["UpdateUser"] = Trunc(b.UpdateUser ?? "aidop", 24),
  782. ["UpdateTime"] = Fmt(now)
  783. };
  784. if (b.ProdDate != null) update["ProdDate"] = Fmt(b.ProdDate);
  785. if (await EnqueueRowAsync(tenantId, $"po|mp|{domain}|{b.BarCode}", ActionMp, "MissedPrint",
  786. keys, insert, update, null, ct))
  787. result.Enqueued++;
  788. result.BarcodeCount++;
  789. }
  790. }
  791. // ── 基础设施 ──────────────────────────────────────────────────────
  792. /// <summary>自建单口径:SAP 下发单不回推 165(决策:SAP 维持现状)。</summary>
  793. private static bool IsSelfBuilt(string? potype, string? reqBy)
  794. {
  795. var p = (potype ?? "").Trim();
  796. var r = (reqBy ?? "").Trim();
  797. return string.Equals(p, "po", StringComparison.OrdinalIgnoreCase)
  798. && (string.Equals(r, "PO", StringComparison.OrdinalIgnoreCase)
  799. || string.Equals(r, "DO", StringComparison.OrdinalIgnoreCase));
  800. }
  801. private async Task<bool> EnqueueRowAsync(
  802. long tenantId,
  803. string idem,
  804. string action,
  805. string table,
  806. Dictionary<string, object?> keys,
  807. Dictionary<string, object?> insert,
  808. Dictionary<string, object?> update,
  809. Dictionary<string, object?>? resolve,
  810. CancellationToken ct)
  811. {
  812. var payload = new Dictionary<string, object?>
  813. {
  814. ["op"] = "UPSERT",
  815. ["table"] = table,
  816. ["keys"] = keys,
  817. ["insert"] = insert,
  818. ["update"] = update,
  819. ["expect"] = new Dictionary<string, object?>()
  820. };
  821. if (resolve != null && resolve.Count > 0) payload["resolve"] = resolve;
  822. var item = new MdpOutbox
  823. {
  824. TenantId = tenantId,
  825. TargetSourceCode = TargetSource,
  826. ActionCode = action,
  827. IdemKey = idem.Length > 200 ? idem[..200] : idem,
  828. PayloadJson = JsonSerializer.Serialize(payload)
  829. };
  830. return await _enqueue.TryEnqueueOrRefreshAsync(item, ct, pulse: false);
  831. }
  832. private static string Fmt(DateTime? dt) => (dt ?? DateTime.Now).ToString("yyyy-MM-dd HH:mm:ss");
  833. private static string Trunc(string? s, int max)
  834. {
  835. if (string.IsNullOrEmpty(s)) return "";
  836. return s.Length <= max ? s : s[..max];
  837. }
  838. private sealed class PurOrdMasterRow
  839. {
  840. public string? Domain { get; set; }
  841. public string? PurOrd { get; set; }
  842. public string? Potype { get; set; }
  843. public string? Typed { get; set; }
  844. public string? Supp { get; set; }
  845. public string? Buyer { get; set; }
  846. public string? ReqBy { get; set; }
  847. public string? UsageText { get; set; }
  848. public string? FSTID { get; set; }
  849. public string? Remark { get; set; }
  850. public DateTime? OrdDate { get; set; }
  851. public DateTime? DueDate { get; set; }
  852. public int IsActive { get; set; }
  853. public int IsConfirm { get; set; }
  854. public string? CreateUser { get; set; }
  855. public DateTime? CreateTime { get; set; }
  856. public string? UpdateUser { get; set; }
  857. public DateTime? UpdateTime { get; set; }
  858. }
  859. private sealed class PurOrdDetailRow
  860. {
  861. public string? Domain { get; set; }
  862. public int Line { get; set; }
  863. public string? ItemNum { get; set; }
  864. public string? Descr { get; set; }
  865. public string? UM { get; set; }
  866. public string? Rev { get; set; }
  867. public string? Drawing { get; set; }
  868. public string? Location { get; set; }
  869. public string? LotSerial { get; set; }
  870. public string? Req { get; set; }
  871. public decimal QtyOrded { get; set; }
  872. public DateTime? DueDate { get; set; }
  873. public DateTime? NeedDate { get; set; }
  874. public int IsActive { get; set; }
  875. public int IsConfirm { get; set; }
  876. public string? CreateUser { get; set; }
  877. public DateTime? CreateTime { get; set; }
  878. public string? UpdateUser { get; set; }
  879. public DateTime? UpdateTime { get; set; }
  880. }
  881. private sealed class DeliveryScheduleRow
  882. {
  883. public long Id { get; set; }
  884. public string? Domain { get; set; }
  885. public long Icdsid { get; set; }
  886. public string? Dsnum { get; set; }
  887. public string? Status { get; set; }
  888. public string? Itemnum { get; set; }
  889. public string? Um { get; set; }
  890. public string? Purgroup { get; set; }
  891. public string? Suppliercode { get; set; }
  892. public string? Supplier { get; set; }
  893. public DateTime? Submitdate { get; set; }
  894. public DateTime? Requestdate { get; set; }
  895. public DateTime? Needdate { get; set; }
  896. public string? Ponumber { get; set; }
  897. public int Poline { get; set; }
  898. public decimal Schedqty { get; set; }
  899. public string? Remarks { get; set; }
  900. public int Isactive { get; set; }
  901. public string? Createuser { get; set; }
  902. public DateTime? Createtime { get; set; }
  903. public string? Updateuser { get; set; }
  904. public DateTime? Updatetime { get; set; }
  905. }
  906. private sealed class ShipmentRow
  907. {
  908. public long Id { get; set; }
  909. public long ShPurchaseId { get; set; }
  910. public string? ShPurchaseName { get; set; }
  911. public string? ShPurchaseNum { get; set; }
  912. public string? ShPurchaseAddress { get; set; }
  913. public string? ShPurchaseLxr { get; set; }
  914. public string? ShPurchasePhone { get; set; }
  915. public string? DeliveryAddress { get; set; }
  916. public string? ExpectedConsignee { get; set; }
  917. public string? ConsigneePhone { get; set; }
  918. public DateTime? EstimatedDeliveryDate { get; set; }
  919. public string? PoBillno { get; set; }
  920. public string? Shddh { get; set; }
  921. public string? Jhshrq { get; set; }
  922. public string? Tjrid { get; set; }
  923. public string? Tjrxm { get; set; }
  924. public string? Tjrq { get; set; }
  925. public int Scbq { get; set; }
  926. public string? Chbg { get; set; }
  927. public int Sfpc { get; set; }
  928. public string? Pcsm { get; set; }
  929. public string? Wlsc { get; set; }
  930. public string? Yjdhrq { get; set; }
  931. public int State { get; set; }
  932. public string? Shzt { get; set; }
  933. public string? Wldh { get; set; }
  934. public int Dycs { get; set; }
  935. }
  936. private sealed class ShipmentLineRow
  937. {
  938. public long Id { get; set; }
  939. public string? Glid { get; set; }
  940. public string? ShMaterialCode { get; set; }
  941. public string? ShMaterialName { get; set; }
  942. public string? ShMaterialGgxh { get; set; }
  943. public decimal ShDeliveryQuantity { get; set; }
  944. public string? ShMaterialDw { get; set; }
  945. public string? Remarks { get; set; }
  946. public decimal Bzsl { get; set; }
  947. public decimal Bqsl { get; set; }
  948. public string? OrderType { get; set; }
  949. public string? PoBill { get; set; }
  950. public string? PoBillline { get; set; }
  951. public int Hh { get; set; }
  952. public string? Scrq { get; set; }
  953. public string? Scph { get; set; }
  954. public string? Th { get; set; }
  955. public string? Bbh { get; set; }
  956. public decimal Djsl { get; set; }
  957. public string? Ccrq { get; set; }
  958. public string? Cgyt { get; set; }
  959. public string? Jybb { get; set; }
  960. public string? Jhdbh { get; set; }
  961. public string? Jhdhh { get; set; }
  962. public string? Shpc { get; set; }
  963. public string? Shzt { get; set; }
  964. public decimal Rksl { get; set; }
  965. public decimal Thsl { get; set; }
  966. }
  967. private sealed class BarcodeRow
  968. {
  969. public string? Domain { get; set; }
  970. public string? Site { get; set; }
  971. public string? BarCode { get; set; }
  972. public string? ItemNum { get; set; }
  973. public string? Descr { get; set; }
  974. public string? Product { get; set; }
  975. public string? Carton { get; set; }
  976. public string? OrdNbr { get; set; }
  977. public decimal PackingQty { get; set; }
  978. public decimal Qty { get; set; }
  979. public string? Status { get; set; }
  980. public string? Supply { get; set; }
  981. public string? LotSerial { get; set; }
  982. public int CartonQty { get; set; }
  983. public string? SuppLotSerial { get; set; }
  984. public string? ShipperNbr { get; set; }
  985. public int ShipperLine { get; set; }
  986. public DateTime? ProdDate { get; set; }
  987. public DateTime? ExpireDate { get; set; }
  988. public string? PurOrd { get; set; }
  989. public int PurLine { get; set; }
  990. public decimal PurQty { get; set; }
  991. public string? LabelFormat { get; set; }
  992. public string? StandItem { get; set; }
  993. public string? EffSize { get; set; }
  994. public decimal GP12CheckedQty { get; set; }
  995. public decimal NetWeight { get; set; }
  996. public string? Remark { get; set; }
  997. public string? LevelChar { get; set; }
  998. public string? PurOrdDetBatchNbr { get; set; }
  999. public string? FirmString5 { get; set; }
  1000. public string? CreateUser { get; set; }
  1001. public DateTime? CreateTime { get; set; }
  1002. public string? UpdateUser { get; set; }
  1003. public DateTime? UpdateTime { get; set; }
  1004. }
  1005. private sealed class ShipmentLabelRow
  1006. {
  1007. public long Id { get; set; }
  1008. public string? Xh { get; set; }
  1009. public string? Wlbm { get; set; }
  1010. public string? Scph { get; set; }
  1011. public string? Shdh { get; set; }
  1012. public string? Shpc { get; set; }
  1013. public string? Gysbm { get; set; }
  1014. public string? Csrq { get; set; }
  1015. }
  1016. }