From a14c5ce07d20e9f28058f6942dbc00197fac47f1 Mon Sep 17 00:00:00 2001
From: yuan <123@>
Date: 星期四, 13 八月 2026 10:19:19 +0800
Subject: [PATCH] fix: 领料功能修改,移除订单领料,添加工序领料功能,支持按工序查询物料与已领料记录

---
 src/views/productionManagement/workOrderManagement/components/MaterialDialog.vue |  478 ++++++++++++++++++++++++++++++++++-------------------------
 1 files changed, 274 insertions(+), 204 deletions(-)

diff --git a/src/views/productionManagement/workOrderManagement/components/MaterialDialog.vue b/src/views/productionManagement/workOrderManagement/components/MaterialDialog.vue
index 45944b5..9b83ebe 100644
--- a/src/views/productionManagement/workOrderManagement/components/MaterialDialog.vue
+++ b/src/views/productionManagement/workOrderManagement/components/MaterialDialog.vue
@@ -1,35 +1,62 @@
 <template>
   <div>
     <el-dialog v-model="dialogVisible"
-               title="鐗╂枡"
+               title="宸ュ簭棰嗘枡"
                width="1200px"
                @close="handleCloseMaterialDialog">
+      <div class="material-toolbar">
+        <span>
+          鎸夊綋鍓嶅伐搴忕殑 BOM 鐗╂枡棰嗙敤锛屼篃鍙坊鍔� BOM 澶栫殑鐗╂枡锛涘~鍐欐湰娆¢鏂欐暟閲忓苟閫夋嫨鎵瑰彿鍚庨鐢紝搴撳瓨绔嬪嵆鎵e噺銆�
+        </span>
+        <el-button type="primary"
+                   link
+                   @click="openProductDialog">娣诲姞鐗╂枡</el-button>
+      </div>
       <el-table v-loading="materialTableLoading"
-                :data="materialTableData"
+                :data="pickRows"
                 border
-                row-key="id">
+                row-key="rowKey">
         <el-table-column label="宸ュ簭鍚嶇О"
-                         prop="processName"
+                         prop="operationName"
+                         min-width="120" />
+        <el-table-column label="鐗╂枡鍚嶇О"
+                         prop="productName"
                          min-width="140" />
-        <el-table-column label="鍘熸枡鍚嶇О"
-                         prop="materialName"
-                         min-width="140" />
-        <el-table-column label="鍘熸枡鍨嬪彿"
-                         prop="materialModel"
+        <el-table-column label="鐗╂枡瑙勬牸"
+                         prop="model"
                          min-width="140" />
         <el-table-column label="璁¢噺鍗曚綅"
                          prop="unit"
-                         min-width="100" />
-        <el-table-column label="绾胯竟浠撴暟閲�"
-                         prop="pickQty"
-                         min-width="100" />
-        <el-table-column label="琛ユ枡鏁伴噺"
-                         prop="supplementQty"
-                         min-width="100" />
-        <el-table-column label="瀹為檯鏁伴噺"
-                         min-width="140">
+                         width="90" />
+        <el-table-column label="鏉ユ簮"
+                         width="90">
           <template #default="{ row }">
-            <el-input-number v-model="row.actualQty"
+            <el-tag :type="row.bom ? 'success' : 'info'"
+                    size="small">{{ row.bom ? "BOM" : "鎵嬪伐" }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="鍙敤搴撳瓨"
+                         prop="stockQuantity"
+                         min-width="100" />
+        <el-table-column label="鎵瑰彿"
+                         min-width="200">
+          <template #default="{ row }">
+            <el-select v-model="row.batchNo"
+                       placeholder="璇烽�夋嫨鎵瑰彿"
+                       filterable
+                       clearable
+                       style="width: 100%;">
+              <el-option v-for="batchNo in row.batchNoList || []"
+                         :key="batchNo"
+                         :label="batchNo"
+                         :value="batchNo" />
+            </el-select>
+          </template>
+        </el-table-column>
+        <el-table-column label="鏈棰嗘枡鏁伴噺"
+                         min-width="150">
+          <template #default="{ row }">
+            <el-input-number v-model="row.pickQuantity"
                              :min="0"
                              :precision="3"
                              :step="1"
@@ -40,17 +67,75 @@
         <el-table-column label="鎿嶄綔"
                          align="center"
                          fixed="right"
-                         width="180">
-          <template #default="{ row }">
-            <el-button type="primary"
+                         width="90">
+          <template #default="{ row, $index }">
+            <el-button v-if="!row.bom"
+                       type="danger"
                        link
-                       @click="openSupplementDialog(row)">琛ユ枡</el-button>
-            <el-button type="info"
-                       link
-                       @click="openSupplementRecordDialog(row)">琛ユ枡璁板綍</el-button>
+                       @click="removePickRow($index)">绉婚櫎</el-button>
           </template>
         </el-table-column>
       </el-table>
+
+      <div class="material-section-title">鏈伐搴忓凡棰嗘枡璁板綍</div>
+      <el-table v-loading="materialTableLoading"
+                :data="pickedRows"
+                border
+                row-key="id">
+        <el-table-column label="鐗╂枡鍚嶇О"
+                         prop="productName"
+                         min-width="140" />
+        <el-table-column label="鐗╂枡瑙勬牸"
+                         prop="model"
+                         min-width="140" />
+        <el-table-column label="鎵瑰彿"
+                         prop="batchNo"
+                         min-width="150" />
+        <el-table-column label="璁¢噺鍗曚綅"
+                         prop="unit"
+                         width="90" />
+        <el-table-column label="棰嗘枡鏁伴噺"
+                         prop="pickQuantity"
+                         min-width="110" />
+        <el-table-column label="閫�鏂欐暟閲�"
+                         min-width="110">
+          <template #default="{ row }">{{ row.returnQty || 0 }}</template>
+        </el-table-column>
+        <el-table-column label="鍑�棰嗘枡鏁伴噺"
+                         prop="effectiveQuantity"
+                         min-width="120" />
+        <el-table-column label="棰嗘枡鏃堕棿"
+                         min-width="120">
+          <template #default="{ row }">
+            {{ formatDate(row.createTime) }}
+          </template>
+        </el-table-column>
+        <el-table-column label="鏈閫�鏂欐暟閲�"
+                         min-width="150">
+          <template #default="{ row }">
+            <el-input-number v-model="row.currentReturnQty"
+                             :min="0"
+                             :max="Number(row.effectiveQuantity || 0)"
+                             :precision="3"
+                             :step="1"
+                             controls-position="right"
+                             style="width: 100%;" />
+          </template>
+        </el-table-column>
+        <el-table-column label="鎿嶄綔"
+                         align="center"
+                         fixed="right"
+                         width="90">
+          <template #default="{ row }">
+            <el-button type="primary"
+                       link
+                       :loading="returningId === row.id"
+                       :disabled="Number(row.effectiveQuantity || 0) <= 0"
+                       @click="handleReturnMaterial(row)">閫�鏂�</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
       <template #footer>
         <span class="dialog-footer">
           <el-button type="primary"
@@ -60,80 +145,25 @@
         </span>
       </template>
     </el-dialog>
-    <FormDialog v-model="supplementDialogVisible"
-                title="琛ユ枡"
-                width="500px"
-                @confirm="handleSubmitSupplement">
-      <el-form ref="supplementFormRef"
-               :model="supplementForm"
-               :rules="supplementRules"
-               label-width="100px">
-        <el-form-item label="琛ユ枡鏁伴噺"
-                      prop="supplementQty">
-          <el-input-number v-model="supplementForm.supplementQty"
-                           :min="0.001"
-                           :precision="3"
-                           :step="1"
-                           style="width: 100%;" />
-        </el-form-item>
-        <el-form-item label="琛ユ枡鍘熷洜"
-                      prop="supplementReason">
-          <el-input v-model="supplementForm.supplementReason"
-                    type="textarea"
-                    :rows="3"
-                    maxlength="200"
-                    show-word-limit
-                    placeholder="璇疯緭鍏ヨˉ鏂欏師鍥�" />
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <span class="dialog-footer">
-          <el-button type="primary"
-                     :loading="supplementSubmitting"
-                     @click="handleSubmitSupplement">纭畾</el-button>
-          <el-button @click="supplementDialogVisible = false">鍙栨秷</el-button>
-        </span>
-      </template>
-    </FormDialog>
-    <el-dialog v-model="supplementRecordDialogVisible"
-               title="琛ユ枡璁板綍"
-               width="900px">
-      <el-table v-loading="supplementRecordLoading"
-                :data="supplementRecordTableData"
-                border
-                row-key="id">
-        <el-table-column label="琛ユ枡鏁伴噺"
-                         prop="supplementQty"
-                         min-width="100" />
-        <el-table-column label="琛ユ枡鍘熷洜"
-                         prop="supplementReason"
-                         min-width="200" />
-        <el-table-column label="琛ユ枡浜�"
-                         prop="supplementUserName"
-                         min-width="120" />
-        <el-table-column label="琛ユ枡鏃ユ湡"
-                         prop="supplementTime"
-                         min-width="160" />
-      </el-table>
-      <template #footer>
-        <span class="dialog-footer">
-          <el-button @click="supplementRecordDialogVisible = false">鍏抽棴</el-button>
-        </span>
-      </template>
-    </el-dialog>
+    <ProductSelectDialog v-if="productDialogVisible"
+                         v-model:model-value="productDialogVisible"
+                         :single="true"
+                         @confirm="handleProductConfirm" />
   </div>
 </template>
 
 <script setup>
-  import { computed, nextTick, reactive, ref, watch } from "vue";
-  import { ElMessage } from "element-plus";
-  import FormDialog from "@/components/Dialog/FormDialog.vue";
+  import { computed, defineAsyncComponent, ref, watch } from "vue";
+  import { ElMessage, ElMessageBox } from "element-plus";
   import {
     listWorkOrderMaterialLedger,
-    addWorkOrderMaterialSupplement,
-    listWorkOrderMaterialSupplementRecord,
     pickWorkOrderMaterial,
+    returnWorkOrderMaterial,
   } from "@/api/productionManagement/workOrder.js";
+
+  const ProductSelectDialog = defineAsyncComponent(() =>
+    import("@/views/basicData/product/ProductSelectDialog.vue")
+  );
 
   const props = defineProps({
     modelValue: {
@@ -154,46 +184,54 @@
   });
 
   const materialTableLoading = ref(false);
-  const materialTableData = ref([]);
-  const currentMaterialRow = ref(null);
-  const currentMaterialOrderRow = ref(null);
+  // 鏈鍙鐢ㄧ殑鐗╂枡琛岋細BOM 寤鸿 + 鎵嬪伐娣诲姞
+  const pickRows = ref([]);
+  // 褰撳墠宸ュ簭宸蹭骇鐢熺殑棰嗘枡璁板綍
+  const pickedRows = ref([]);
+  const productDialogVisible = ref(false);
+  const currentWorkOrderRow = ref(null);
   const pickSubmitting = ref(false);
+  const returningId = ref(null);
 
-  const supplementDialogVisible = ref(false);
-  const supplementSubmitting = ref(false);
-  const supplementFormRef = ref(null);
-  const supplementForm = reactive({
-    supplementQty: null,
-    supplementReason: "",
-  });
+  const formatDate = value => (value ? String(value).slice(0, 10) : "-");
 
-  const supplementRecordDialogVisible = ref(false);
-  const supplementRecordLoading = ref(false);
-  const supplementRecordTableData = ref([]);
+  const createRowKey = () => `${Date.now()}_${Math.random()}`;
 
-  const supplementRules = {
-    supplementQty: [
-      { required: true, message: "璇疯緭鍏ヨˉ鏂欐暟閲�", trigger: "blur" },
-    ],
-    supplementReason: [
-      { required: true, message: "璇疯緭鍏ヨˉ鏂欏師鍥�", trigger: "blur" },
-    ],
-  };
   const loadMaterialTable = async row => {
     if (!row?.id) return;
-    currentMaterialOrderRow.value = row;
+    currentWorkOrderRow.value = row;
     materialTableLoading.value = true;
-    materialTableData.value = [];
+    pickRows.value = [];
+    pickedRows.value = [];
     try {
-      const res = await listWorkOrderMaterialLedger({
-        workOrderId: row.id,
-        processId: row.processId,
-        productProcessRouteItemId: row.productProcessRouteItemId,
-      });
-      materialTableData.value = res.data || [];
+      const res = await listWorkOrderMaterialLedger(row.id);
+      const records = Array.isArray(res.data) ? res.data : res.data?.records || [];
+      pickRows.value = records
+        .filter(item => item.picked === false || !item.id)
+        .map(item => ({
+          rowKey: createRowKey(),
+          bom: item.bom !== false,
+          productModelId: item.productModelId,
+          productName: item.productName || "",
+          model: item.model || "",
+          unit: item.unit || "",
+          operationName: item.operationName || "",
+          stockQuantity: item.stockQuantity ?? 0,
+          batchNoList: item.batchNoList || [],
+          batchNo: "",
+          pickQuantity: 0,
+        }));
+      pickedRows.value = records
+        .filter(item => item.picked === true || item.id)
+        .map(item => ({
+          ...item,
+          pickQuantity: item.pickQuantity ?? item.quantity ?? 0,
+          effectiveQuantity: item.effectiveQuantity ?? 0,
+          currentReturnQty: 0,
+        }));
     } catch (e) {
-      console.error("鑾峰彇鐗╂枡鍙拌处澶辫触", e);
-      ElMessage.error("鑾峰彇鐗╂枡鍙拌处澶辫触");
+      console.error("鑾峰彇宸ュ簭鐗╂枡澶辫触", e);
+      ElMessage.error("鑾峰彇宸ュ簭鐗╂枡澶辫触");
     } finally {
       materialTableLoading.value = false;
     }
@@ -209,112 +247,144 @@
   );
 
   const handleCloseMaterialDialog = () => {
-    materialTableData.value = [];
-    currentMaterialRow.value = null;
-    currentMaterialOrderRow.value = null;
+    pickRows.value = [];
+    pickedRows.value = [];
+    currentWorkOrderRow.value = null;
   };
 
-  const openSupplementDialog = row => {
-    currentMaterialRow.value = row;
-    supplementForm.supplementQty = null;
-    supplementForm.supplementReason = "";
-    supplementDialogVisible.value = true;
-    nextTick(() => {
-      supplementFormRef.value?.clearValidate();
-    });
+  const openProductDialog = () => {
+    productDialogVisible.value = true;
   };
 
-  const handleSubmitSupplement = () => {
-    supplementFormRef.value?.validate(async valid => {
-      if (!valid || !currentMaterialRow.value?.id) {
-        ElMessage.warning("缂哄皯鐗╂枡鏄庣粏ID");
-        return;
-      }
-      supplementSubmitting.value = true;
-      try {
-        await addWorkOrderMaterialSupplement({
-          materialLedgerId: currentMaterialRow.value.id,
-          supplementQty: Number(supplementForm.supplementQty),
-          supplementReason: supplementForm.supplementReason,
-          workOrderId: currentMaterialOrderRow.value?.id,
-        });
-        supplementDialogVisible.value = false;
-        await loadMaterialTable(currentMaterialOrderRow.value);
-        ElMessage.success("琛ユ枡鎴愬姛");
-        emit("refresh");
-      } catch (e) {
-        console.error("琛ユ枡澶辫触", e);
-        ElMessage.error("琛ユ枡澶辫触");
-      } finally {
-        supplementSubmitting.value = false;
-      }
-    });
-  };
+  const handleProductConfirm = products => {
+    const product = Array.isArray(products) ? products[0] : null;
+    if (!product) return;
 
-  const openSupplementRecordDialog = async row => {
-    supplementRecordDialogVisible.value = true;
-    supplementRecordLoading.value = true;
-    supplementRecordTableData.value = [];
-    try {
-      const res = await listWorkOrderMaterialSupplementRecord({
-        materialLedgerId: row.id,
-      });
-      supplementRecordTableData.value = res.data || [];
-    } catch (e) {
-      console.error("鑾峰彇琛ユ枡璁板綍澶辫触", e);
-      ElMessage.error("鑾峰彇琛ユ枡璁板綍澶辫触");
-    } finally {
-      supplementRecordLoading.value = false;
-    }
-  };
-
-  const validatePickRows = () => {
-    if (materialTableData.value.length === 0) {
-      return { valid: false, message: "鏆傛棤鍙鐢ㄧ墿鏂�" };
-    }
-    const invalidRow = materialTableData.value.find(
-      item =>
-        item.actualQty === null ||
-        item.actualQty === undefined ||
-        item.actualQty === ""
+    const productModelId = product.id || product.productModelId || product.modelId;
+    const exists = pickRows.value.some(
+      item => String(item.productModelId || "") === String(productModelId || "")
     );
-    if (invalidRow) {
-      return { valid: false, message: "璇峰~鍐欏疄闄呮暟閲忓悗鍐嶉鐢�" };
+    if (exists) {
+      ElMessage.warning("璇ョ墿鏂欏凡鍦ㄩ鏂欏垪琛ㄤ腑");
+      return;
     }
-    const exceedRow = materialTableData.value.find(item => {
-      const maxQty = Number(item.pickQty || 0) + Number(item.supplementQty || 0);
-      return Number(item.actualQty || 0) > maxQty;
+
+    pickRows.value.push({
+      rowKey: createRowKey(),
+      bom: false,
+      productModelId,
+      productName: product.productName || product.materialName || "",
+      model: product.model || product.materialModel || "",
+      unit: product.unit || "",
+      operationName:
+        currentWorkOrderRow.value?.processName ||
+        currentWorkOrderRow.value?.operationName ||
+        "",
+      stockQuantity: product.stockQuantity ?? 0,
+      batchNoList: product.batchNoList || [],
+      batchNo: "",
+      pickQuantity: 0,
     });
-    if (exceedRow) {
-      return { valid: false, message: "瀹為檯鏁伴噺涓嶈兘澶т簬棰嗙敤鏁伴噺+琛ユ枡鏁伴噺" };
+  };
+
+  const removePickRow = index => {
+    pickRows.value.splice(index, 1);
+  };
+
+  const handleReturnMaterial = async row => {
+    const returnQty = Number(row.currentReturnQty || 0);
+    if (returnQty <= 0) {
+      ElMessage.warning("璇疯緭鍏ユ湰娆¢��鏂欐暟閲�");
+      return;
     }
-    return { valid: true, message: "" };
+    if (returnQty > Number(row.effectiveQuantity || 0)) {
+      ElMessage.warning("閫�鏂欐暟閲忎笉鑳借秴杩囧噣棰嗘枡鏁伴噺");
+      return;
+    }
+
+    await ElMessageBox.confirm(
+      `纭閫�鍥� ${row.productName || "璇ョ墿鏂�"} ${returnQty}${row.unit || ""}锛焋,
+      "閫�鏂欑‘璁�",
+      { type: "warning" }
+    );
+
+    returningId.value = row.id;
+    try {
+      await returnWorkOrderMaterial({
+        productionOrderId: row.productionOrderId,
+        pickList: [
+          {
+            id: row.id,
+            productionOrderId: row.productionOrderId,
+            returned: true,
+            returnQty,
+          },
+        ],
+      });
+      ElMessage.success("閫�鏂欐垚鍔�");
+      await loadMaterialTable(currentWorkOrderRow.value);
+      emit("refresh");
+    } catch (e) {
+      if (e !== "cancel" && e !== "close") {
+        console.error("閫�鏂欏け璐�", e);
+      }
+    } finally {
+      returningId.value = null;
+    }
   };
 
   const handleSubmitPick = async () => {
-    if (!currentMaterialOrderRow.value?.id) return;
-    const validateResult = validatePickRows();
-    if (!validateResult.valid) {
-      ElMessage.warning(validateResult.message);
+    if (!currentWorkOrderRow.value?.id) return;
+    const submitRows = pickRows.value.filter(
+      item => Number(item.pickQuantity || 0) > 0
+    );
+    if (submitRows.length === 0) {
+      ElMessage.warning("璇疯嚦灏戝~鍐欎竴涓墿鏂欑殑鏈棰嗘枡鏁伴噺");
       return;
     }
+    const missingBatchRow = submitRows.find(
+      item => (item.batchNoList || []).length > 0 && !item.batchNo
+    );
+    if (missingBatchRow) {
+      ElMessage.warning("璇烽�夋嫨棰嗘枡鎵瑰彿");
+      return;
+    }
+
     pickSubmitting.value = true;
     try {
       await pickWorkOrderMaterial({
-        workOrderId: currentMaterialOrderRow.value.id,
-        items: materialTableData.value.map(item => ({
-          materialLedgerId: item.id,
-          actualQty: Number(item.actualQty || 0),
+        productionOperationTaskId: currentWorkOrderRow.value.id,
+        pickList: submitRows.map(item => ({
+          productModelId: item.productModelId,
+          batchNo: item.batchNo || undefined,
+          pickQuantity: Number(item.pickQuantity || 0),
         })),
       });
       ElMessage.success("棰嗙敤鎴愬姛");
-      await loadMaterialTable(currentMaterialOrderRow.value);
+      await loadMaterialTable(currentWorkOrderRow.value);
       emit("refresh");
     } catch (e) {
       console.error("棰嗙敤澶辫触", e);
-      ElMessage.error("棰嗙敤澶辫触");
     } finally {
       pickSubmitting.value = false;
     }
   };
 </script>
+
+<style scoped>
+  .material-toolbar {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    margin-bottom: 12px;
+    color: #606266;
+    font-size: 13px;
+  }
+
+  .material-section-title {
+    margin: 16px 0 8px;
+    color: #303133;
+    font-size: 14px;
+    font-weight: 600;
+  }
+</style>

--
Gitblit v1.9.3