PurOrdWmsPushService.cs 48 KB

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