|
|
@@ -1,3 +1,4 @@
|
|
|
+using Admin.NET.Plugin.AiDOP.DataPlatform;
|
|
|
using Admin.NET.Plugin.AiDOP.DataPlatform.Executors;
|
|
|
|
|
|
namespace Admin.NET.Plugin.AiDOP.MaterialWarehouse;
|
|
|
@@ -23,6 +24,11 @@ public class PurchaseReceiptMdpSyncService : ITransient
|
|
|
private const string InboundEntityCode = "S5_PURCHASE_RECEIPT_DETAIL";
|
|
|
private const string InboundMasterEntityCode = "S5_PURCHASE_RECEIPT_MASTER";
|
|
|
|
|
|
+ // 双源(dopdemorq SQL Server)Phase 1:默认第二源与其入站实体。实体默认 status=0,未启用前 RunSourceSwitchFullAsync 会因找不到启用实体而抛错(就位不启用)。
|
|
|
+ private const string SqlServerSourceCode = "DOPDEMORQ_SQLSERVER";
|
|
|
+ private const string SqlServerMasterEntityCode = "S5_PURCHASE_RECEIPT_MASTER_SQLSERVER";
|
|
|
+ private const string SqlServerDetailEntityCode = "S5_PURCHASE_RECEIPT_DETAIL_SQLSERVER";
|
|
|
+
|
|
|
private readonly ISqlSugarClient _db;
|
|
|
private readonly MdpSourcePullDispatcher _pullDispatcher;
|
|
|
|
|
|
@@ -113,6 +119,69 @@ public class PurchaseReceiptMdpSyncService : ITransient
|
|
|
};
|
|
|
}
|
|
|
|
|
|
+ /// <summary>
|
|
|
+ /// 双源切换 FULL Replace(Phase 1):从指定 DB 源(默认 DOPDEMORQ_SQLSERVER)PullAll 全量灌 stg,
|
|
|
+ /// 成功后在单事务内以「仅当前源」结果 FULL 重建 mdp_std_purchase_receipt(按 tenant 精确隔离),消除旧源独有业务键残留。
|
|
|
+ ///
|
|
|
+ /// 与既有 RunFull/RunInbound 的区别:① 用 PullAllByEntityCodeAsync 抽尽(非单批 PullByEntityCode);
|
|
|
+ /// ② FullRefresh=true 显式全量 bootstrap;③ transform 仅当前 source_system;④ std DELETE+INSERT 同事务,Pull 成功后才进入。
|
|
|
+ ///
|
|
|
+ /// 切源语义为「部署级单活源」。SQLSERVER 实体默认 status=0,未启用前本方法运行时会因 PullByEntityCode 找不到启用实体抛错——符合 Phase 1「就位不启用」。
|
|
|
+ /// dopdemorq 当前六表为空:本方法 FULL Replace 的理论结果是把当前 tenant 对应 std 置空;Phase 1 不实际执行 destructive 切换。
|
|
|
+ /// </summary>
|
|
|
+ public async Task<PurchaseReceiptInboundResult> RunSourceSwitchFullAsync(
|
|
|
+ string sourceCode = SqlServerSourceCode,
|
|
|
+ string masterEntityCode = SqlServerMasterEntityCode,
|
|
|
+ string detailEntityCode = SqlServerDetailEntityCode,
|
|
|
+ long tenantId = 0,
|
|
|
+ CancellationToken cancellationToken = default)
|
|
|
+ {
|
|
|
+ cancellationToken.ThrowIfCancellationRequested();
|
|
|
+ await EnsureTablesAsync();
|
|
|
+ await EnsureStgTableAsync();
|
|
|
+
|
|
|
+ var now = DateTime.Now;
|
|
|
+ // 1) PullAll 抽尽 + fullRefresh=true(不用单批 PullByEntityCode)。任一 Pull 失败会抛异常,std 未动。
|
|
|
+ var pullCtx = new MdpPullContext
|
|
|
+ {
|
|
|
+ TenantId = tenantId,
|
|
|
+ FullRefresh = true,
|
|
|
+ TaskCode = "S5_PURCHASE_RECEIPT_INBOUND",
|
|
|
+ BatchId = $"S5_PUR_RCT_SW_{now:yyyyMMddHHmmss}"
|
|
|
+ };
|
|
|
+ var master = await _pullDispatcher.PullAllByEntityCodeAsync(masterEntityCode, pullCtx, cancellationToken);
|
|
|
+ var detail = await _pullDispatcher.PullAllByEntityCodeAsync(detailEntityCode, pullCtx, cancellationToken);
|
|
|
+
|
|
|
+ // 2) FULL Replace:事务内 DELETE 当前 tenant 的 std,再 INSERT 仅当前 source_system 的结果。
|
|
|
+ var batchId = $"S5_PUR_RCT_SWSTD_{now:yyyyMMddHHmmss}";
|
|
|
+ var runLogId = await InsertRunLogAsync(batchId, now, "SOURCE_SWITCH");
|
|
|
+ var result = new PurchaseReceiptMdpSyncResult { BatchId = batchId, RunLogId = runLogId };
|
|
|
+ try
|
|
|
+ {
|
|
|
+ result.StdRows = await MdpStdFullReplace.ReplaceAsync(
|
|
|
+ _db, "mdp_std_purchase_receipt", tenantId, extraWhere: null,
|
|
|
+ insertScopedAsync: () => TransformStandardAsync(batchId, now, sourceCode),
|
|
|
+ cancellationToken);
|
|
|
+ await MarkRunSuccessAsync(runLogId, now, result);
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ await MarkRunFailedAsync(runLogId, now, ex.Message);
|
|
|
+ throw;
|
|
|
+ }
|
|
|
+
|
|
|
+ return new PurchaseReceiptInboundResult
|
|
|
+ {
|
|
|
+ PullBatchId = pullCtx.BatchId,
|
|
|
+ RowsPulled = master.RowsPulled + detail.RowsPulled,
|
|
|
+ RowsWrittenStg = master.RowsWritten + detail.RowsWritten,
|
|
|
+ NewCursor = detail.NewCursor ?? master.NewCursor,
|
|
|
+ PullMessage = $"{master.Message}; {detail.Message}",
|
|
|
+ TransformBatchId = batchId,
|
|
|
+ StdRows = result.StdRows
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
private async Task<(int RowsPulled, int RowsWritten, string? NewCursor, string? Message)> PopulateStgAsync(
|
|
|
MdpPullContext pullCtx, CancellationToken cancellationToken)
|
|
|
{
|
|
|
@@ -205,10 +274,14 @@ public class PurchaseReceiptMdpSyncService : ITransient
|
|
|
/// <summary>
|
|
|
/// 标准化:stg(PurOrdRctDetail/Master) + 维表(本库) → mdp_std_purchase_receipt。
|
|
|
/// </summary>
|
|
|
- private async Task<int> TransformStandardAsync(string batchId, DateTime now)
|
|
|
+ private async Task<int> TransformStandardAsync(string batchId, DateTime now, string? sourceSystem = null)
|
|
|
{
|
|
|
+ // 双源:sourceSystem 非空时仅统计/转换当前 source(切源 FULL Replace 用);为空时保持既有全源行为不变。
|
|
|
+ var srcClause = sourceSystem == null ? "" : " AND p.source_system=@Src AND d.source_system=@Src";
|
|
|
+ var countPars = new List<SugarParameter>();
|
|
|
+ if (sourceSystem != null) countPars.Add(new SugarParameter("@Src", sourceSystem));
|
|
|
var rows = await _db.Ado.GetIntAsync(
|
|
|
- """
|
|
|
+ $"""
|
|
|
SELECT COUNT(1)
|
|
|
FROM mdp_stg_purchase_receipt p
|
|
|
INNER JOIN mdp_stg_purchase_receipt d
|
|
|
@@ -216,10 +289,10 @@ public class PurchaseReceiptMdpSyncService : ITransient
|
|
|
AND JSON_UNQUOTE(JSON_EXTRACT(p.raw_data,'$.Domain')) = JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.Domain'))
|
|
|
AND JSON_UNQUOTE(JSON_EXTRACT(p.raw_data,'$.Receiver')) = JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.Receiver'))
|
|
|
WHERE p.source_table='PurOrdRctDetail'
|
|
|
- AND JSON_UNQUOTE(JSON_EXTRACT(p.raw_data,'$.RctType'))='rc'
|
|
|
- """);
|
|
|
- await _db.Ado.ExecuteCommandAsync(
|
|
|
- """
|
|
|
+ AND JSON_UNQUOTE(JSON_EXTRACT(p.raw_data,'$.RctType'))='rc'{srcClause}
|
|
|
+ """, countPars);
|
|
|
+ var insertSql =
|
|
|
+ $"""
|
|
|
INSERT INTO mdp_std_purchase_receipt
|
|
|
(tenant_id, factory_id, source_system, domain, receiver, line, rct_date, supp, sort_name,
|
|
|
item_num, item_name, item_spec, um, qty_ordered, qty_received, lot_serial, location,
|
|
|
@@ -287,7 +360,7 @@ public class PurchaseReceiptMdpSyncService : ITransient
|
|
|
AND dr.SAP_pr_billno = sd.Req
|
|
|
AND IFNULL(dr.SAP_pr_billno,'')<>''
|
|
|
WHERE p.source_table='PurOrdRctDetail'
|
|
|
- AND JSON_UNQUOTE(JSON_EXTRACT(p.raw_data,'$.RctType'))='rc'
|
|
|
+ AND JSON_UNQUOTE(JSON_EXTRACT(p.raw_data,'$.RctType'))='rc'{srcClause}
|
|
|
ON DUPLICATE KEY UPDATE
|
|
|
factory_id=VALUES(factory_id), rct_date=VALUES(rct_date), supp=VALUES(supp), sort_name=VALUES(sort_name),
|
|
|
item_num=VALUES(item_num), item_name=VALUES(item_name), item_spec=VALUES(item_spec), um=VALUES(um),
|
|
|
@@ -296,9 +369,14 @@ public class PurchaseReceiptMdpSyncService : ITransient
|
|
|
pur_ord=VALUES(pur_ord), pur_line=VALUES(pur_line), sales_job=VALUES(sales_job), address1=VALUES(address1),
|
|
|
req=VALUES(req), req_line=VALUES(req_line), dop_req=VALUES(dop_req),
|
|
|
sync_batch_id=VALUES(sync_batch_id), sync_time=VALUES(sync_time), update_time=CURRENT_TIMESTAMP
|
|
|
- """,
|
|
|
- new SugarParameter("@BatchId", batchId),
|
|
|
- new SugarParameter("@Now", now));
|
|
|
+ """;
|
|
|
+ var insPars = new List<SugarParameter>
|
|
|
+ {
|
|
|
+ new("@BatchId", batchId),
|
|
|
+ new("@Now", now)
|
|
|
+ };
|
|
|
+ if (sourceSystem != null) insPars.Add(new SugarParameter("@Src", sourceSystem));
|
|
|
+ await _db.Ado.ExecuteCommandAsync(insertSql, insPars);
|
|
|
return rows;
|
|
|
}
|
|
|
|