IqcReceiptStateMdpSyncService.cs 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. using Admin.NET.Plugin.AiDOP.DataPlatform;
  2. using SqlSugar;
  3. using Yitter.IdGenerator;
  4. namespace Admin.NET.Plugin.AiDOP.MaterialWarehouse.InventoryPosting.L0;
  5. /// <summary>
  6. /// L0 IQC 收货标准层贴源转换(态①只读贴源)。**只读源、只写 `mdp_std_iqc_receipt_state`**——绝不写源库/旧 WMS 表。
  7. ///
  8. /// identity 链(据 LIVE SP `qms_WMS_SaveIQCResultcheck` 与 DOP `IqcReturnQueryService` 取证):
  9. /// qms_qcp_inspbill a ⋈(a.lydjbh=b.FBILLNO) qms_qcp_inspecapplyn b ⋈(b.id=c.glid, a.hid=c.id) qms_qcp_insappnentry c
  10. /// ⋈(d.Domain,d.BarCode=a.lydjbh,d.ItemNum=c.FMATERIALCFG) PurOrdRctDetail d ⋈(Domain,OrdNbr=PurOrd,OrdLine=Line) PurOrdDetail p
  11. /// + MissedPrint(CheckNo=a.lydjbh,Status='N',Type&lt;&gt;'Card') 计数。
  12. ///
  13. /// **⚠️ 未接线/未实库验证**:本服务不入任何 cron/job(态①就位不启用),Phase 5B 受控实库验证前不跑真数据。
  14. /// **双源**:源 A=本库 aidopdev(MySQL),下方 SQL 为 MySQL 方言;源 B=dopdemorq(SQLSERVER) 方言适配留待
  15. /// (dopdemorq 六表当前空、默认禁用,见项目说明)。读端 <see cref="MdpIqcReceiptStateLoader"/> 与源无关,切源不改读代码。
  16. /// </summary>
  17. public sealed class IqcReceiptStateMdpSyncService : ITransient
  18. {
  19. public const string StdTable = "mdp_std_iqc_receipt_state";
  20. private readonly ISqlSugarClient _db; // 主库:写 std
  21. private readonly MdpSourceScopeFactory _scopeFactory; // 源库只读连接域
  22. private readonly MdpNeutralSourceGate _neutralGate;
  23. public IqcReceiptStateMdpSyncService(ISqlSugarClient db, MdpSourceScopeFactory scopeFactory, MdpNeutralSourceGate neutralGate)
  24. {
  25. _db = db;
  26. _scopeFactory = scopeFactory;
  27. _neutralGate = neutralGate;
  28. }
  29. /// <summary>运行时建 std 表(与 WIP-S5-IQC-INVPOSTING.sql 一致;mdp 表按框架惯例用原生 DDL 非 SugarTable)。</summary>
  30. public async Task EnsureTablesAsync()
  31. {
  32. await MdpSchemaAligner.ExecuteAsync(_db, MdpSchemaDefinition.Ddl("mdp_std_iqc_receipt_state"));
  33. }
  34. /// <summary>
  35. /// 态①贴源:从 <paramref name="sourceCode"/> 只读 join 拉取 → FULL REPLACE 本 tenant+domain 的 std。
  36. /// 只 SELECT 源、只在主库事务内 DELETE+INSERT std;返回落地行数。
  37. /// </summary>
  38. public async Task<int> RunInboundAsync(string sourceCode, long tenantId, string domainCode, CancellationToken ct = default)
  39. {
  40. var domain = domainCode ?? "";
  41. var scope = await _scopeFactory.GetScopeAsync(sourceCode, ct); // 源库只读连接域
  42. // 只读源:单条 join SELECT(本库 MySQL 方言)。不 UPDATE/DELETE/INSERT 源。
  43. var dt = await scope.Ado.GetDataTableAsync(SourceJoinSql,
  44. new SugarParameter("@domain", domain));
  45. if (!await _neutralGate.AllowsAsync(tenantId, "IQC", sourceCode))
  46. return 0;
  47. await _db.Ado.BeginTranAsync();
  48. try
  49. {
  50. // FULL REPLACE:先删本 tenant+domain 旧 std,再插新(态①全量替换,等价 MdpStdFullReplace)
  51. await MdpSchemaAligner.ExecuteAsync(_db,
  52. "DELETE FROM mdp_std_iqc_receipt_state WHERE tenant_id=@t AND domain=@d",
  53. new SugarParameter("@t", tenantId), new SugarParameter("@d", domain));
  54. var now = DateTime.Now;
  55. var batch = YitIdHelper.NextId().ToString();
  56. var rows = 0;
  57. foreach (System.Data.DataRow r in dt.Rows)
  58. {
  59. await MdpSchemaAligner.ExecuteAsync(_db,
  60. """
  61. INSERT INTO mdp_std_iqc_receipt_state
  62. (id,tenant_id,domain,fbillno,qc_nbr,item_num,location,lot_serial,rct_nbr,pur_ord,pur_line,potype,
  63. qty_ordered,received_cum_qty,returned_cum_qty,sample_qty,receipt_pending_qty,receivable_detail_count,
  64. source_system,sync_batch_id,sync_time,create_time)
  65. VALUES (@id,@t,@d,@fb,@qc,@item,@loc,@lot,@rct,@po,@line,@pt,
  66. @ord,@rcv,@ret,@smp,@pend,@rdc,@src,@bat,@now,@now)
  67. """,
  68. new SugarParameter("@id", YitIdHelper.NextId()), new SugarParameter("@t", tenantId), new SugarParameter("@d", domain),
  69. new SugarParameter("@fb", S(r, "fbillno")), new SugarParameter("@qc", S(r, "qc_nbr")),
  70. new SugarParameter("@item", S(r, "item_num")), new SugarParameter("@loc", S(r, "location")),
  71. new SugarParameter("@lot", S(r, "lot_serial")), new SugarParameter("@rct", S(r, "rct_nbr")),
  72. new SugarParameter("@po", S(r, "pur_ord")), new SugarParameter("@line", I(r, "pur_line")),
  73. new SugarParameter("@pt", S(r, "potype")), new SugarParameter("@ord", D(r, "qty_ordered")),
  74. new SugarParameter("@rcv", D(r, "received_cum_qty")), new SugarParameter("@ret", D(r, "returned_cum_qty")),
  75. new SugarParameter("@smp", D(r, "sample_qty")), new SugarParameter("@pend", D(r, "receipt_pending_qty")),
  76. new SugarParameter("@rdc", I(r, "receivable_detail_count")), new SugarParameter("@src", sourceCode),
  77. new SugarParameter("@bat", batch), new SugarParameter("@now", now));
  78. rows++;
  79. }
  80. await _db.Ado.CommitTranAsync();
  81. return rows;
  82. }
  83. catch
  84. {
  85. await _db.Ado.RollbackTranAsync();
  86. throw;
  87. }
  88. }
  89. private static string S(System.Data.DataRow r, string c) => r.Table.Columns.Contains(c) && r[c] != DBNull.Value ? Convert.ToString(r[c]) : "";
  90. private static int I(System.Data.DataRow r, string c) => r.Table.Columns.Contains(c) && r[c] != DBNull.Value ? Convert.ToInt32(r[c]) : 0;
  91. private static decimal D(System.Data.DataRow r, string c) => r.Table.Columns.Contains(c) && r[c] != DBNull.Value ? Convert.ToDecimal(r[c]) : 0m;
  92. /// <summary>源侧只读 join(本库 MySQL 方言)——严格 SELECT,无源写。</summary>
  93. private const string SourceJoinSql =
  94. """
  95. SELECT
  96. TRIM(IFNULL(a.FBILLNO,'')) AS fbillno,
  97. TRIM(IFNULL(a.lydjbh,'')) AS qc_nbr,
  98. TRIM(IFNULL(c.FMATERIALCFG,'')) AS item_num,
  99. TRIM(IFNULL(d.Location,'')) AS location,
  100. TRIM(IFNULL(d.LotSerial,'')) AS lot_serial,
  101. TRIM(IFNULL(d.Receiver,'')) AS rct_nbr,
  102. TRIM(IFNULL(d.OrdNbr,'')) AS pur_ord,
  103. IFNULL(d.OrdLine,0) AS pur_line,
  104. TRIM(IFNULL(p.Potype,'')) AS potype,
  105. CAST(IFNULL(p.QtyOrded,0) AS DECIMAL(18,4)) AS qty_ordered,
  106. CAST(IFNULL(p.RctQty,0) AS DECIMAL(18,4)) AS received_cum_qty,
  107. CAST(IFNULL(p.QtyReturned,0) AS DECIMAL(18,4)) AS returned_cum_qty,
  108. CAST(IFNULL(a.lysl,0) AS DECIMAL(18,4)) AS sample_qty,
  109. CAST(IFNULL(p.ReceiptQty,0) AS DECIMAL(18,4)) AS receipt_pending_qty,
  110. (SELECT COUNT(1) FROM MissedPrint m
  111. WHERE m.CheckNo=a.lydjbh AND m.ItemNum=c.FMATERIALCFG AND m.Status='N' AND m.Type<>'Card') AS receivable_detail_count
  112. FROM qms_qcp_inspbill a
  113. LEFT JOIN qms_qcp_inspecapplyn b ON BINARY TRIM(IFNULL(a.lydjbh,'')) = BINARY TRIM(IFNULL(b.FBILLNO,''))
  114. LEFT JOIN qms_qcp_insappnentry c ON b.id = c.glid AND a.hid = c.id
  115. LEFT JOIN PurOrdRctDetail d ON d.Domain=@domain AND d.BarCode=a.lydjbh AND d.ItemNum=c.FMATERIALCFG
  116. LEFT JOIN PurOrdDetail p ON d.Domain=p.Domain AND d.OrdNbr=p.PurOrd AND d.OrdLine=p.Line
  117. WHERE TRIM(IFNULL(a.FBILLNO,'')) <> ''
  118. """;
  119. }