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; } }