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