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

feat: 采购退货单收货单全量退货校验 - 下拉过滤已全部退完的收货单 - 保存时校验退货数量不超过可退余量 - 子表退货原因必填校验

Pengxy 18 часов назад
Родитель
Сommit
207cc02fff

+ 1 - 1
Web/package.json

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

+ 6 - 0
Web/src/views/aidop/s4/return/purchaseReturnOrderForm.vue

@@ -577,6 +577,12 @@ async function submit() {
 		ElMessage.warning('请填写明细物料');
 		return;
 	}
+	for (let i = 0; i < details.value.length; i++) {
+		if (!details.value[i]?.qcDescr?.trim()) {
+			ElMessage.warning(`第 ${i + 1} 行请选择退货原因`);
+			return;
+		}
+	}
 	saving.value = true;
 	try {
 		const res = await savePurchaseReturn(buildSaveBody());

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

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

@@ -160,9 +160,16 @@ public class PurchaseReturnService : IDynamicApiController, ITransient
             LEFT JOIN PurOrdRctDetail d
               ON p.RctType = d.RctType AND p.Receiver = d.Receiver
             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,'') <> ''
+            WHERE p.RctType = 'rc' AND p.Supp = @Supp AND d.RecID > 0
               AND p.tenant_id = @TenantId
             GROUP BY p.Receiver, d.LotSerial, d.ItemNum, i.Descr, p.Domain
+            HAVING SUM(d.QtyReceived) > IFNULL((
+                SELECT SUM(rd.QtyReceived) FROM PurOrdRctDetail rd
+                JOIN PurOrdRctMaster rm ON rd.PurOrdRctRecID = rm.RecID AND rd.RctType = rm.RctType
+                WHERE rm.RctType = 'pt' AND rm.OrdNbr = p.Receiver AND rd.IsActive = 1
+                  AND rd.ItemNum = d.ItemNum AND IFNULL(rd.LotSerial,'') = IFNULL(d.LotSerial,'')
+                  AND rm.tenant_id = p.tenant_id
+            ), 0)
             ORDER BY p.Receiver DESC
             """,
             new SugarParameter("@Supp", supp.Trim()),
@@ -404,6 +411,9 @@ public class PurchaseReturnService : IDynamicApiController, ITransient
         if (string.IsNullOrWhiteSpace(domain))
             domain = "100";
 
+        // 校验退货数量不超过收货单可退余量
+        await ValidateReturnQuantities(input.OrdNbr.Trim(), input.Id, input.Details, tenantId);
+
         if (input.Id is null or 0)
         {
             try
@@ -626,4 +636,80 @@ public class PurchaseReturnService : IDynamicApiController, ITransient
         if (string.IsNullOrWhiteSpace(raw)) return null;
         return DateTime.TryParse(raw.Trim(), out var d) ? d.Date : null;
     }
+
+    /// <summary>
+    /// 校验退货数量不超过收货单可退余量。
+    /// 对每行按 ItemNum + LotSerial 分组,比较:已有退货总量 + 本次退货量 ≤ 收货量。
+    /// 编辑时排除当前退货单自身数量。
+    /// </summary>
+    private async Task ValidateReturnQuantities(
+        string rcReceiver, int? editingId, List<PurchaseReturnDetailInput> details, long tenantId)
+    {
+        // 1. 取收货单明细:按 (ItemNum, LotSerial) 汇总收货数量
+        var rcLines = await _db.Ado.SqlQueryAsync<dynamic>(
+            """
+            SELECT ItemNum, IFNULL(LotSerial,'') AS LotSerial, SUM(QtyReceived) AS QtyReceived
+            FROM PurOrdRctDetail
+            WHERE RctType='rc' AND Receiver=@Receiver AND tenant_id=@TenantId
+            GROUP BY ItemNum, IFNULL(LotSerial,'')
+            """,
+            new SugarParameter("@Receiver", rcReceiver),
+            new SugarParameter("@TenantId", tenantId));
+
+        if (!rcLines.Any()) return; // 收货单无明细则跳过校验
+
+        var received = new Dictionary<string, decimal>();
+        foreach (var rc in rcLines)
+        {
+            var key = $"{(string)rc.ItemNum}|{(string)rc.LotSerial}";
+            received[key] = (decimal)rc.QtyReceived;
+        }
+
+        // 2. 取已有退货总量(排除编辑中的当前退货单)
+        var excludeSql = editingId.HasValue ? " AND rm.RecID <> @ExcludeId" : "";
+        var existingReturns = await _db.Ado.SqlQueryAsync<dynamic>(
+            $"""
+            SELECT rd.ItemNum, IFNULL(rd.LotSerial,'') AS LotSerial, SUM(rd.QtyReceived) AS ReturnedQty
+            FROM PurOrdRctDetail rd
+            JOIN PurOrdRctMaster rm ON rd.PurOrdRctRecID = rm.RecID AND rd.RctType = rm.RctType
+            WHERE rm.RctType='pt' AND rm.OrdNbr=@Receiver AND rd.IsActive=1{excludeSql}
+              AND rm.tenant_id=@TenantId
+            GROUP BY rd.ItemNum, IFNULL(rd.LotSerial,'')
+            """,
+            editingId.HasValue
+                ? new List<SugarParameter>
+                  {
+                      new("@Receiver", rcReceiver),
+                      new("@ExcludeId", editingId.Value),
+                      new("@TenantId", tenantId)
+                  }
+                : new List<SugarParameter>
+                  {
+                      new("@Receiver", rcReceiver),
+                      new("@TenantId", tenantId)
+                  });
+
+        var returned = new Dictionary<string, decimal>();
+        foreach (var rt in existingReturns)
+        {
+            var key = $"{(string)rt.ItemNum}|{(string)rt.LotSerial}";
+            returned[key] = (decimal)rt.ReturnedQty;
+        }
+
+        // 3. 逐行校验
+        for (var i = 0; i < details.Count; i++)
+        {
+            var d = details[i];
+            var key = $"{d.ItemNum?.Trim()}|{d.LotSerial?.Trim() ?? ""}";
+            if (!received.TryGetValue(key, out var rcQty)) continue;
+
+            var alreadyReturned = returned.GetValueOrDefault(key, 0m);
+            var thisQty = d.QtyReceived;
+            if (alreadyReturned + thisQty > rcQty)
+            {
+                var available = rcQty - alreadyReturned;
+                throw Oops.Oh($"第 {i + 1} 行退货数量({thisQty})超过可退余量({available}),收货数量={rcQty},已退数量={alreadyReturned}");
+            }
+        }
+    }
 }