using Admin.NET.Plugin.AiDOP.Entity.S0.Manufacturing;
using Admin.NET.Plugin.AiDOP.Entity.S0.Sales;
using Admin.NET.Plugin.AiDOP.Entity.S0.Supply;
using Admin.NET.Plugin.AiDOP.Entity.S0.Warehouse;
using Microsoft.Extensions.Logging;
using SqlSugar;
namespace Admin.NET.Plugin.AiDOP.Infrastructure;
///
/// S0 删除前下游引用检查。
/// 每个主数据主表在 DELETE 前调用对应方法,若存在下游引用则拒绝删除。
/// 返回 null 表示无引用可安全删除;非空表示 (引用数, 下游表语义描述)。
///
public sealed class AdoS0ReferenceChecker
{
private readonly ISqlSugarClient _db;
private readonly ILogger _logger;
public AdoS0ReferenceChecker(ISqlSugarClient db, ILogger logger)
{
_db = db;
_logger = logger;
}
///
/// 容错包装:对已知 schema 不对齐表(如 srm_purchase)的查询失败时不阻断删除,只记日志。
/// 独立问题由 BUG-S0-SRMPURCHASE-SCHEMA-MISMATCH 单独修。
///
private async Task SafeCountAsync(Func> queryFactory, string tag) where T : class, new()
{
try
{
return await queryFactory().CountAsync();
}
catch (Exception ex)
{
_logger.LogWarning(ex, "[AdoS0ReferenceChecker] skip {Tag} check due to schema mismatch", tag);
return 0;
}
}
///
/// 保存端:物料(ItemMaster)存在性检查。
/// 仅校验"目标主表存在该 itemNum",不做 factoryRefId/domain 作用域校验(B2 归口)。
/// 空值视为"未填"直接放行,由调用方先做必填校验。
///
public async Task MaterialExistsAsync(string? itemNum)
{
if (string.IsNullOrWhiteSpace(itemNum)) return true;
return await _db.Queryable()
.Where(x => x.ItemNum == itemNum)
.AnyAsync();
}
///
/// 保存端:工作中心(WorkCtrMaster)存在性检查。
/// 注意跨命名:业务键是 WorkCtrMaster.WorkCtr(非 WorkCenterCode)。
/// 空值视为"未填"直接放行;作用域(Domain/Factory)一致性归 B2。
///
public async Task WorkCenterExistsAsync(string? workCenterCode)
{
if (string.IsNullOrWhiteSpace(workCenterCode)) return true;
return await _db.Queryable()
.Where(x => x.WorkCtr == workCenterCode)
.AnyAsync();
}
///
/// 物料(ItemMaster)删除前引用检查。
/// 覆盖下游:工艺路线明细 / 物料替代关系 / 货源清单(SRM 采购)。
///
public async Task<(int Count, string Table)?> MaterialReferencesAsync(string? itemNum)
{
if (string.IsNullOrWhiteSpace(itemNum)) return null;
var routing = await _db.Queryable()
.Where(x => x.MaterialCode == itemNum)
.CountAsync();
if (routing > 0) return (routing, "工艺路线明细 (RoutingOpDetail)");
var sub = await _db.Queryable()
.Where(x => x.ItemNum == itemNum || x.SubstituteItem == itemNum)
.CountAsync();
if (sub > 0) return (sub, "物料替代关系 (ItemSubstituteDetail)");
var srm = await SafeCountAsync(
() => _db.Queryable().Where(x => x.MaterialCode == itemNum),
"SrmPurchase.MaterialCode");
if (srm > 0) return (srm, "货源清单 (SrmPurchase)");
return null;
}
///
/// 供应商(SuppMaster)删除前引用检查。
/// 覆盖下游:货源清单(SRM 采购)。
///
public async Task<(int Count, string Table)?> SupplierReferencesAsync(string? supp)
{
if (string.IsNullOrWhiteSpace(supp)) return null;
var srm = await SafeCountAsync(
() => _db.Queryable().Where(x => x.Supplier == supp || x.SupplierNumber == supp),
"SrmPurchase.Supplier");
if (srm > 0) return (srm, "货源清单 (SrmPurchase)");
return null;
}
///
/// 库位(LocationMaster)删除前引用检查。
/// 覆盖下游:货架 / 物料默认库位。
///
public async Task<(int Count, string Table)?> LocationReferencesAsync(string? location)
{
if (string.IsNullOrWhiteSpace(location)) return null;
var shelf = await _db.Queryable()
.Where(x => x.Location == location)
.CountAsync();
if (shelf > 0) return (shelf, "货架 (LocationShelfMaster)");
var item = await _db.Queryable()
.Where(x => x.Location == location)
.CountAsync();
if (item > 0) return (item, "物料默认库位 (ItemMaster.Location)");
return null;
}
///
/// 部门(DepartmentMaster)删除前引用检查。
/// 覆盖下游:员工所属部门 / 工作中心所属部门。
///
public async Task<(int Count, string Table)?> DepartmentReferencesAsync(string? department)
{
if (string.IsNullOrWhiteSpace(department)) return null;
var emp = await _db.Queryable()
.Where(x => x.Department == department)
.CountAsync();
if (emp > 0) return (emp, "员工 (EmployeeMaster)");
var wc = await _db.Queryable()
.Where(x => x.Department == department)
.CountAsync();
if (wc > 0) return (wc, "工作中心 (WorkCtrMaster)");
return null;
}
}