AdoS0ReferenceChecker.cs 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. using Admin.NET.Plugin.AiDOP.Entity.S0.Manufacturing;
  2. using Admin.NET.Plugin.AiDOP.Entity.S0.Sales;
  3. using Admin.NET.Plugin.AiDOP.Entity.S0.Supply;
  4. using Admin.NET.Plugin.AiDOP.Entity.S0.Warehouse;
  5. using Microsoft.Extensions.Logging;
  6. using SqlSugar;
  7. namespace Admin.NET.Plugin.AiDOP.Infrastructure;
  8. /// <summary>
  9. /// S0 删除前下游引用检查。
  10. /// 每个主数据主表在 DELETE 前调用对应方法,若存在下游引用则拒绝删除。
  11. /// 返回 null 表示无引用可安全删除;非空表示 (引用数, 下游表语义描述)。
  12. /// </summary>
  13. public sealed class AdoS0ReferenceChecker
  14. {
  15. private readonly ISqlSugarClient _db;
  16. private readonly ILogger<AdoS0ReferenceChecker> _logger;
  17. public AdoS0ReferenceChecker(ISqlSugarClient db, ILogger<AdoS0ReferenceChecker> logger)
  18. {
  19. _db = db;
  20. _logger = logger;
  21. }
  22. /// <summary>
  23. /// 容错包装:对已知 schema 不对齐表(如 srm_purchase)的查询失败时不阻断删除,只记日志。
  24. /// 独立问题由 BUG-S0-SRMPURCHASE-SCHEMA-MISMATCH 单独修。
  25. /// </summary>
  26. private async Task<int> SafeCountAsync<T>(Func<ISugarQueryable<T>> queryFactory, string tag) where T : class, new()
  27. {
  28. try
  29. {
  30. return await queryFactory().CountAsync();
  31. }
  32. catch (Exception ex)
  33. {
  34. _logger.LogWarning(ex, "[AdoS0ReferenceChecker] skip {Tag} check due to schema mismatch", tag);
  35. return 0;
  36. }
  37. }
  38. /// <summary>
  39. /// 保存端:物料(ItemMaster)存在性检查。
  40. /// 仅校验"目标主表存在该 itemNum",不做 factoryRefId/domain 作用域校验(B2 归口)。
  41. /// 空值视为"未填"直接放行,由调用方先做必填校验。
  42. /// </summary>
  43. public async Task<bool> MaterialExistsAsync(string? itemNum)
  44. {
  45. if (string.IsNullOrWhiteSpace(itemNum)) return true;
  46. return await _db.Queryable<AdoS0ItemMaster>()
  47. .Where(x => x.ItemNum == itemNum)
  48. .AnyAsync();
  49. }
  50. /// <summary>
  51. /// 保存端:工作中心(WorkCtrMaster)存在性检查。
  52. /// 注意跨命名:业务键是 WorkCtrMaster.WorkCtr(非 WorkCenterCode)。
  53. /// 空值视为"未填"直接放行;作用域(Domain/Factory)一致性归 B2。
  54. /// </summary>
  55. public async Task<bool> WorkCenterExistsAsync(string? workCenterCode)
  56. {
  57. if (string.IsNullOrWhiteSpace(workCenterCode)) return true;
  58. return await _db.Queryable<AdoS0WorkCtrMaster>()
  59. .Where(x => x.WorkCtr == workCenterCode)
  60. .AnyAsync();
  61. }
  62. /// <summary>
  63. /// 物料(ItemMaster)删除前引用检查。
  64. /// 覆盖下游:工艺路线明细 / 物料替代关系 / 货源清单(SRM 采购)。
  65. /// </summary>
  66. public async Task<(int Count, string Table)?> MaterialReferencesAsync(string? itemNum)
  67. {
  68. if (string.IsNullOrWhiteSpace(itemNum)) return null;
  69. var routing = await _db.Queryable<AdoS0MfgRoutingOpDetail>()
  70. .Where(x => x.MaterialCode == itemNum)
  71. .CountAsync();
  72. if (routing > 0) return (routing, "工艺路线明细 (RoutingOpDetail)");
  73. var sub = await _db.Queryable<AdoS0ItemSubstituteDetail>()
  74. .Where(x => x.ItemNum == itemNum || x.SubstituteItem == itemNum)
  75. .CountAsync();
  76. if (sub > 0) return (sub, "物料替代关系 (ItemSubstituteDetail)");
  77. var srm = await SafeCountAsync(
  78. () => _db.Queryable<AdoS0SrmPurchase>().Where(x => x.MaterialCode == itemNum),
  79. "SrmPurchase.MaterialCode");
  80. if (srm > 0) return (srm, "货源清单 (SrmPurchase)");
  81. return null;
  82. }
  83. /// <summary>
  84. /// 供应商(SuppMaster)删除前引用检查。
  85. /// 覆盖下游:货源清单(SRM 采购)。
  86. /// </summary>
  87. public async Task<(int Count, string Table)?> SupplierReferencesAsync(string? supp)
  88. {
  89. if (string.IsNullOrWhiteSpace(supp)) return null;
  90. var srm = await SafeCountAsync(
  91. () => _db.Queryable<AdoS0SrmPurchase>().Where(x => x.Supplier == supp || x.SupplierNumber == supp),
  92. "SrmPurchase.Supplier");
  93. if (srm > 0) return (srm, "货源清单 (SrmPurchase)");
  94. return null;
  95. }
  96. /// <summary>
  97. /// 库位(LocationMaster)删除前引用检查。
  98. /// 覆盖下游:货架 / 物料默认库位。
  99. /// </summary>
  100. public async Task<(int Count, string Table)?> LocationReferencesAsync(string? location)
  101. {
  102. if (string.IsNullOrWhiteSpace(location)) return null;
  103. var shelf = await _db.Queryable<AdoS0LocationShelfMaster>()
  104. .Where(x => x.Location == location)
  105. .CountAsync();
  106. if (shelf > 0) return (shelf, "货架 (LocationShelfMaster)");
  107. var item = await _db.Queryable<AdoS0ItemMaster>()
  108. .Where(x => x.Location == location)
  109. .CountAsync();
  110. if (item > 0) return (item, "物料默认库位 (ItemMaster.Location)");
  111. return null;
  112. }
  113. /// <summary>
  114. /// 部门(DepartmentMaster)删除前引用检查。
  115. /// 覆盖下游:员工所属部门 / 工作中心所属部门。
  116. /// </summary>
  117. public async Task<(int Count, string Table)?> DepartmentReferencesAsync(string? department)
  118. {
  119. if (string.IsNullOrWhiteSpace(department)) return null;
  120. var emp = await _db.Queryable<AdoS0EmployeeMaster>()
  121. .Where(x => x.Department == department)
  122. .CountAsync();
  123. if (emp > 0) return (emp, "员工 (EmployeeMaster)");
  124. var wc = await _db.Queryable<AdoS0WorkCtrMaster>()
  125. .Where(x => x.Department == department)
  126. .CountAsync();
  127. if (wc > 0) return (wc, "工作中心 (WorkCtrMaster)");
  128. return null;
  129. }
  130. }