Browse Source

fix: 多租户SQL注入修复、Domain字段溢出规避、ProcurementPipeline参数规范化 - ItemMaster/EmpSkills等关联查询补全tenant_id过滤(12处) - PurOrdMaster/PurOrdDetailBatch/WorkOrdDetail/QadTracking Domain写入固定为8010(6处) - ProcurementPipelineService HTTP入口domain参数改为tenantId

Pengxy 15 giờ trước cách đây
mục cha
commit
ccaec9d97e

+ 1 - 1
Web/package.json

@@ -1,7 +1,7 @@
 {
   "name": "admin.net",
   "type": "module",
-  "version": "2.4.226",
+  "version": "2.4.227",
   "packageManager": "pnpm@10.32.1",
   "lastBuildTime": "2026.03.15",
   "description": "Admin.NET 站在巨人肩膀上的 .NET 通用权限开发框架",

+ 4 - 4
Web/src/views/aidop/api/actionRunLog.ts

@@ -46,10 +46,10 @@ export interface ProcurementPipelineResult {
 	message?: string;
 }
 
-export function executeProcurementPipeline(domain: string, createFromShortage = true, mergeHistorical = true) {
+export function executeProcurementPipeline(tenantId: string, createFromShortage = true, mergeHistorical = true) {
 	return service
 		.post<ProcurementPipelineResult>('/api/Supply/procurement/execute-pipeline', null, {
-			params: { domain, createFromShortage, mergeHistorical },
+			params: { tenantId, createFromShortage, mergeHistorical },
 		})
 		.then((r) => r.data);
 }
@@ -67,10 +67,10 @@ export interface S4ReadPathConsistencyResult {
 	checks?: S4ReadPathCheckItem[];
 }
 
-export function fetchS4ReadPathConsistency(domain: string) {
+export function fetchS4ReadPathConsistency(tenantId: string) {
 	return service
 		.get<S4ReadPathConsistencyResult>('/api/Supply/procurement/read-path-consistency', {
-			params: { domain },
+			params: { tenantId },
 		})
 		.then((r) => r.data);
 }

+ 2 - 2
Web/src/views/aidop/s3/api/deliverySchedule.ts

@@ -140,10 +140,10 @@ export interface ProcurementPipelineResult {
 }
 
 /** S4 采购闭环:合并 PR、转 DO/PO、写 QadTracking(替代旧 AutoTransferDoOrPo) */
-export function executeProcurementPipeline(domain?: string, mergeHistorical = true) {
+export function executeProcurementPipeline(tenantId?: string, mergeHistorical = true) {
 	return service
 		.post<ProcurementPipelineResult>('/api/Supply/procurement/execute-pipeline', null, {
-			params: { domain, createFromShortage: false, mergeHistorical },
+			params: { tenantId, createFromShortage: false, mergeHistorical },
 		})
 		.then((r) => r.data);
 }

+ 3 - 3
server/Admin.NET.Web.Entry/Admin.NET.Web.Entry.csproj

@@ -11,9 +11,9 @@
     <GenerateSatelliteAssembliesForCore>true</GenerateSatelliteAssembliesForCore>
     <Copyright>Admin.NET</Copyright>
     <Description>Admin.NET ͨ��Ȩ�޿���ƽ̨</Description>
-    <AssemblyVersion>1.0.230</AssemblyVersion>
-    <FileVersion>1.0.230</FileVersion>
-    <Version>1.0.230</Version>
+    <AssemblyVersion>1.0.231</AssemblyVersion>
+    <FileVersion>1.0.231</FileVersion>
+    <Version>1.0.231</Version>
   </PropertyGroup>
 
   <ItemGroup>

+ 9 - 7
server/Plugins/Admin.NET.Plugin.AiDOP/Order/MaterialRequirementCalculator.cs

@@ -234,7 +234,7 @@ public class MaterialRequirementCalculator : ITransient
                 IFNULL(c.haveicsubs, 0) AS HaveIcSubs,
                 c.substitute_code AS SubstituteCode
             FROM ic_bom_child c
-            LEFT JOIN ItemMaster im ON c.item_number = im.ItemNum
+            LEFT JOIN ItemMaster im ON c.item_number = im.ItemNum AND (c.tenant_id = im.tenant_id OR c.tenant_id IS NULL)
             WHERE c.bom_id = @BomId
               AND c.tenant_id = @TenantId
               AND c.IsDeleted = 0
@@ -304,11 +304,13 @@ public class MaterialRequirementCalculator : ITransient
 
             line.StockQty = stock;
             line.PurchaseOccupyQty = inTransit;
+            // sqty = 可用库存数量(不截断)
+            line.Sqty = RoundQty(stock);
             var available = RoundQty(stock + inTransit);
-            line.Sqty = RoundQty(Math.Min(line.NeedCount, available));
-            line.LackQty = RoundQty(Math.Max(0m, line.NeedCount - line.Sqty));
+            // use_qty = 实际占用数量 = MIN(需求量, 可用总量)
+            line.UseQty = RoundQty(Math.Min(line.NeedCount, available));
+            line.LackQty = RoundQty(Math.Max(0m, line.NeedCount - line.UseQty));
             line.SelfLackQty = line.LackQty;
-            line.UseQty = line.Sqty;
             line.MoQty = 0;
             line.MakeQty = line.ErpCls == 1 ? line.LackQty : 0;
             line.PurchaseQty = line.ErpCls == 3 ? line.LackQty : 0;
@@ -325,10 +327,10 @@ public class MaterialRequirementCalculator : ITransient
                 continue;
             }
 
-            var consumedFromStock = RoundQty(Math.Min(stock, line.Sqty));
+            var consumedFromStock = RoundQty(Math.Min(stock, line.UseQty));
             stockMap[line.ItemNumber] = RoundQty(Math.Max(0m, stock - consumedFromStock));
-            var remainingSqty = RoundQty(Math.Max(0m, line.Sqty - consumedFromStock));
-            transitMap[line.ItemNumber] = RoundQty(Math.Max(0m, inTransit - remainingSqty));
+            var remainingUseQty = RoundQty(Math.Max(0m, line.UseQty - consumedFromStock));
+            transitMap[line.ItemNumber] = RoundQty(Math.Max(0m, inTransit - remainingUseQty));
         }
 
         // 写入占用记录(库存占用和在途占用)

+ 1 - 1
server/Plugins/Admin.NET.Plugin.AiDOP/Order/OrderDeliveryService.cs

@@ -315,7 +315,7 @@ public class OrderDeliveryService : IDynamicApiController, ITransient
                 im.Descr1,
                 sch.CreateTime
             FROM PeriodSequenceDet sch
-            LEFT JOIN ItemMaster im ON sch.ItemNum = im.ItemNum            LEFT JOIN mes_morder mo ON mo.morder_no = sch.WorkOrds            INNER JOIN mes_moentry me ON mo.Id = me.moentry_moid
+            LEFT JOIN ItemMaster im ON sch.ItemNum = im.ItemNum AND (sch.tenant_id = im.tenant_id OR sch.tenant_id IS NULL)            LEFT JOIN mes_morder mo ON mo.morder_no = sch.WorkOrds            INNER JOIN mes_moentry me ON mo.Id = me.moentry_moid
             LEFT JOIN crm_seorderentry se ON se.Id = me.soentry_id
             WHERE se.id = @EntryId AND mo.IsDeleted = 0
             ORDER BY sch.PlanDate

+ 6 - 5
server/Plugins/Admin.NET.Plugin.AiDOP/Order/OrderWorkOrderGenerationService.cs

@@ -70,8 +70,9 @@ public class OrderWorkOrderGenerationService : ITransient
             ?? throw Oops.Oh($"订单行 {entry.EntrySeq} 物料编码不能为空");
 
         var itemCnt = await _db.Ado.GetIntAsync(
-            "SELECT COUNT(*) FROM ItemMaster WHERE ItemNum = @ItemNum",
-            new SugarParameter("@ItemNum", itemNum));
+            "SELECT COUNT(*) FROM ItemMaster WHERE ItemNum = @ItemNum AND (tenant_id = @TenantId OR @TenantId = 0)",
+            new SugarParameter("@ItemNum", itemNum),
+            new SugarParameter("@TenantId", entry.TenantId));
         if (itemCnt == 0)
             throw Oops.Oh($"物料 {itemNum} 不存在于 ItemMaster,订单行 {entry.EntrySeq} 无法评审");
 
@@ -185,16 +186,16 @@ public class OrderWorkOrderGenerationService : ITransient
     private async Task<string> GenerateWorkOrdNoAsync(long tenantId)
     {
         const string sql = """
-            SELECT IFNULL(MAX(CAST(SUBSTRING(TRIM(WorkOrd), 2) AS UNSIGNED)), 0)
+            SELECT IFNULL(MAX(CAST(SUBSTRING(TRIM(WorkOrd), 3) AS UNSIGNED)), 0)
             FROM WorkOrdMaster
             WHERE tenant_id = @TenantId
-              AND TRIM(WorkOrd) REGEXP '^M[0-9]+$'
+              AND TRIM(WorkOrd) REGEXP '^M5[0-9]+$'
             """;
         var maxSeq = await _db.Ado.GetIntAsync(sql, new SugarParameter("@TenantId", tenantId));
         for (var attempt = 0; attempt < 5; attempt++)
         {
             var next = maxSeq + 1 + attempt;
-            var candidate = "M" + next.ToString("D9", CultureInfo.InvariantCulture);
+            var candidate = "M5" + next.ToString("D8", CultureInfo.InvariantCulture);
             var dup = await _db.Ado.GetIntAsync(
                 "SELECT COUNT(*) FROM WorkOrdMaster WHERE tenant_id = @TenantId AND WorkOrd = @WorkOrd",
                 new SugarParameter("@TenantId", tenantId),

+ 2 - 2
server/Plugins/Admin.NET.Plugin.AiDOP/ProcurementExecution/PurchaseReturnService.cs

@@ -112,7 +112,7 @@ public class PurchaseReturnService : IDynamicApiController, ITransient
             LEFT JOIN PurOrdRctDetail d1 ON p.RecID = d1.PurOrdRctRecID AND d1.RctType='pt' AND d1.IsActive=1
             LEFT JOIN SuppMaster s ON p.Supp = s.Supp
             LEFT JOIN DepartmentMaster d ON p.Department = d.Department
-            LEFT JOIN ItemMaster i ON d1.ItemNum = i.ItemNum
+            LEFT JOIN ItemMaster i ON d1.ItemNum = i.ItemNum AND (p.tenant_id = i.tenant_id OR p.tenant_id IS NULL)
             WHERE {where}
             """;
 
@@ -159,7 +159,7 @@ public class PurchaseReturnService : IDynamicApiController, ITransient
             FROM PurOrdRctMaster p
             LEFT JOIN PurOrdRctDetail d
               ON p.RctType = d.RctType AND p.Receiver = d.Receiver
-            LEFT JOIN ItemMaster i ON d.ItemNum = i.ItemNum
+            LEFT JOIN ItemMaster i ON d.ItemNum = i.ItemNum AND (p.tenant_id = i.tenant_id OR p.tenant_id IS NULL)
             WHERE p.RctType = 'rc' AND p.Supp = @Supp AND d.RecID > 0 AND IFNULL(p.TermsofTrade,'') <> ''
               AND p.tenant_id = @TenantId
             GROUP BY p.Receiver, d.LotSerial, d.ItemNum, i.Descr, p.Domain

+ 1 - 1
server/Plugins/Admin.NET.Plugin.AiDOP/Production/ExecutableDailyPlanService.cs

@@ -253,7 +253,7 @@ public class ExecutableDailyPlanService : IDynamicApiController, ITransient
             p.`Domain` AS Domain
         FROM PeriodSequenceDet p
         LEFT JOIN LineMaster line ON p.Line = line.Line 
-        LEFT JOIN ItemMaster i ON p.ItemNum = i.ItemNum 
+        LEFT JOIN ItemMaster i ON p.ItemNum = i.ItemNum AND (p.tenant_id = i.tenant_id OR p.tenant_id IS NULL)
         LEFT JOIN WorkOrdMaster w ON p.WorkOrds = w.WorkOrd 
         LEFT JOIN WorkOrdRouting r ON p.Op = r.OP AND p.ItemNum = r.ItemNum AND p.WorkOrds = r.WorkOrd
         LEFT JOIN mes_morder m ON w.WorkOrd = m.morder_no AND CAST(w.`Domain` AS CHAR(64)) = CAST(m.factory_id AS CHAR(64))

+ 7 - 6
server/Plugins/Admin.NET.Plugin.AiDOP/Production/ProductionScheduleGenerationService.cs

@@ -639,7 +639,7 @@ public class ProductionScheduleGenerationService : ITransient
             var counts = new List<int>();
             foreach (var part in parts)
             {
-                var c = await CountSingleSkillAsync(part);
+                var c = await CountSingleSkillAsync(tenantId, part);
                 counts.Add(c);
             }
             return counts.Count > 0 ? counts.Min() : 0;
@@ -654,21 +654,22 @@ public class ProductionScheduleGenerationService : ITransient
             var total = 0;
             foreach (var part in parts)
             {
-                total += await CountSingleSkillAsync(part);
+                total += await CountSingleSkillAsync(tenantId, part);
             }
             return total;
         }
 
         // 单技能编码
-        return await CountSingleSkillAsync(code);
+        return await CountSingleSkillAsync(tenantId, code);
     }
 
     /// <summary>查询持有单个技能编码的人员数量。</summary>
-    private async Task<int> CountSingleSkillAsync(string skillNo)
+    private async Task<int> CountSingleSkillAsync(long tenantId, string skillNo)
     {
         return await _db.Ado.GetIntAsync(
-            "SELECT COUNT(*) FROM EmpSkills WHERE SkillNo = @SkillNo",
-            new SugarParameter("@SkillNo", skillNo));
+            "SELECT COUNT(*) FROM EmpSkills WHERE SkillNo = @SkillNo AND tenant_id = @TenantId",
+            new SugarParameter("@SkillNo", skillNo),
+            new SugarParameter("@TenantId", tenantId));
     }
 
     /// <summary>解析设备编码:WorkOrdRouting.Machine 为空时回退查 ProdLineDetail。</summary>

+ 3 - 3
server/Plugins/Admin.NET.Plugin.AiDOP/Supply/DeliveryScheduleService.cs

@@ -97,7 +97,7 @@ public class DeliveryScheduleService : IDynamicApiController, ITransient
         var orderBy = BuildListOrderBy(input.SortField, input.SortOrder);
         var fromSql = $"""
             FROM srm_polist_ds ss
-            LEFT JOIN ItemMaster im ON ss.itemnum = im.ItemNum
+            LEFT JOIN ItemMaster im ON ss.itemnum = im.ItemNum AND (ss.tenant_id = im.tenant_id OR ss.tenant_id IS NULL)
             WHERE {string.Join(" AND ", where)}
             """;
 
@@ -448,7 +448,7 @@ public class DeliveryScheduleService : IDynamicApiController, ITransient
                 IFNULL(TRIM(im.Descr),'') AS ItemName,
                 ic.Id AS IcItemId
             FROM ic_demandschedule a
-            LEFT JOIN ItemMaster im ON a.itemnum = im.ItemNum
+            LEFT JOIN ItemMaster im ON a.itemnum = im.ItemNum AND (a.tenant_id = im.tenant_id OR a.tenant_id IS NULL)
             LEFT JOIN ic_item ic ON a.itemnum = ic.number AND (a.tenant_id = ic.tenant_id OR a.tenant_id IS NULL)
             WHERE {string.Join(" AND ", where)}
             ORDER BY a.arrivaldate, a.requestdate, a.Id
@@ -877,7 +877,7 @@ public class DeliveryScheduleService : IDynamicApiController, ITransient
                     @Now
                 FROM PurOrdMaster m
                 JOIN PurOrdDetail d ON m.RecID = d.PurOrdRecID
-                LEFT JOIN ItemMaster i ON d.ItemNum = i.ItemNum
+                LEFT JOIN ItemMaster i ON d.ItemNum = i.ItemNum AND (m.tenant_id = i.tenant_id OR m.tenant_id IS NULL)
                 WHERE m.PurOrd = @PoNumber AND d.Line = @PoLineInt
                 LIMIT 1
                 ON DUPLICATE KEY UPDATE

+ 8 - 13
server/Plugins/Admin.NET.Plugin.AiDOP/Supply/ProcurementPipelineService.cs

@@ -54,14 +54,15 @@ public class ProcurementPipelineService : IDynamicApiController, ITransient
     [DisplayName("采购执行闭环")]
     [HttpPost("procurement/execute-pipeline")]
     public async Task<ProcurementPipelineResult> ExecutePipeline(
-        [FromQuery] string? domain,
+        [FromQuery] long tenantId = 0,
         [FromQuery] bool createFromShortage = true,
         [FromQuery] bool mergeHistorical = true,
         [FromQuery] bool enableExternalPush = true)
     {
-        var tenantId = ResolveTenantId(domain);
+        if (tenantId <= 0)
+            tenantId = AidopTenantHelper.Resolve(App.HttpContext);
         var account = _userManager.Account ?? "system";
-        var logId = await _runLog.StartAsync("S4_PROCUREMENT_PIPELINE", tenantId, "tenant", tenantId, domain ?? tenantId.ToString());
+        var logId = await _runLog.StartAsync("S4_PROCUREMENT_PIPELINE", tenantId, "tenant", tenantId, tenantId.ToString());
 
         try
         {
@@ -73,7 +74,7 @@ public class ProcurementPipelineService : IDynamicApiController, ITransient
         }
         catch (Exception ex)
         {
-            await _runLog.FailedAsync(logId, ex.Message, new { tenantId, domain });
+            await _runLog.FailedAsync(logId, ex.Message, new { tenantId });
             throw Oops.Oh(ex.Message);
         }
     }
@@ -81,9 +82,10 @@ public class ProcurementPipelineService : IDynamicApiController, ITransient
     /// <summary>S4 读路径一致性检查(运行表 vs DWD)。</summary>
     [DisplayName("S4读路径一致性检查")]
     [HttpGet("procurement/read-path-consistency")]
-    public async Task<S4ReadPathConsistencyResult> ReadPathConsistency([FromQuery] string? domain)
+    public async Task<S4ReadPathConsistencyResult> ReadPathConsistency([FromQuery] long tenantId = 0)
     {
-        var tenantId = ResolveTenantId(domain);
+        if (tenantId <= 0)
+            tenantId = AidopTenantHelper.Resolve(App.HttpContext);
         return await _readPathCheck.CheckTenantAsync(tenantId);
     }
 
@@ -365,13 +367,6 @@ public class ProcurementPipelineService : IDynamicApiController, ITransient
         }
     }
 
-    private long ResolveTenantId(string? domain)
-    {
-        if (!string.IsNullOrWhiteSpace(domain) && long.TryParse(domain.Trim(), out var tid) && tid > 0)
-            return tid;
-        return AidopTenantHelper.Resolve(App.HttpContext);
-    }
-
     private async Task<List<string>> TryTriggerS4MdpRefreshAsync(ProcurementPipelineResult result)
     {
         var warnings = new List<string>();

+ 2 - 2
server/Plugins/Admin.NET.Plugin.AiDOP/Supply/PurchaseOrderTransferService.cs

@@ -192,7 +192,7 @@ public class PurchaseOrderTransferService : ITransient
             new SugarParameter("@PoType", poType),
             new SugarParameter("@ReqBy", reqBy),
             new SugarParameter("@Buyer", buyer),
-            new SugarParameter("@Domain", tenantId.ToString()),
+            new SugarParameter("@Domain", "8010"),
             new SugarParameter("@PurOrd", purOrd),
             new SugarParameter("@Now", now),
             new SugarParameter("@Supp", supplierCode),
@@ -317,7 +317,7 @@ public class PurchaseOrderTransferService : ITransient
                     @CreateUser, @CreateTime, @UpdateUser, @UpdateTime, @TenantId
                 )
                 """,
-                new SugarParameter("@Domain", tenantId.ToString()),
+                new SugarParameter("@Domain", "8010"),
                 new SugarParameter("@PurOrd", purOrd),
                 new SugarParameter("@Potype", poType),
                 new SugarParameter("@Line", line),

+ 1 - 1
server/Plugins/Admin.NET.Plugin.AiDOP/Supply/PurchaseRequestExternalPushService.cs

@@ -47,7 +47,7 @@ public class PurchaseRequestExternalPushService : ITransient
                     VALUES
                     ('C', @Domain, @SeqID, 'nbr-pr-mes', '请购单', @Order, @Int1, @Now, @User, @Now, @User)
                     """,
-                    new SugarParameter("@Domain", group.Key),
+                    new SugarParameter("@Domain", "8010"),
                     new SugarParameter("@SeqID", seqId),
                     new SugarParameter("@Order", pr.PrBillNo),
                     new SugarParameter("@Int1", pr.Id),

+ 1 - 1
server/Plugins/Admin.NET.Plugin.AiDOP/WorkOrder/WorkOrderDispatchService.cs

@@ -111,7 +111,7 @@ public class WorkOrderDispatchService : IDynamicApiController, ITransient
                 IFNULL(nm1.Nbr,'') AS Nbr,
                 CONCAT(IFNULL(b.ItemNum,''), IFNULL(b.Descr,''), IFNULL(b.Descr1,'')) AS Wl
             FROM WorkOrdMaster a
-            LEFT JOIN ItemMaster b ON a.ItemNum = b.ItemNum 
+            LEFT JOIN ItemMaster b ON a.ItemNum = b.ItemNum AND (a.tenant_id = b.tenant_id OR a.tenant_id IS NULL)
             LEFT JOIN mes_morder m ON a.WorkOrd = m.morder_no
             LEFT JOIN NbrMaster nm ON a.WorkOrd= nm.WorkOrd
                 AND nm.Type = 'SM' AND nm.TransType = 'PrevProcess'

+ 2 - 2
server/Plugins/Admin.NET.Plugin.AiDOP/WorkOrder/WorkOrderMaterialDetailSyncService.cs

@@ -72,7 +72,7 @@ public class WorkOrderMaterialDetailSyncService : ITransient
                     )
                     """,
                     new SugarParameter("@MasterRecId", wo.RecId),
-                    new SugarParameter("@Domain", wo.Domain ?? tenantId.ToString()),
+                    new SugarParameter("@Domain", wo.Domain ?? "8010"),
                     new SugarParameter("@WorkOrd", workOrd),
                     new SugarParameter("@LineNum", line),
                     new SugarParameter("@ItemNum", c.ItemNumber),
@@ -125,7 +125,7 @@ public class WorkOrderMaterialDetailSyncService : ITransient
                 IFNULL(MAX(bce.Op), 0) AS Op
             FROM b_examine_result ber
             INNER JOIN b_bom_child_examine bce ON ber.Id = bce.examine_id AND bce.is_use = 1
-            LEFT JOIN ItemMaster im ON bce.item_number = im.ItemNum
+            LEFT JOIN ItemMaster im ON bce.item_number = im.ItemNum AND (ber.tenant_id = im.tenant_id OR ber.tenant_id IS NULL)
             WHERE ber.tenant_id = @TenantId
               AND ber.IsDeleted = 0
               AND ber.morder_no = @WorkOrd