From a4ded59a4784abc853e734f6d38316af2c656716 Mon Sep 17 00:00:00 2001
From: gongchunyi <deslre0381@gmail.com>
Date: 星期四, 02 七月 2026 15:43:17 +0800
Subject: [PATCH] Merge branch 'dev_NEW_pro' of http://114.132.189.42:9002/r/product-inventory-management into dev_NEW_pro

---
 src/views/procurementManagement/procurementLedger/index.vue    |    4 
 src/views/salesManagement/returnOrder/components/formDia.vue   |   81 +++++++++++--------
 src/api/procurementManagement/procurementLedger.js             |    8 ++
 src/views/salesManagement/returnOrder/components/detailDia.vue |    8 -
 src/views/financialManagement/receivable/receipt.vue           |   71 ++++++++++++++++-
 src/views/procurementManagement/purchaseReturnOrder/New.vue    |    6 
 src/views/financialManagement/receivable/outputInvoice.vue     |   12 +-
 src/views/salesManagement/returnOrder/index.vue                |   22 +++++
 8 files changed, 156 insertions(+), 56 deletions(-)

diff --git a/src/api/procurementManagement/procurementLedger.js b/src/api/procurementManagement/procurementLedger.js
index 88f4ce0..c389582 100644
--- a/src/api/procurementManagement/procurementLedger.js
+++ b/src/api/procurementManagement/procurementLedger.js
@@ -9,6 +9,14 @@
     params: query,
   });
 }
+// 閲囪喘閫�璐у彲閫夐噰璐彴璐�
+export function purchaseReturnableList(query) {
+  return request({
+    url: "/purchase/ledger/returnableList",
+    method: "get",
+    params: query,
+  });
+}
 // 鏌ヨ鍚堝悓鍙�
 export function getSalesNo(query) {
   return request({
diff --git a/src/views/financialManagement/receivable/outputInvoice.vue b/src/views/financialManagement/receivable/outputInvoice.vue
index e5c8bf8..effb0af 100644
--- a/src/views/financialManagement/receivable/outputInvoice.vue
+++ b/src/views/financialManagement/receivable/outputInvoice.vue
@@ -93,7 +93,7 @@
       @confirm="submitForm"
       @cancel="closeDialog"
     >
-      <el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
+      <el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
         <el-row v-if="isView" :gutter="20">
           <el-col :span="12">
             <el-form-item label="鐘舵��">
@@ -178,7 +178,7 @@
           </el-col>
         </el-row>
         <el-row :gutter="20">
-          <el-col :span="8">
+          <el-col :span="10">
             <el-form-item label="閲戦(涓嶅惈绋�)" prop="amount">
               <el-input-number
                 v-model="form.amount"
@@ -190,14 +190,14 @@
               />
             </el-form-item>
           </el-col>
-          <el-col :span="8">
+          <el-col :span="7">
             <el-form-item label="绋庨">
-              <el-input v-model="form.taxAmount" disabled />
+              <el-input v-model="form.taxAmount" style="width: 100%;" disabled />
             </el-form-item>
           </el-col>
-          <el-col :span="8">
+          <el-col :span="7">
             <el-form-item label="浠风◣鍚堣">
-              <el-input v-model="form.totalAmount" disabled />
+              <el-input v-model="form.totalAmount" style="width: 100%;" disabled />
             </el-form-item>
           </el-col>
         </el-row>
diff --git a/src/views/financialManagement/receivable/receipt.vue b/src/views/financialManagement/receivable/receipt.vue
index 467a368..29194e7 100644
--- a/src/views/financialManagement/receivable/receipt.vue
+++ b/src/views/financialManagement/receivable/receipt.vue
@@ -171,10 +171,10 @@
                           prop="amount">
               <el-input-number v-model="form.amount"
                                :min="0"
+                               :max="collectionAmountInputMax"
                                :precision="2"
                                style="width: 100%;"
-                               :disabled="isView"
-                               placeholder="鏍规嵁鍏宠仈鍗曟嵁鑷姩姹囨�伙紝鍙慨鏀�" />
+                               :disabled="isView" />
             </el-form-item>
           </el-col>
         </el-row>
@@ -368,6 +368,7 @@
   const isEdit = ref(false);
   const isView = ref(false);
   const currentId = ref(null);
+  const originalReceiptAmount = ref(0);
   const submitLoading = ref(false);
 
   const customerList = ref([]);
@@ -406,6 +407,46 @@
     },
   });
 
+  const maxCollectionAmount = computed(() => {
+    const selected = form.stockOutRecordIds || [];
+    const editAmount = isEdit.value ? Number(originalReceiptAmount.value) || 0 : 0;
+    if (!selected.length) return isEdit.value ? editAmount : undefined;
+    const selectedValueSet = new Set(selected.map(id => String(id)));
+    const selectedOptions = outboundBatchOptions.value.filter(
+      opt => opt.amountLimitAvailable && selectedValueSet.has(String(opt.value))
+    );
+    if (selectedOptions.length !== selectedValueSet.size) {
+      return isEdit.value ? Number(editAmount.toFixed(2)) : undefined;
+    }
+    const sum = selectedOptions.reduce(
+      (acc, opt) => acc + (Number(opt.outboundAmount) || 0),
+      0
+    );
+    return Number((sum + editAmount).toFixed(2));
+  });
+
+  const collectionAmountInputMax = computed(
+    () => maxCollectionAmount.value ?? Number.MAX_SAFE_INTEGER
+  );
+
+  const validateCollectionAmount = (rule, value, callback) => {
+    if (value === undefined || value === null || value === "") {
+      callback();
+      return;
+    }
+    const amount = Number(value);
+    if (Number.isNaN(amount)) {
+      callback(new Error("璇疯緭鍏ユ敹娆鹃噾棰�"));
+      return;
+    }
+    const max = maxCollectionAmount.value;
+    if (max !== undefined && amount - max > 0.000001) {
+      callback(new Error(`鏀舵閲戦涓嶈兘瓒呰繃${max.toFixed(2)}`));
+      return;
+    }
+    callback();
+  };
+
   const rules = {
     customerId: [{ required: true, message: "璇烽�夋嫨瀹㈡埛", trigger: "change" }],
     stockOutRecordIds: [
@@ -420,7 +461,10 @@
     receiptDate: [
       { required: true, message: "璇烽�夋嫨鏀舵鏃ユ湡", trigger: "change" },
     ],
-    amount: [{ required: true, message: "璇疯緭鍏ユ敹娆鹃噾棰�", trigger: "blur" }],
+    amount: [
+      { required: true, message: "璇疯緭鍏ユ敹娆鹃噾棰�", trigger: "blur" },
+      { validator: validateCollectionAmount, trigger: ["blur", "change"] },
+    ],
     receiptMethod: [
       { required: true, message: "璇烽�夋嫨鏀舵鏂瑰紡", trigger: "change" },
     ],
@@ -480,7 +524,12 @@
     return list.map((item, index) => {
       if (typeof item === "string" || typeof item === "number") {
         const text = String(item);
-        return { label: text, value: text, outboundAmount: 0 };
+        return {
+          label: text,
+          value: text,
+          outboundAmount: 0,
+          amountLimitAvailable: false,
+        };
       }
       const label =
         item.outboundBatches ??
@@ -490,10 +539,15 @@
         item.label ??
         `鍑哄簱鍗�${index + 1}`;
       const value = item.id ?? item.stockOutRecordId ?? label;
+      const outboundAmount = Number(item.outboundAmount) || 0;
+      const amountReceived = Number(item.amountReceived) || 0;
+      const availableAmount = outboundAmount - amountReceived;
       return {
         label: String(label),
         value,
-        outboundAmount: (Number(item.outboundAmount)-Number(item.amountReceived)) || 0,
+        outboundAmount:
+          availableAmount > 0 ? Number(availableAmount.toFixed(2)) : 0,
+        amountLimitAvailable: true,
       };
     });
   };
@@ -541,6 +595,7 @@
         label: String(id),
         value: id,
         outboundAmount: 0,
+        amountLimitAvailable: false,
       });
     });
   };
@@ -643,6 +698,7 @@
     outboundSelectVisible.value = false;
     syncCollectionAmount();
     formRef.value?.validateField("stockOutRecordIds");
+    formRef.value?.validateField("amount");
   };
 
   const handleOutboundDialogClosed = () => {
@@ -713,6 +769,7 @@
     outboundSelectVisible.value = false;
     isView.value = false;
     isEdit.value = false;
+    originalReceiptAmount.value = 0;
   };
 
   const handleExport = () => {
@@ -772,6 +829,7 @@
     isEdit.value = false;
     isView.value = false;
     dialogTitle.value = "鏂板鏀舵";
+    originalReceiptAmount.value = 0;
     Object.assign(form, {
       receiptCode: "",
       customerId: "",
@@ -794,13 +852,16 @@
     currentId.value = row.id;
     dialogTitle.value = "缂栬緫鏀舵";
     fillFormFromRow(row);
+    originalReceiptAmount.value = Number(form.amount || 0);
     dialogVisible.value = true;
+    loadOutboundBatches(form.customerId, true);
   };
 
   const view = row => {
     isView.value = true;
     isEdit.value = false;
     dialogTitle.value = "鏌ョ湅鏀舵";
+    originalReceiptAmount.value = 0;
     fillFormFromRow(row);
     dialogVisible.value = true;
   };
diff --git a/src/views/procurementManagement/procurementLedger/index.vue b/src/views/procurementManagement/procurementLedger/index.vue
index ed7e5d9..6d9cae6 100644
--- a/src/views/procurementManagement/procurementLedger/index.vue
+++ b/src/views/procurementManagement/procurementLedger/index.vue
@@ -219,7 +219,7 @@
             <el-button link
                        type="primary"
                        @click="openForm('edit', scope.row)"
-                       :disabled="scope.row.stockInStatus === '瀹屽叏鍏ュ簱'">缂栬緫
+                       :disabled="scope.row.approvalStatus === 3">缂栬緫
             </el-button>
             <el-button link
                        type="primary"
@@ -1747,7 +1747,7 @@
       return;
     }
     const ids = selectedRows.value.map((item) => item.id);
-    
+
     ElMessageBox.confirm("纭鎵归噺鐢熸垚鏁版嵁锛�", "鎵归噺鐢熸垚", {
       confirmButtonText: "纭",
       cancelButtonText: "鍙栨秷",
diff --git a/src/views/procurementManagement/purchaseReturnOrder/New.vue b/src/views/procurementManagement/purchaseReturnOrder/New.vue
index 9474b11..f4771d3 100644
--- a/src/views/procurementManagement/purchaseReturnOrder/New.vue
+++ b/src/views/procurementManagement/purchaseReturnOrder/New.vue
@@ -420,7 +420,7 @@
 <script setup>
 import {ref, computed, getCurrentInstance, watch, defineAsyncComponent} from "vue";
 import {createPurchaseReturnOrder} from "@/api/procurementManagement/purchase_return_order.js";
-import {getOptions, purchaseList} from "@/api/procurementManagement/procurementLedger.js";
+import {getOptions, purchaseReturnableList} from "@/api/procurementManagement/procurementLedger.js";
 import {userListNoPageByTenantId} from "@/api/system/user.js";
 const ProductList = defineAsyncComponent(() => import("@/views/procurementManagement/purchaseReturnOrder/ProductList.vue"));
 const props = defineProps({
@@ -674,7 +674,7 @@
 const fetchPurchaseLedgerOptions = () => {
   purchaseLedgerOptions.value = []
   if (formState.value.supplierId) {
-    purchaseList({supplierId: formState.value.supplierId,approvalStatus:3}).then((res) => {
+    purchaseReturnableList({ supplierId: formState.value.supplierId }).then((res) => {
       purchaseLedgerOptions.value = res.rows;
     });
   }
@@ -805,4 +805,4 @@
 }
 
 
-</style>
\ No newline at end of file
+</style>
diff --git a/src/views/salesManagement/returnOrder/components/detailDia.vue b/src/views/salesManagement/returnOrder/components/detailDia.vue
index ecc663f..60b485b 100644
--- a/src/views/salesManagement/returnOrder/components/detailDia.vue
+++ b/src/views/salesManagement/returnOrder/components/detailDia.vue
@@ -21,9 +21,6 @@
       <div style="padding-top: 20px">
         <span class="descriptions">浜у搧鍒楄〃</span>
         <PIMTable :isShowPagination="false" rowKey="id" :column="tableColumn" :tableData="tableData">
-          <template #totalReturnNum="{ row }">
-            {{ calcAlreadyReturned(row) }}
-          </template>
         </PIMTable>
       </div>
     </div>
@@ -252,8 +249,9 @@
   {align: "center", label: "瑙勬牸鍨嬪彿", prop: "model"},
   {align: "center", label: "鍗曚綅", prop: "unit", width: 80},
   {align: "center", label: "鎬绘暟閲�", prop: "stockOutNum", width: 120},
-  {align: "center", label: "宸查��璐ф暟閲�", prop: "totalReturnNum", width: 120, dataType: "slot", slot: "totalReturnNum"},
+  {align: "center", label: "宸查��璐ф暟閲�", prop: "totalReturnNum", width: 120},
   {align: "center", label: "鏈��璐ф暟閲�", prop: "unQuantity", width: 120},
+  {align: "center", label: "寰呭鐞嗛��璐ф暟閲�", prop: "pendingReturnNum", width: 120},
   {align: "center", label: "閫�璐ф暟閲�", prop: "returnQuantity", width: 120},
   {align: "center", label: "閫�璐т骇鍝佸崟浠�", prop: "price", width: 120},
   {align: "center", label: "閫�璐т骇鍝侀噾棰�", prop: "amount", width: 120},
@@ -284,7 +282,7 @@
   try {
     const res = await returnManagementGetById({ returnManagementId: row.id });
     detail.value = res?.data ?? res ?? {};
-    
+
     if (detail.value.shippingId) {
       const productRes = await returnManagementGetByShippingId({ shippingId: detail.value.shippingId });
       if (productRes.code === 200) {
diff --git a/src/views/salesManagement/returnOrder/components/formDia.vue b/src/views/salesManagement/returnOrder/components/formDia.vue
index 81d9487..40c98dc 100644
--- a/src/views/salesManagement/returnOrder/components/formDia.vue
+++ b/src/views/salesManagement/returnOrder/components/formDia.vue
@@ -82,32 +82,29 @@
             <el-button type="primary" @click="openProductSelection" :disabled="!form.shippingId">娣诲姞浜у搧</el-button>
           </div>
           <PIMTable :isShowPagination="false" rowKey="id" :column="tableColumn" :tableData="tableData">
-            <template #totalReturnNum="{ row }">
-              {{ calcAlreadyReturned(row) }}
-            </template>
             <template #returnQuantity="{ row }">
-              <el-input 
-                v-model="row.returnQuantity" 
-                style="width:100px" 
-                placeholder="璇疯緭鍏�" 
+              <el-input
+                v-model="row.returnQuantity"
+                style="width:100px"
+                placeholder="璇疯緭鍏�"
                 type="number"
                 @input="(val) => handleReturnQuantityChange(val, row)"
               />
             </template>
             <template #price="{ row }">
-              <el-input 
-                v-model="row.price" 
-                style="width:100px" 
-                placeholder="璇疯緭鍏�" 
+              <el-input
+                v-model="row.price"
+                style="width:100px"
+                placeholder="璇疯緭鍏�"
                 type="number"
                 @input="(val) => handlePriceChange(val, row)"
               />
             </template>
             <template #amount="{ row }">
-              <el-input 
-                v-model="row.amount" 
-                style="width:100px" 
-                placeholder="鑷姩璁$畻" 
+              <el-input
+                v-model="row.amount"
+                style="width:100px"
+                placeholder="鑷姩璁$畻"
                 type="number"
                 disabled
               />
@@ -119,10 +116,10 @@
               </el-select>
             </template>
             <template #remark="{ row }">
-              <el-input 
-                v-model="row.remark" 
-                style="width:130px" 
-                placeholder="璇疯緭鍏�" 
+              <el-input
+                v-model="row.remark"
+                style="width:130px"
+                placeholder="璇疯緭鍏�"
               />
             </template>
             <template #action="{ index }">
@@ -140,9 +137,9 @@
     </el-dialog>
 
     <el-dialog v-model="productSelectionVisible" title="閫夋嫨浜у搧" width="70%" append-to-body>
-      <el-table 
-        :data="availableProducts" 
-        style="width: 100%" 
+      <el-table
+        :data="availableProducts"
+        style="width: 100%"
         @selection-change="handleSelectionChange"
         ref="productTableRef"
         row-key="id"
@@ -154,11 +151,9 @@
         <el-table-column align="center" prop="specificationModel" label="瑙勬牸鍨嬪彿" />
         <el-table-column align="center" prop="unit" label="鍗曚綅" />
         <el-table-column align="center" prop="stockOutNum" label="鎬绘暟閲�" />
+        <el-table-column align="center" label="宸查��璐ф暟閲�" prop="totalReturnNum"></el-table-column>
         <el-table-column align="center" prop="unQuantity" label="鏈��璐ф暟閲�" />
-        <el-table-column align="center" label="宸查��璐ф暟閲�">
-          <template #default="{ row }">{{ calcAlreadyReturned(row) }}</template>
-        </el-table-column>
-
+        <el-table-column align="center" prop="pendingReturnNum" label="寰呭鐞嗛��璐ф暟閲�" />
       </el-table>
       <template #footer>
         <div class="dialog-footer">
@@ -226,7 +221,8 @@
   {align: "center", label: "瑙勬牸鍨嬪彿", prop: "specificationModel" },
   {align: "center", label: "鍗曚綅", prop: "unit", width: 80 },
   {align: "center", label: "鎬绘暟閲�", prop: "stockOutNum", width: 120 },
-  {align: "center", label: "宸查��璐ф暟閲�", prop: "totalReturnNum", width: 120, dataType: "slot", slot: "totalReturnNum" },
+  {align: "center", label: "宸查��璐ф暟閲�", prop: "totalReturnNum", width: 120 },
+  {align: "center", label: "寰呭鐞嗛��璐ф暟閲�", prop: "pendingReturnNum", width: 120 },
   {align: "center", label: "鏈��璐ф暟閲�", prop: "unQuantity", width: 120 },
   {align: "center", label: "閫�璐ф暟閲�", prop: "returnQuantity", dataType: "slot", slot: "returnQuantity", width: 120 },
   {align: "center", label: "閫�璐т骇鍝佸崟浠�", prop: "price", dataType: "slot", slot: "price", width: 120 },
@@ -559,6 +555,18 @@
   proxy.resetForm("formRef");
   dialogFormVisible.value = false;
   emit('close');
+  data.form = {
+        returnNoCheckbox: true,
+        returnNo: "",
+        customerId: "",
+        shippingId: "",
+        projectId: "",
+        maker: "",
+        makeTime: "",
+        status: 0,
+        returnReason: "",
+        refundAmount: "",
+  }
 };
 
 const initCustomers = async () => {
@@ -601,7 +609,7 @@
     form.value.shippingId = "";
     outboundOptions.value = [];
   }
-  
+
   // Find customer name for getSalesLedger if it requires name
   const customer = customerNameOptions.value.find(c => c.id === val);
   if (!customer) return;
@@ -609,7 +617,7 @@
   // Assuming getSalesLedger takes customerName. If it takes ID, adjust accordingly.
   // Previous code used customerName. Let's try passing customerName.
   getSalesLedger({
-    customerName: customer.label, 
+    customerName: customer.label,
   }).then(res => {
     if(res.code === 200){
       outboundOptions.value = res.data.map(item => ({
@@ -626,7 +634,7 @@
   if(res.code === 200){
     // If backend returns project info, set it
     if (res.data.projectId) form.value.projectId = res.data.projectId;
-    
+
     availableProducts.value = mergeShippingProductLists(res.data);
     if (clearTable) tableData.value = [];
   }
@@ -634,9 +642,12 @@
 
 const handleReturnQuantityChange = (val, row) => {
   if (val === "" || val === null) return;
-  const max = row.unQuantity === undefined || row.unQuantity === null ? Infinity : Number(row.unQuantity || 0);
+  const unQuantity = row.unQuantity === undefined || row.unQuantity === null ? Infinity : Number(row.unQuantity || 0);//鏈��璐ф暟閲�
+  const pendingReturnNum = row.pendingReturnNum === undefined || row.pendingReturnNum === null ? Infinity : Number(row.pendingReturnNum || 0);//寰呭鐞嗛��璐ф暟閲�
+  //鏈�澶ч��璐ф暟閲忥紝濡傛灉涓虹紪杈戠姸鎬佸姞涓婂緟澶勭悊閫�璐ф暟閲�
+  const max = operationType.value === 'edit' ? unQuantity + pendingReturnNum : unQuantity
   const current = Number(val);
-  
+
   if (current > max) {
     proxy.$nextTick(() => {
       row.returnQuantity = max;
@@ -702,7 +713,7 @@
 // Removed checkSelectable to allow toggling existing items
 const confirmProductSelection = () => {
   const newTableData = [];
-  
+
   selectedProducts.value.forEach(product => {
     const existing = tableData.value.find(item => item.id === product.id);
     if (existing) {
@@ -710,7 +721,7 @@
     } else {
       newTableData.push({
         ...product,
-        returnSaleLedgerProductId: product.id, 
+        returnSaleLedgerProductId: product.id,
         productModelId: product.productModelId,
         returnQuantity: 0,
         num: 0,
@@ -728,7 +739,7 @@
       });
     }
   });
-  
+
   tableData.value = newTableData;
   productSelectionVisible.value = false;
 };
diff --git a/src/views/salesManagement/returnOrder/index.vue b/src/views/salesManagement/returnOrder/index.vue
index 0a8257b..e190901 100644
--- a/src/views/salesManagement/returnOrder/index.vue
+++ b/src/views/salesManagement/returnOrder/index.vue
@@ -38,6 +38,9 @@
         <template #status="{ row }">
           <el-tag :type="getStatusType(row.status)">{{ getStatusText(row.status) }}</el-tag>
         </template>
+        <template #stockInApprovalStatus="{ row }">
+          <el-tag :type="getStockInApprovalStatusType(row.stockInApprovalStatus)">{{ getStockInApprovalStatusText(row.stockInApprovalStatus) }}</el-tag>
+        </template>
       </PIMTable>
     </div>
     <form-dia ref="formDia" @close="handleQuery" />
@@ -114,6 +117,7 @@
 const defaultColumns = [
   { label: "閫�璐у崟鍙�", prop: "returnNo", minWidth: 160 },
   { label: "鍗曟嵁鐘舵��", prop: "status", minWidth: 90, dataType: "slot", slot: "status" },
+  { label: "鍏ュ簱瀹℃壒鐘舵��", prop: "stockInApprovalStatus", minWidth: 120, dataType: "slot", slot: "stockInApprovalStatus" },
   { label: "鍒跺崟鏃堕棿", prop: "makeTime", minWidth: 170 },
   { label: "瀹㈡埛鍚嶇О", prop: "customerName", minWidth: 220 },
   { label: "閿�鍞崟鍙�", prop: "salesContractNo", minWidth: 160 },
@@ -207,6 +211,24 @@
   return statusMap[status] || "鏈煡";
 };
 
+const getStockInApprovalStatusType = (status) => {
+  const statusMap = {
+    0: "",
+    1: "success",
+    2: "warning"
+  };
+  return statusMap[status] || "info";
+};
+
+const getStockInApprovalStatusText = (status) => {
+  const statusMap = {
+    0: "鏈鎵�",
+    1: "宸插鎵�",
+    2: "瀹℃壒涓�"
+  };
+  return statusMap[status] || "鏈煡";
+};
+
 onMounted(() => {
   getList();
 });

--
Gitblit v1.9.3