using System.Text.RegularExpressions;
using SqlSugar;
namespace Admin.NET.Plugin.AiDOP.DataPlatform;
///
/// 标准层「当前 active 源 FULL Replace」局部工具(S5/S6/S7 列表双源 Phase 1 用)。
///
/// 用途:切源/FULL 刷新时,保证 std = 当前 active source 的完整结果集,消除旧源独有业务键残留。
/// 语义:先由调用方 PullAll 成功灌 stg,再在单事务内 DELETE 当前 tenant(+可选业务条件) 的 std,
/// 随后执行「仅当前 source_system」的 INSERT(由调用方以委托传入其对象专属 transform SQL)。
///
/// 安全边界:
/// - 仅本工具删除 std;不 TRUNCATE 整表、不跨 tenant、不跨业务类型(extraWhere 由调用方精确限定)。
/// - DELETE + INSERT 同一本库事务;任一失败回滚,旧 std 保持不变。
/// - 必须在 Pull 成功之后调用;Pull 失败应让异常在调用方先抛出,绝不先删 std。
/// - 非数据平台核心(不改 Factory/Executor/Writer),仅一层薄事务封装。
///
internal static class MdpStdFullReplace
{
// ⚠️ 适用边界(Phase 2 防御性复核确认):本工具的 DELETE 仅按 tenant_id(+可选 extraWhere) 限定,
// 因此**仅适用于 tenant 独占的业务专表 std**(如 mdp_std_purchase_receipt / _production_receipt / _ipqc_inspection,
// 实测各仅其转换服务写入)。若某 std 与其它业务共享,必须由调用方传入精确 extraWhere 业务分区条件,否则会误删他业务行。
private static readonly Regex IdentifierRe = new(@"^[A-Za-z0-9_]+$", RegexOptions.Compiled);
///
/// 单事务内 FULL Replace 当前 tenant 的 std:先 DELETE,再执行 source 过滤的 INSERT 委托。
///
/// 调用方自身注入的 ISqlSugarClient(同一 scope,事务覆盖委托内的写入)。
/// 标准层表名(标识符白名单校验)。
/// 精确隔离的租户 id。
/// 可选附加删除条件(如业务类型列),已由调用方保证安全,不含用户输入。
/// 仅写入当前 source_system 结果的 INSERT 委托,返回写入行数。
public static async Task ReplaceAsync(
ISqlSugarClient db,
string stdTable,
long tenantId,
string? extraWhere,
Func> insertScopedAsync,
CancellationToken cancellationToken = default)
{
if (string.IsNullOrWhiteSpace(stdTable) || !IdentifierRe.IsMatch(stdTable))
throw new InvalidOperationException($"非法 std 表名:{stdTable}");
cancellationToken.ThrowIfCancellationRequested();
var where = string.IsNullOrWhiteSpace(extraWhere)
? "tenant_id=@__tenant"
: $"tenant_id=@__tenant AND {extraWhere}";
await db.Ado.BeginTranAsync();
try
{
await db.Ado.ExecuteCommandAsync(
$"DELETE FROM `{stdTable}` WHERE {where}",
new SugarParameter("@__tenant", tenantId));
var rows = await insertScopedAsync();
await db.Ado.CommitTranAsync();
return rows;
}
catch
{
await db.Ado.RollbackTranAsync();
throw;
}
}
}