Просмотр исходного кода

feat: 多处性能优化与功能修复 - 要货令/委外加工订单列表前后端并行加载与SQL优化 - 委外加工转单BOM字段QtyBO/QtyReleased赋值修复 - 工单物料明细表单库位必填 - 发货单子表生产日期/生产批号必填校验 - 发货单表单取消时正确关闭tagsView标签

Pengxy 1 день назад
Родитель
Сommit
8966f4aee6

+ 1 - 1
Web/package.json

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

+ 1 - 0
Web/src/views/aidop/production/workOrderMaterialDetailForm.vue

@@ -86,6 +86,7 @@ const rules: FormRules = {
 	workOrd: [{ required: true, message: '请选择工单编号', trigger: 'change' }],
 	itemNum: [{ required: true, message: '请输入物料号', trigger: 'blur' }],
 	qtyRequired: [{ required: true, message: '请输入需求数量', trigger: 'blur' }],
+	location: [{ required: true, message: '请输入库位', trigger: 'blur' }],
 };
 
 function onWorkOrdChange() {

+ 2 - 2
Web/src/views/aidop/s3/supply/demandOrderForm.vue

@@ -313,12 +313,12 @@ async function onSave() {
 }
 
 onMounted(async () => {
-	await loadOptions();
 	if (props.mode === 'create') {
+		await loadOptions();
 		form.ordDate = new Date().toISOString().slice(0, 10);
 		onUsageChange();
 	} else {
-		await loadDetail();
+		await Promise.all([loadOptions(), loadDetail()]);
 	}
 });
 </script>

+ 5 - 4
Web/src/views/aidop/s3/supply/demandOrderList.vue

@@ -267,10 +267,11 @@ async function onSaved() {
 }
 
 onMounted(async () => {
-	const [buyers, suppliers] = await Promise.all([fetchDemandOrderBuyers(), fetchDemandOrderSuppliers()]);
-	buyerOptions.value = buyers.list || [];
-	supplierOptions.value = suppliers.list || [];
-	await loadList();
+	const [buyers, suppliers] = await Promise.all([
+		fetchDemandOrderBuyers().then((r) => { buyerOptions.value = r.list || []; }),
+		fetchDemandOrderSuppliers().then((r) => { supplierOptions.value = r.list || []; }),
+		loadList(),
+	]);
 });
 </script>
 

+ 5 - 2
Web/src/views/aidop/s3/supply/outsourceOrderForm.vue

@@ -111,8 +111,11 @@ async function onSave() {
 }
 
 onMounted(async () => {
-	await loadOptions();
-	await loadDetail();
+	if (props.id) {
+		await Promise.all([loadOptions(), loadDetail()]);
+	} else {
+		await loadOptions();
+	}
 });
 </script>
 

+ 5 - 4
Web/src/views/aidop/s3/supply/outsourceOrderList.vue

@@ -150,10 +150,11 @@ function onSaved() {
 }
 
 onMounted(async () => {
-	const [buyers, suppliers] = await Promise.all([fetchOutsourceBuyers(), fetchOutsourceSuppliers()]);
-	buyerOptions.value = buyers.list || [];
-	supplierOptions.value = suppliers.list || [];
-	await loadList();
+	await Promise.all([
+		fetchOutsourceBuyers().then((r) => { buyerOptions.value = r.list || []; }),
+		fetchOutsourceSuppliers().then((r) => { supplierOptions.value = r.list || []; }),
+		loadList(),
+	]);
 });
 </script>
 

+ 32 - 2
Web/src/views/aidop/s4/delivery/supplierShipmentForm.vue

@@ -145,6 +145,7 @@ import ShipmentAttachment from './ShipmentAttachment.vue';
 import printDialog from '/@/views/system/print/component/hiprint/preview.vue';
 import { formatDate } from '/@/utils/formatTime';
 import { showSysPrintDialog } from './supplierHiprint';
+import mittBus from '/@/utils/mitt';
 
 const PRINT_SHIPPING_TEMPLATE_NAME = '送货单';
 const printShippingDialogRef = ref();
@@ -319,6 +320,18 @@ function onPrintShippingNote() {
 }
 
 async function onSave() {
+	// 校验生产日期和生产批号必填
+	for (let i = 0; i < form.details.length; i++) {
+		const row = form.details[i]!;
+		if (!row.scrq?.trim()) {
+			ElMessage.warning(`第 ${i + 1} 行请填写生产日期`);
+			return;
+		}
+		if (!row.scph?.trim()) {
+			ElMessage.warning(`第 ${i + 1} 行请填写生产批号`);
+			return;
+		}
+	}
 	// 校验送货数量不能超过待交数量
 	for (let i = 0; i < form.details.length; i++) {
 		const row = form.details[i]!;
@@ -352,8 +365,25 @@ async function onSave() {
 }
 
 function onCancel() {
-	if (props.embedded) emit('close');
-	else router.push('/aidop/s4/delivery/supplier-shipment');
+	if (props.embedded) {
+		emit('close');
+		return;
+	}
+	// 先保存当前路由信息,再跳转后通知 tagsView 关闭旧标签
+	const oldRoute = {
+		path: route.path,
+		name: route.name,
+		params: { ...route.params },
+		query: { ...route.query },
+		meta: { ...route.meta },
+		url: route.fullPath,
+	};
+	router.push('/aidop/s4/delivery/supplier-shipment').then(() => {
+		mittBus.emit('onCurrentContextmenuClick', {
+			...oldRoute,
+			contextMenuClickId: 1,
+		} as any);
+	});
 }
 
 onMounted(loadData);

+ 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.239</AssemblyVersion>
-    <FileVersion>1.0.239</FileVersion>
-    <Version>1.0.239</Version>
+    <AssemblyVersion>1.0.240</AssemblyVersion>
+    <FileVersion>1.0.240</FileVersion>
+    <Version>1.0.240</Version>
   </PropertyGroup>
 
   <ItemGroup>

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

@@ -285,9 +285,9 @@ public class ExecutableDailyPlanService : IDynamicApiController, ITransient
             "lotserial" => "t.LotSerial",
             _ => "t.PlanDate"
         };
-        // 默认(无排序参数时)按计划日期倒
+        // 默认(无排序参数时)按工单号、工序、计划日期升
         if (string.IsNullOrWhiteSpace(key))
-            return "t.PlanDate DESC, t.Id DESC";
+            return "t.WorkOrds ASC, t.Op ASC, t.PlanDate ASC";
         return $"{col} {dir}, t.Id DESC";
     }
 

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

@@ -94,8 +94,8 @@ public class ProductionScheduleGenerationService : ITransient
             var routings = await LoadRoutingsAsync(tenantId, wo.WorkOrd, wo.ItemNum);
             if (routings.Count == 0)
             {
-                skipped.Add($"{wo.WorkOrd}(无工艺路线)");
-                await InsertScheduleExceptionAsync(tenantId, wo, domain, "无工艺路线", "工单无工艺路线,无法生成排程");
+                skipped.Add($"{wo.WorkOrd}(无关键工序)");
+                await InsertScheduleExceptionAsync(tenantId, wo, domain, "无关键工序", "工单无关键工序(MilestoneOp=1),无法生成排程");
                 woSw.Stop();
                 otherMs += woSw.ElapsedMilliseconds;
                 if (woIndex % 5 == 0 || woIndex == workOrders.Count)
@@ -473,7 +473,7 @@ public class ProductionScheduleGenerationService : ITransient
                    IFNULL(r.StdSetupTime, 0) AS SetupTime
             FROM WorkOrdRouting r
             LEFT JOIN ProdLineDetail pd ON pd.Part = r.ItemNum AND pd.Op = r.OP
-            WHERE r.tenant_id = @TenantId AND r.WorkOrd = @WorkOrd AND IFNULL(r.IsActive, 0) = 1
+            WHERE r.tenant_id = @TenantId AND r.WorkOrd = @WorkOrd AND IFNULL(r.IsActive, 0) = 1 AND IFNULL(r.MilestoneOp, 0) = 1
             ORDER BY (r.OP + 0), r.OP
             """,
             new SugarParameter("@TenantId", tenantId),

+ 36 - 22
server/Plugins/Admin.NET.Plugin.AiDOP/Supply/DemandOrderService.cs

@@ -70,9 +70,10 @@ public class DemandOrderService : IDynamicApiController, ITransient
 
         var fromSql = $"""
             FROM PurOrdMaster p
-            LEFT JOIN SuppMaster s ON p.Supp=s.Supp
-            LEFT JOIN DepartmentMaster d ON p.Department=d.Department
-            LEFT JOIN EmployeeMaster e ON p.Buyer=e.Employee
+            LEFT JOIN SuppMaster s ON p.Supp=s.Supp AND s.tenant_id=p.tenant_id
+            LEFT JOIN DepartmentMaster d ON p.Department=d.Department AND d.tenant_id=p.tenant_id
+            LEFT JOIN EmployeeMaster e ON p.Buyer=e.Employee AND e.tenant_id=p.tenant_id
+            LEFT JOIN (SELECT DISTINCT PurOrd FROM PurOrdDetail WHERE IFNULL(RctQty,0)>0) pdhr ON pdhr.PurOrd=p.PurOrd
             WHERE {string.Join(" AND ", where)}
             """;
 
@@ -106,7 +107,7 @@ public class DemandOrderService : IDynamicApiController, ITransient
                 p.Department AS Department,
                 p.`Usage` AS `Usage`,
                 p.FSTID AS Fstid,
-                (CASE WHEN EXISTS(SELECT 1 FROM PurOrdDetail pd WHERE pd.PurOrd=p.PurOrd AND IFNULL(pd.RctQty,0)>0) THEN 1 ELSE 0 END) AS HasReceived
+                CASE WHEN pdhr.PurOrd IS NOT NULL THEN 1 ELSE 0 END AS HasReceived
             {fromSql}
             ORDER BY {BuildOrderBy(input.SortField, input.SortOrder)}
             LIMIT {pageSize} OFFSET {offset}
@@ -336,16 +337,25 @@ public class DemandOrderService : IDynamicApiController, ITransient
         return new { total, page, pageSize, list };
     }
 
+    private (string Where, List<SugarParameter> Pars) BuildTenantFilter()
+    {
+        var tenantId = _userManager.TenantId;
+        if (tenantId > 0)
+            return (" AND tenant_id = @TenantId", new List<SugarParameter> { new("@TenantId", tenantId) });
+        return ("", new List<SugarParameter>());
+    }
+
     [DisplayName("采购组下拉")]
     [HttpGet("demand-order/options/buyers")]
     public async Task<object> GetBuyerOptions()
     {
+        var (where, pars) = BuildTenantFilter();
         var list = await _db.Ado.SqlQueryAsync<OptionRow>(
-            """
+            $"""
             SELECT Employee AS Value, CONCAT(TRIM(Employee),' ',TRIM(IFNULL(Name,''))) AS Label
-            FROM EmployeeMaster
+            FROM EmployeeMaster WHERE 1=1 {where}
             ORDER BY Employee
-            """);
+            """, pars);
         return new { list };
     }
 
@@ -353,12 +363,13 @@ public class DemandOrderService : IDynamicApiController, ITransient
     [HttpGet("demand-order/options/suppliers")]
     public async Task<object> GetSupplierOptions()
     {
+        var (where, pars) = BuildTenantFilter();
         var list = await _db.Ado.SqlQueryAsync<OptionRow>(
-            """
+            $"""
             SELECT Supp AS Value, CONCAT(TRIM(Supp),' ',TRIM(IFNULL(SortName,''))) AS Label
-            FROM SuppMaster
+            FROM SuppMaster WHERE 1=1 {where}
             ORDER BY Supp
-            """);
+            """, pars);
         return new { list };
     }
 
@@ -366,12 +377,13 @@ public class DemandOrderService : IDynamicApiController, ITransient
     [HttpGet("demand-order/options/departments")]
     public async Task<object> GetDepartmentOptions()
     {
+        var (where, pars) = BuildTenantFilter();
         var list = await _db.Ado.SqlQueryAsync<OptionRow>(
-            """
+            $"""
             SELECT Department AS Value, CONCAT(TRIM(Department),' ',TRIM(IFNULL(Descr,''))) AS Label
-            FROM DepartmentMaster
+            FROM DepartmentMaster WHERE 1=1 {where}
             ORDER BY Department
-            """);
+            """, pars);
         return new { list };
     }
 
@@ -379,13 +391,14 @@ public class DemandOrderService : IDynamicApiController, ITransient
     [HttpGet("demand-order/options/curr")]
     public async Task<object> GetCurrOptions()
     {
+        var (where, pars) = BuildTenantFilter();
         var list = await _db.Ado.SqlQueryAsync<OptionRow>(
-            """
+            $"""
             SELECT Val AS Value, CONCAT(TRIM(Val),' ',TRIM(IFNULL(Comments,''))) AS Label
             FROM GeneralizedCodeMaster
-            WHERE FldName='Curr'
+            WHERE FldName='Curr' {where}
             ORDER BY Val
-            """);
+            """, pars);
         return new { list };
     }
 
@@ -393,13 +406,14 @@ public class DemandOrderService : IDynamicApiController, ITransient
     [HttpGet("demand-order/options/locations")]
     public async Task<object> GetLocationOptions()
     {
+        var (where, pars) = BuildTenantFilter();
         var list = await _db.Ado.SqlQueryAsync<OptionRow>(
-            """
+            $"""
             SELECT location AS Value, CONCAT(TRIM(location),' ',TRIM(IFNULL(descr,''))) AS Label
             FROM LocationMaster
-            WHERE IFNULL(typed,'')<>'Supp'
+            WHERE IFNULL(typed,'')<>'Supp' {where}
             ORDER BY location
-            """);
+            """, pars);
         return new { list };
     }
 
@@ -563,9 +577,9 @@ public class DemandOrderService : IDynamicApiController, ITransient
                 p.`Usage` AS `Usage`,
                 p.FSTID AS Fstid
             FROM PurOrdMaster p
-            LEFT JOIN SuppMaster s ON p.Supp=s.Supp
-            LEFT JOIN DepartmentMaster d ON p.Department=d.Department
-            LEFT JOIN EmployeeMaster e ON p.Buyer=e.Employee
+            LEFT JOIN SuppMaster s ON p.Supp=s.Supp AND s.tenant_id=p.tenant_id
+            LEFT JOIN DepartmentMaster d ON p.Department=d.Department AND d.tenant_id=p.tenant_id
+            LEFT JOIN EmployeeMaster e ON p.Buyer=e.Employee AND e.tenant_id=p.tenant_id
             WHERE p.RecID=@Id AND p.Potype='PO' AND IFNULL(p.ReqBy,'')='DO'{tenantWhere}
             LIMIT 1
             """,

+ 40 - 16
server/Plugins/Admin.NET.Plugin.AiDOP/Supply/OutsourceOrderService.cs

@@ -56,11 +56,13 @@ public class OutsourceOrderService : IDynamicApiController, ITransient
             pars.Add(new SugarParameter("@Supp", input.Supp.Trim()));
         }
 
+        var tenantJoin = _userManager.TenantId > 0 ? " AND s.tenant_id=p.tenant_id" : "";
         var fromSql = $"""
             FROM PurOrdMaster p
-            LEFT JOIN SuppMaster s ON p.Supp=s.Supp
-            LEFT JOIN DepartmentMaster d ON p.Department=d.Department
-            LEFT JOIN EmployeeMaster e ON p.Buyer=e.Employee
+            LEFT JOIN SuppMaster s ON p.Supp=s.Supp{tenantJoin}
+            LEFT JOIN DepartmentMaster d ON p.Department=d.Department{(_userManager.TenantId > 0 ? " AND d.tenant_id=p.tenant_id" : "")}
+            LEFT JOIN EmployeeMaster e ON p.Buyer=e.Employee{(_userManager.TenantId > 0 ? " AND e.tenant_id=p.tenant_id" : "")}
+            LEFT JOIN (SELECT DISTINCT PurOrd FROM PurOrdDetail WHERE IFNULL(RctQty,0)>0) pdhr ON pdhr.PurOrd=p.PurOrd
             WHERE {string.Join(" AND ", where)}
             """;
 
@@ -87,7 +89,7 @@ public class OutsourceOrderService : IDynamicApiController, ITransient
                 p.Department AS Department,
                 p.`Usage` AS `Usage`,
                 p.Remark AS Remark,
-                (CASE WHEN EXISTS(SELECT 1 FROM PurOrdDetail pd WHERE pd.PurOrd=p.PurOrd AND IFNULL(pd.RctQty,0)>0) THEN 1 ELSE 0 END) AS HasReceived
+                CASE WHEN pdhr.PurOrd IS NOT NULL THEN 1 ELSE 0 END AS HasReceived
             {fromSql}
             ORDER BY {BuildOrderBy(input.SortField, input.SortOrder)}
             LIMIT {pageSize} OFFSET {offset}
@@ -102,6 +104,9 @@ public class OutsourceOrderService : IDynamicApiController, ITransient
     public async Task<object> GetOrderDetail(int id)
     {
         var tenantWhere = _userManager.TenantId > 0 ? " AND p.tenant_id=@TenantId" : string.Empty;
+        var tenantJoin = _userManager.TenantId > 0 ? " AND s.tenant_id=p.tenant_id" : string.Empty;
+        var tenantJoinD = _userManager.TenantId > 0 ? " AND d.tenant_id=p.tenant_id" : string.Empty;
+        var tenantJoinE = _userManager.TenantId > 0 ? " AND e.tenant_id=p.tenant_id" : string.Empty;
         var detailPars = _userManager.TenantId > 0
             ? new[] { new SugarParameter("@Id", id), new SugarParameter("@TenantId", _userManager.TenantId) }
             : new[] { new SugarParameter("@Id", id) };
@@ -125,9 +130,9 @@ public class OutsourceOrderService : IDynamicApiController, ITransient
                 p.`Usage` AS `Usage`,
                 p.Remark AS Remark
             FROM PurOrdMaster p
-            LEFT JOIN SuppMaster s ON p.Supp=s.Supp
-            LEFT JOIN DepartmentMaster d ON p.Department=d.Department
-            LEFT JOIN EmployeeMaster e ON p.Buyer=e.Employee
+            LEFT JOIN SuppMaster s ON p.Supp=s.Supp{tenantJoin}
+            LEFT JOIN DepartmentMaster d ON p.Department=d.Department{tenantJoinD}
+            LEFT JOIN EmployeeMaster e ON p.Buyer=e.Employee{tenantJoinE}
             WHERE p.RecID=@Id{tenantWhere}
             LIMIT 1
             """,
@@ -616,12 +621,15 @@ public class OutsourceOrderService : IDynamicApiController, ITransient
     [HttpGet("outsource-order/options/buyers")]
     public async Task<object> GetBuyerOptions()
     {
+        var (where, pars) = BuildTenantFilter();
         var list = await _db.Ado.SqlQueryAsync<OptionRow>(
-            """
+            $"""
             SELECT Employee AS Value, CONCAT(TRIM(Employee),' ',TRIM(IFNULL(Name,''))) AS Label
             FROM EmployeeMaster
+            WHERE 1=1{where}
             ORDER BY Employee
-            """);
+            """,
+            pars);
         return new { list };
     }
 
@@ -629,12 +637,15 @@ public class OutsourceOrderService : IDynamicApiController, ITransient
     [HttpGet("outsource-order/options/suppliers")]
     public async Task<object> GetSupplierOptions()
     {
+        var (where, pars) = BuildTenantFilter();
         var list = await _db.Ado.SqlQueryAsync<OptionRow>(
-            """
+            $"""
             SELECT Supp AS Value, CONCAT(TRIM(Supp),' ',TRIM(IFNULL(SortName,''))) AS Label
             FROM SuppMaster
+            WHERE 1=1{where}
             ORDER BY Supp
-            """);
+            """,
+            pars);
         return new { list };
     }
 
@@ -642,12 +653,15 @@ public class OutsourceOrderService : IDynamicApiController, ITransient
     [HttpGet("outsource-order/options/departments")]
     public async Task<object> GetDepartmentOptions()
     {
+        var (where, pars) = BuildTenantFilter();
         var list = await _db.Ado.SqlQueryAsync<OptionRow>(
-            """
+            $"""
             SELECT Department AS Value, CONCAT(TRIM(Department),' ',TRIM(IFNULL(Descr,''))) AS Label
             FROM DepartmentMaster
+            WHERE 1=1{where}
             ORDER BY Department
-            """);
+            """,
+            pars);
         return new { list };
     }
 
@@ -655,16 +669,26 @@ public class OutsourceOrderService : IDynamicApiController, ITransient
     [HttpGet("outsource-order/options/locations")]
     public async Task<object> GetLocationOptions()
     {
+        var (where, pars) = BuildTenantFilter();
         var list = await _db.Ado.SqlQueryAsync<OptionRow>(
-            """
+            $"""
             SELECT location AS Value, CONCAT(TRIM(location),' ',TRIM(IFNULL(descr,''))) AS Label
             FROM LocationMaster
-            WHERE IFNULL(typed,'')<>'Supp'
+            WHERE IFNULL(typed,'')<>'Supp'{where}
             ORDER BY location
-            """);
+            """,
+            pars);
         return new { list };
     }
 
+    private (string Where, List<SugarParameter> Pars) BuildTenantFilter()
+    {
+        var tenantId = _userManager.TenantId;
+        if (tenantId > 0)
+            return (" AND tenant_id = @TenantId", new List<SugarParameter> { new("@TenantId", tenantId) });
+        return ("", new List<SugarParameter>());
+    }
+
     [DisplayName("BOM组件查询")]
     [HttpGet("outsource-order/options/bom")]
     public async Task<object> GetBomComponents([FromQuery] string itemNum)

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

@@ -313,7 +313,7 @@ public class PurchaseOrderTransferService : ITransient
                 VALUES
                 (
                     @Domain, @PurOrd, @Potype, @Line, @Batch, @ItemNum, @SuppItem, @UM, @Location,
-                    @QtyOrded, @QtyBO, 0, 0, 0, '', @PurOrdDetailRecID,
+                    @QtyOrded, @QtyBO, @QtyReleased, 0, 0, '', @PurOrdDetailRecID,
                     @CreateUser, @CreateTime, @UpdateUser, @UpdateTime, @TenantId
                 )
                 """,
@@ -327,7 +327,8 @@ public class PurchaseOrderTransferService : ITransient
                 new SugarParameter("@UM", comp.UM ?? string.Empty),
                 new SugarParameter("@Location", string.IsNullOrWhiteSpace(comp.Location) ? "1001" : comp.Location),
                 new SugarParameter("@QtyOrded", batchQty),
-                new SugarParameter("@QtyBO", batchQty),
+                new SugarParameter("@QtyBO", comp.QtyPerUnit),
+                new SugarParameter("@QtyReleased", batchQty),
                 new SugarParameter("@PurOrdDetailRecID", detailRecId),
                 new SugarParameter("@CreateUser", account),
                 new SugarParameter("@CreateTime", now),