From 04687ca035e6fa517e88470aac7247812f85eb95 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期五, 17 四月 2026 17:09:29 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/dev_New' into dev_New

---
 src/views/productionManagement/productStructure/Detail/index.vue                   |   72 +++++---
 src/views/productionManagement/productionOrder/components/MaterialDetailDialog.vue |   15 +
 src/views/productionManagement/processRoute/index.vue                              |    1 
 src/views/basicData/product/ProductSelectDialog.vue                                |   27 ++-
 src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue |  109 ++++++++++---
 src/views/productionManagement/workOrderManagement/components/MaterialDialog.vue   |  152 ++++++++----------
 src/api/productionManagement/workOrder.js                                          |   11 +
 src/views/productionManagement/processRoute/processRouteItem/index.vue             |   15 +
 src/views/productionManagement/productionOrder/index.vue                           |   23 +-
 src/api/basicData/productModel.js                                                  |    8 +
 src/views/productionManagement/workOrderManagement/index.vue                       |   12 
 11 files changed, 274 insertions(+), 171 deletions(-)

diff --git a/src/api/basicData/productModel.js b/src/api/basicData/productModel.js
index f048f9e..8acc2ef 100644
--- a/src/api/basicData/productModel.js
+++ b/src/api/basicData/productModel.js
@@ -6,4 +6,12 @@
         method: 'get',
         params: query
     })
+}
+
+export function productModelListByUrl(url, query) {
+    return request({
+        url,
+        method: 'get',
+        params: query
+    })
 }
\ No newline at end of file
diff --git a/src/api/productionManagement/workOrder.js b/src/api/productionManagement/workOrder.js
index d2a8095..cdc97cd 100644
--- a/src/api/productionManagement/workOrder.js
+++ b/src/api/productionManagement/workOrder.js
@@ -37,7 +37,7 @@
 // 宸ュ崟-褰撳墠宸ュ簭鐗╂枡鍙拌处
 export function listWorkOrderMaterialLedger(query) {
   return request({
-    url: "/productWorkOrder/material/list",
+    url: "/productOrderMaterial/reportMaterials",
     method: "get",
     params: query,
   });
@@ -69,3 +69,12 @@
     params: query,
   });
 }
+
+// 宸ュ崟-棰嗙敤锛堟彁浜ゅ疄闄呴鐢ㄦ暟閲忥級
+export function pickWorkOrderMaterial(data) {
+  return request({
+    url: "/productWorkOrder/material/pick",
+    method: "post",
+    data,
+  });
+}
diff --git a/src/views/basicData/product/ProductSelectDialog.vue b/src/views/basicData/product/ProductSelectDialog.vue
index c10d29e..ad27baa 100644
--- a/src/views/basicData/product/ProductSelectDialog.vue
+++ b/src/views/basicData/product/ProductSelectDialog.vue
@@ -1,12 +1,12 @@
 <template>
   <el-dialog v-model="visible" title="閫夋嫨浜у搧" width="900px" destroy-on-close :close-on-click-modal="false">
     <el-form :inline="true" :model="query" class="mb-2">
-      <el-form-item label="浜у搧澶х被">
-        <el-input v-model="query.productName" placeholder="杈撳叆浜у搧澶х被" clearable @keyup.enter="onSearch" />
+      <el-form-item label="浜у搧鍚嶇О">
+        <el-input v-model="query.productName" placeholder="杈撳叆浜у搧鍚嶇О" clearable @keyup.enter="onSearch" />
       </el-form-item>
 
-      <el-form-item label="鍨嬪彿鍚嶇О">
-        <el-input v-model="query.model" placeholder="杈撳叆鍨嬪彿鍚嶇О" clearable @keyup.enter="onSearch" />
+      <el-form-item label="浜у搧鍨嬪彿">
+        <el-input v-model="query.model" placeholder="杈撳叆浜у搧鍨嬪彿" clearable @keyup.enter="onSearch" />
       </el-form-item>
 
       <el-form-item>
@@ -20,8 +20,8 @@
       @selection-change="handleSelectionChange" @select="handleSelect">
       <el-table-column type="selection" width="55" />
       <el-table-column type="index" label="搴忓彿" width="60" />
-      <el-table-column prop="productName" label="浜у搧澶х被" min-width="160" />
-      <el-table-column prop="model" label="鍨嬪彿鍚嶇О" min-width="200" />
+      <el-table-column prop="productName" label="浜у搧鍚嶇О" min-width="160" />
+      <el-table-column prop="model" label="浜у搧鍨嬪彿" min-width="200" />
       <el-table-column prop="unit" label="鍗曚綅" min-width="160" />
     </el-table>
 
@@ -43,7 +43,7 @@
 <script setup lang="ts">
 import { computed, onMounted, reactive, ref, watch, nextTick } from "vue";
 import { ElMessage } from "element-plus";
-import { productModelList } from '@/api/basicData/productModel'
+import { productModelList, productModelListByUrl } from '@/api/basicData/productModel'
 
 export type ProductRow = {
   id: number;
@@ -56,6 +56,7 @@
   modelValue: boolean;
   single?: boolean; // 鏄惁鍙兘閫夋嫨涓�涓紝榛樿false锛堝彲閫夋嫨澶氫釜锛�
   topProductParentId?: number; // 涓�绾т骇鍝乮d
+  requestUrl?: string; // 鑷畾涔夋煡璇㈡帴鍙�
 }>();
 
 const emit = defineEmits(['update:modelValue', 'confirm']);
@@ -155,15 +156,19 @@
   loading.value = true;
   try {
     multipleSelection.value = []; // 缈婚〉/鎼滅储鍚庢竻绌洪�夋嫨鏇寸鍚堥鏈�
-    const res: any = await productModelList({
+    const params = {
       productName: query.productName.trim(),
       model: query.model.trim(),
       current: page.pageNum,
       size: page.pageSize,
       topProductParentId: props.topProductParentId,
-    });
-    tableData.value = res.records;
-    total.value = res.total;
+    };
+    const res: any = props.requestUrl
+      ? await productModelListByUrl(props.requestUrl, params)
+      : await productModelList(params);
+    const records = res?.records || res?.data?.records || res?.data || [];
+    tableData.value = Array.isArray(records) ? records : [];
+    total.value = Number(res?.total ?? res?.data?.total ?? tableData.value.length);
   } finally {
     loading.value = false;
   }
diff --git a/src/views/productionManagement/processRoute/index.vue b/src/views/productionManagement/processRoute/index.vue
index 0d1bb14..553dbe2 100644
--- a/src/views/productionManagement/processRoute/index.vue
+++ b/src/views/productionManagement/processRoute/index.vue
@@ -171,6 +171,7 @@
     path: '/productionManagement/processRouteItem',
     query: {
       id: row.id,
+      bomId: row.bomId,
       processRouteCode: row.processRouteCode || '',
       productName: row.productName || '',
       model: row.model || '',
diff --git a/src/views/productionManagement/processRoute/processRouteItem/index.vue b/src/views/productionManagement/processRoute/processRouteItem/index.vue
index aca3550..b673a81 100644
--- a/src/views/productionManagement/processRoute/processRouteItem/index.vue
+++ b/src/views/productionManagement/processRoute/processRouteItem/index.vue
@@ -47,7 +47,13 @@
         </div>
       </div>
     </el-card>
-    
+    <div class="section-title" style="margin-bottom: 10px;">浜у搧缁撴瀯</div>
+    <ProductStructureDetail
+      class="product-structure-panel"
+      style="margin-bottom: 20px;"
+      embedded
+      :bom-id="route.query.bomId"
+    />
     <!-- 琛ㄦ牸瑙嗗浘 -->
     <div v-if="viewMode === 'table'" class="section-header">
       <div class="section-title">宸ヨ壓璺嚎椤圭洰鍒楄〃</div>
@@ -231,7 +237,7 @@
 </template>
 
 <script setup>
-import { ref, computed, getCurrentInstance, onMounted, onUnmounted, nextTick } from "vue";
+import { ref, computed, getCurrentInstance, onMounted, onUnmounted, nextTick, defineAsyncComponent } from "vue";
 import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
 import { findProcessRouteItemList, addOrUpdateProcessRouteItem, sortProcessRouteItem, batchDeleteProcessRouteItem } from "@/api/productionManagement/processRouteItem.js";
 import { findProductProcessRouteItemList, deleteRouteItem, addRouteItem, addOrUpdateProductProcessRouteItem, sortRouteItem } from "@/api/productionManagement/productProcessRoute.js";
@@ -242,6 +248,7 @@
 
 const route = useRoute()
 const { proxy } = getCurrentInstance() || {};
+const ProductStructureDetail = defineAsyncComponent(() => import("@/views/productionManagement/productStructure/Detail/index.vue"));
 
 const routeId = computed(() => route.query.id);
 const orderId = computed(() => route.query.orderId);
@@ -841,6 +848,10 @@
   align-items: center;
 }
 
+.product-structure-panel {
+  margin: 12px 0 20px;
+}
+
 /* 宸ヨ壓璺嚎淇℃伅鍗$墖鏍峰紡 */
 .route-info-card {
   margin-bottom: 20px;
diff --git a/src/views/productionManagement/productStructure/Detail/index.vue b/src/views/productionManagement/productStructure/Detail/index.vue
index 6734830..832c121 100644
--- a/src/views/productionManagement/productStructure/Detail/index.vue
+++ b/src/views/productionManagement/productStructure/Detail/index.vue
@@ -1,6 +1,6 @@
 <template>
-  <div class="app-container">
-    <PageHeader content="浜у搧缁撴瀯璇︽儏">
+  <div :class="embedded ? 'embedded-container' : 'app-container'">
+    <PageHeader v-if="!embedded" content="浜у搧缁撴瀯璇︽儏">
       <template #right-button>
         <el-button v-if="!dataValue.isEdit && !isOrderPage"
                    type="primary"
@@ -119,7 +119,7 @@
                   </el-form-item>
                 </template>
               </el-table-column>
-              <el-table-column label="鎿嶄綔"
+              <el-table-column v-if="!embedded" label="鎿嶄綔"
                                fixed="right"
                                width="200">
                 <template #default="{ row, $index }">
@@ -174,6 +174,18 @@
   const ProductSelectDialog = defineAsyncComponent(
     () => import("@/views/basicData/product/ProductSelectDialog.vue")
   );
+  const props = defineProps({
+    embedded: {
+      type: Boolean,
+      default: false,
+    },
+    // 鏄惧紡鎸囧畾BOM涓婚敭锛堢敤浜庡祵鍏ュ埌鈥滃伐鑹鸿矾绾块」鐩�濈瓑椤甸潰鏃讹紝璺敱 query.id 涓嶆槸 bomId 鐨勬儏鍐碉級
+    bomId: {
+      type: [String, Number],
+      default: undefined,
+    },
+  });
+  const embedded = computed(() => props.embedded);
   const emit = defineEmits(["update:router"]);
   const form = ref();
 
@@ -181,7 +193,8 @@
   const router = useRouter();
   const routeId = computed({
     get() {
-      return route.query.id;
+      // 浼樺厛浣跨敤澶栭儴浼犲叆鐨� bomId锛屽叾娆′娇鐢ㄨ矾鐢辩殑 bomId锛屾渶鍚庡洖閫�鍒拌矾鐢辩殑 id锛堝吋瀹瑰師椤甸潰锛�
+      return props.bomId ?? route.query.bomId ?? route.query.id;
     },
 
     set(val) {
@@ -227,29 +240,27 @@
   };
 
   const fetchData = async () => {
-    if (isOrderPage.value) {
-      // 璁㈠崟鎯呭喌锛氫娇鐢ㄨ鍗曠殑浜у搧缁撴瀯鎺ュ彛
-      const { data } = await listProcessBom({ orderId: routeOrderId.value });
-      dataValue.dataList = (data as any) || [];
-    } else {
-      // 闈炶鍗曟儏鍐碉細浣跨敤鍘熸潵鐨勬帴鍙�
-      const { data } = await queryList(routeId.value);
-      dataValue.dataList = (data as any) || [];
-      // 涓烘墍鏈夐」鍙婂叾瀛愰」璁剧疆name灞炴��
-      const setNameRecursively = (items: any[]) => {
-        items.forEach((item: any) => {
-          item.tempId = item.id;
-          item.processName =
-            dataValue.processOptions.find(option => option.id === item.processId)
-              ?.name || "";
-          if (item.children && item.children.length > 0) {
-            setNameRecursively(item.children);
-          }
-        });
-      };
-      setNameRecursively(dataValue.dataList);
-      console.log(dataValue.dataList, "dataValue.dataList");
+    const setNameRecursively = (items: any[]) => {
+      items.forEach((item: any) => {
+        item.tempId = item.tempId || item.id || new Date().getTime() + Math.random();
+        item.processName =
+          dataValue.processOptions.find(option => option.id === item.processId)?.name || item.processName || "";
+        if (item.children && item.children.length > 0) {
+          setNameRecursively(item.children);
+        }
+      });
+    };
+
+    // 缁熶竴浣跨敤 BOM 鏌ヨ浜у搧缁撴瀯锛�/productStructure/listBybomId/{bomId}
+    // 璇存槑锛氳鍗曢〉涔熶細浠庤矾鐢�/鐖剁粍浠跺甫鍏� bomId锛坮oute.query.bomId 鎴� props.bomId锛�
+    const bomId = routeId.value;
+    if (!bomId) {
+      dataValue.dataList = [];
+      return;
     }
+    const { data } = await queryList(bomId);
+    dataValue.dataList = (data as any) || [];
+    setNameRecursively(dataValue.dataList);
   };
 
   const fetchProcessOptions = async () => {
@@ -518,4 +529,11 @@
     await fetchProcessOptions();
     await fetchData();
   });
-</script>
\ No newline at end of file
+</script>
+
+<style scoped>
+.embedded-container {
+  padding: 0;
+  margin: 0;
+}
+</style>
\ No newline at end of file
diff --git a/src/views/productionManagement/productionOrder/components/MaterialDetailDialog.vue b/src/views/productionManagement/productionOrder/components/MaterialDetailDialog.vue
index 61ed2f5..9c50fc8 100644
--- a/src/views/productionManagement/productionOrder/components/MaterialDetailDialog.vue
+++ b/src/views/productionManagement/productionOrder/components/MaterialDetailDialog.vue
@@ -36,8 +36,9 @@
     <el-dialog v-model="supplementRecordDialogVisible" title="琛ユ枡璁板綍" width="800px">
       <el-table v-loading="supplementRecordLoading" :data="supplementRecordTableData" border row-key="id">
         <el-table-column label="琛ユ枡鏁伴噺" prop="supplementQty" min-width="120" />
-        <el-table-column label="琛ユ枡鏃堕棿" prop="supplementTime" min-width="180" />
-        <el-table-column label="澶囨敞" prop="remark" min-width="200" />
+        <el-table-column label="琛ユ枡浜�" prop="supplementUserName" min-width="120" />
+        <el-table-column label="琛ユ枡鏃ユ湡" prop="supplementTime" min-width="160" />
+        <el-table-column label="琛ユ枡鍘熷洜" prop="supplementReason" min-width="200" />
       </el-table>
       <template #footer>
         <span class="dialog-footer">
@@ -88,8 +89,10 @@
 const supplementRecordTableData = ref([]);
 const returnSummaryDialogVisible = ref(false);
 const returnSummaryList = ref([]);
+const calcReturnQty = item =>
+  Number(item.pickQty || 0) + Number(item.supplementQty || 0) - Number(item.actualQty || 0);
 const canOpenReturnSummary = computed(() =>
-  materialDetailTableData.value.some(item => Number(item.returnQty || 0) > 0)
+  materialDetailTableData.value.some(item => calcReturnQty(item) > 0)
 );
 
 const loadDetailList = async () => {
@@ -133,6 +136,8 @@
 const buildReturnSummary = () => {
   const map = new Map();
   materialDetailTableData.value.forEach(item => {
+    const returnQty = calcReturnQty(item);
+    if (returnQty <= 0) return;
     const key = `${item.materialModelId || ""}_${item.materialName || ""}_${item.materialModel || ""}_${item.unit || ""}`;
     const old = map.get(key) || {
       summaryKey: key,
@@ -141,7 +146,7 @@
       unit: item.unit || "",
       returnQtyTotal: 0,
     };
-    old.returnQtyTotal += Number(item.returnQty || 0);
+    old.returnQtyTotal += returnQty;
     map.set(key, old);
   });
   return Array.from(map.values());
@@ -149,7 +154,7 @@
 
 const openReturnSummaryDialog = async () => {
   if (!canOpenReturnSummary.value) {
-    ElMessage.warning("閫�鏂欐暟閲忓ぇ浜�0鏃舵墠鑳介��鏂欑‘璁�");
+    ElMessage.warning("閫�鏂欐暟閲�=棰嗙敤鏁伴噺+琛ユ枡鏁伴噺-瀹為檯鏁伴噺锛屼笖闇�澶т簬0");
     return;
   }
   returnSummaryList.value = buildReturnSummary();
diff --git a/src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue b/src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue
index f9db112..9e1a852 100644
--- a/src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue
+++ b/src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue
@@ -7,21 +7,24 @@
       <el-table v-loading="materialTableLoading" :data="materialTableData" border row-key="tempId">
         <el-table-column label="宸ュ簭鍚嶇О" min-width="180">
           <template #default="{ row }">
+            <span v-if="row.bom === true">{{ row.processName || "-" }}</span>
             <el-select
-              v-model="row.processId"
+              v-else
+              v-model="row.processName"
               placeholder="璇烽�夋嫨宸ュ簭"
               clearable
               filterable
               style="width: 100%;"
-              @change="val => handleProcessChange(row, val)"
+              @change="val => handleProcessNameChange(row, val)"
             >
-              <el-option v-for="item in processOptions" :key="item.id" :label="item.name" :value="item.id" />
+              <el-option v-for="item in processOptions" :key="item.id" :label="item.name" :value="item.name" />
             </el-select>
           </template>
         </el-table-column>
         <el-table-column label="鍘熸枡鍚嶇О" min-width="160">
           <template #default="{ row }">
-            <el-button type="primary" link @click="openMaterialProductSelect(row)">
+            <span v-if="row.bom === true">{{ row.materialName || "-" }}</span>
+            <el-button v-else type="primary" link @click="openMaterialProductSelect(row)">
               {{ row.materialName || "閫夋嫨鍘熸枡" }}
             </el-button>
           </template>
@@ -33,7 +36,9 @@
         </el-table-column>
         <el-table-column label="闇�姹傛暟閲�" min-width="120">
           <template #default="{ row }">
+            <span v-if="row.bom === true">{{ row.requiredQty ?? "-" }}</span>
             <el-input-number
+              v-else
               v-model="row.requiredQty"
               :min="0"
               :precision="3"
@@ -62,8 +67,8 @@
           </template>
         </el-table-column>
         <el-table-column label="鎿嶄綔" width="90" fixed="right">
-          <template #default="{ $index }">
-            <el-button type="danger" link @click="handleDeleteMaterialRow($index)">鍒犻櫎</el-button>
+          <template #default="{ $index, row }">
+            <el-button v-if="row.bom !== true" type="danger" link @click="handleDeleteMaterialRow($index)">鍒犻櫎</el-button>
           </template>
         </el-table-column>
       </el-table>
@@ -79,15 +84,21 @@
       v-model="materialProductDialogVisible"
       @confirm="handleMaterialProductConfirm"
       single
+      request-url="/stockInventory/rawMaterials"
     />
   </div>
 </template>
 
 <script setup>
 import { computed, ref, watch } from "vue";
+import { ElMessage } from "element-plus";
 import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
-import { processList } from "@/api/productionManagement/productionProcess.js";
-import { listMaterialPickingLedger, saveMaterialPickingLedger } from "@/api/productionManagement/productionOrder.js";
+import { findProductProcessRouteItemList } from "@/api/productionManagement/productProcessRoute.js";
+import {
+  listMaterialPickingDetail,
+  listMaterialPickingLedger,
+  saveMaterialPickingLedger,
+} from "@/api/productionManagement/productionOrder.js";
 
 const props = defineProps({
   modelValue: { type: Boolean, default: false },
@@ -112,7 +123,9 @@
   tempId: row.id || `temp_${++materialTempId}`,
   id: row.id,
   processId: row.processId,
+  productProcessId: row.productProcessId || row.processId,
   processName: row.processName || "",
+  bom: row.bom === true,
   materialModelId: row.materialModelId,
   materialName: row.materialName || "",
   materialModel: row.materialModel || "",
@@ -122,9 +135,23 @@
 });
 
 const getProcessOptions = async () => {
-  if (processOptions.value.length > 0) return;
-  const res = await processList({});
-  processOptions.value = res.data || [];
+  if (!props.orderRow?.id) return;
+  const res = await findProductProcessRouteItemList({ orderId: props.orderRow.id });
+  const routeList = Array.isArray(res?.data) ? res.data : res?.data?.records || [];
+  const processMap = new Map();
+  routeList.forEach(item => {
+    const processId = item.processId;
+    const processName = item.processName;
+    if (!processId || !processName) return;
+    const key = `${processId}_${processName}`;
+    if (!processMap.has(key)) {
+      processMap.set(key, {
+        id: processId,
+        name: processName,
+      });
+    }
+  });
+  processOptions.value = Array.from(processMap.values());
 };
 
 const loadMaterialData = async () => {
@@ -133,8 +160,19 @@
   materialTableData.value = [];
   await getProcessOptions();
   try {
-    const res = await listMaterialPickingLedger({ orderId: props.orderRow.id });
-    materialTableData.value = (res.data || []).map(item => createMaterialRow(item));
+    const detailRes = await listMaterialPickingDetail({ orderId: props.orderRow.id });
+    const detailList = Array.isArray(detailRes?.data)
+      ? detailRes.data
+      : detailRes?.data?.records || [];
+    if (detailList.length > 0) {
+      materialTableData.value = detailList.map(item => createMaterialRow(item));
+      return;
+    }
+    const ledgerRes = await listMaterialPickingLedger({ orderId: props.orderRow.id });
+    const ledgerList = Array.isArray(ledgerRes?.data)
+      ? ledgerRes.data
+      : ledgerRes?.data?.records || [];
+    materialTableData.value = ledgerList.map(item => createMaterialRow(item));
   } finally {
     materialTableLoading.value = false;
   }
@@ -162,9 +200,9 @@
   materialTableData.value.splice(index, 1);
 };
 
-const handleProcessChange = (row, processId) => {
-  const process = processOptions.value.find(item => item.id === processId);
-  row.processName = process?.name || "";
+const handleProcessNameChange = (row, processName) => {
+  const process = processOptions.value.find(item => item.name === processName);
+  row.productProcessId = process?.id;
 };
 
 const handleRequiredQtyChange = (row, val) => {
@@ -186,37 +224,56 @@
   if (index < 0 || !materialTableData.value[index]) return;
   const product = products[0];
   const row = materialTableData.value[index];
-  row.materialModelId = product.id;
-  row.materialName = product.productName || "";
-  row.materialModel = product.model || "";
-  row.unit = product.unit || "";
+  row.materialModelId = product.materialModelId || product.modelId || product.id;
+  row.materialName = product.materialName || product.productName || product.name || "";
+  row.materialModel = product.materialModel || product.model || "";
+  row.unit = product.unit || product.measureUnit || "";
   currentMaterialSelectRowIndex.value = -1;
   materialProductDialogVisible.value = false;
 };
 
 const validateMaterialRows = () => {
-  if (materialTableData.value.length === 0) return false;
-  return !materialTableData.value.find(
+  if (materialTableData.value.length === 0) {
+    return { valid: false, message: "璇峰厛鏂板棰嗘枡鏁版嵁" };
+  }
+  const invalidNewRow = materialTableData.value.find(
+    item => item.bom !== true && (!item.processName || !item.materialName)
+  );
+  if (invalidNewRow) {
+    return { valid: false, message: "鏂板琛岀殑宸ュ簭鍚嶇О鍜屽師鏂欏悕绉颁负蹇呭~椤�" };
+  }
+  const invalidRow = materialTableData.value.find(
     item =>
-      !item.processId ||
-      !item.materialModelId ||
+      !item.processName ||
+      !item.materialName ||
       item.requiredQty === null ||
       item.requiredQty === undefined ||
       item.pickQty === null ||
       item.pickQty === undefined
   );
+  if (invalidRow) {
+    return { valid: false, message: "璇峰畬鍠勫伐搴忋�佸師鏂欏拰鏁伴噺鍚庡啀淇濆瓨" };
+  }
+  return { valid: true, message: "" };
 };
 
 const handleMaterialSave = async () => {
-  if (!props.orderRow?.id || !validateMaterialRows()) return;
+  if (!props.orderRow?.id) return;
+  const validateResult = validateMaterialRows();
+  if (!validateResult.valid) {
+    ElMessage.warning(validateResult.message);
+    return;
+  }
   materialSaving.value = true;
   try {
     await saveMaterialPickingLedger({
       orderId: props.orderRow.id,
       items: materialTableData.value.map(item => ({
         id: item.id,
-        processId: item.processId,
+        processId: item.processName,
+        productProcessId: item.productProcessId,
         processName: item.processName,
+        bom: item.bom === true,
         materialModelId: item.materialModelId,
         materialName: item.materialName,
         materialModel: item.materialModel,
diff --git a/src/views/productionManagement/productionOrder/index.vue b/src/views/productionManagement/productionOrder/index.vue
index 8d4a8aa..667688e 100644
--- a/src/views/productionManagement/productionOrder/index.vue
+++ b/src/views/productionManagement/productionOrder/index.vue
@@ -48,7 +48,7 @@
                      @click="handleQuery">鎼滅储</el-button>
         </el-form-item>
       </el-form>
-      <div>
+      <div class="action-buttons">
         <el-button type="primary" @click="isShowNewModal = true">鏂板</el-button>
         <el-button type="danger" @click="handleDelete">鍒犻櫎</el-button>
         <el-button @click="handleOut">瀵煎嚭</el-button>
@@ -224,13 +224,13 @@
             openBindRouteDialog(row);
           },
         },
-        {
-          name: "浜у搧缁撴瀯",
-          type: "text",
-          clickFun: row => {
-            showProductStructure(row);
-          },
-        },
+        // {
+        //   name: "浜у搧缁撴瀯",
+        //   type: "text",
+        //   clickFun: row => {
+        //     showProductStructure(row);
+        //   },
+        // },
         {
           name: "棰嗘枡",
           type: "text",
@@ -421,6 +421,7 @@
         path: "/productionManagement/processRouteItem",
         query: {
           id: data.id,
+          bomId: data.bomId,
           processRouteCode: data.processRouteCode || "",
           productName: data.productName || "",
           model: data.model || "",
@@ -504,6 +505,12 @@
   align-items: start;
 }
 
+.action-buttons {
+  display: flex;
+  flex-wrap: nowrap;
+  gap: 8px;
+}
+
 :deep(.yellow) {
   background-color: #FAF0DE;
 }
diff --git a/src/views/productionManagement/workOrderManagement/components/MaterialDialog.vue b/src/views/productionManagement/workOrderManagement/components/MaterialDialog.vue
index e5eee56..575d888 100644
--- a/src/views/productionManagement/workOrderManagement/components/MaterialDialog.vue
+++ b/src/views/productionManagement/workOrderManagement/components/MaterialDialog.vue
@@ -11,18 +11,33 @@
         <el-table-column label="鍘熸枡鍚嶇О" prop="materialName" min-width="140" />
         <el-table-column label="鍘熸枡鍨嬪彿" prop="materialModel" 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="pickQty" min-width="100" />
         <el-table-column label="琛ユ枡鏁伴噺" prop="supplementQty" min-width="100" />
-        <el-table-column label="閫�鏂欐暟閲�" prop="returnQty" min-width="100" />
-        <el-table-column label="瀹為檯鏁伴噺" prop="actualQty" min-width="100" />
-        <el-table-column label="鎿嶄綔" align="center" fixed="right" width="220">
+        <el-table-column label="瀹為檯鏁伴噺" min-width="140">
+          <template #default="{ row }">
+            <el-input-number
+              v-model="row.actualQty"
+              :min="0"
+              :precision="3"
+              :step="1"
+              controls-position="right"
+              style="width: 100%;"
+            />
+          </template>
+        </el-table-column>
+        <el-table-column label="鎿嶄綔" align="center" fixed="right" width="180">
           <template #default="{ row }">
             <el-button type="primary" link @click="openSupplementDialog(row)">琛ユ枡</el-button>
-            <el-button type="warning" link @click="openReturnDialog(row)">閫�鏂�</el-button>
             <el-button type="info" link @click="openSupplementRecordDialog(row)">琛ユ枡璁板綍</el-button>
           </template>
         </el-table-column>
       </el-table>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button type="primary" :loading="pickSubmitting" @click="handleSubmitPick">棰嗙敤</el-button>
+          <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+        </span>
+      </template>
     </el-dialog>
 
     <FormDialog
@@ -60,31 +75,6 @@
       </template>
     </FormDialog>
 
-    <FormDialog
-      v-model="returnDialogVisible"
-      title="閫�鏂�"
-      width="500px"
-      @confirm="handleSubmitReturn"
-    >
-      <el-form ref="returnFormRef" :model="returnForm" :rules="returnRules" label-width="120px">
-        <el-form-item label="閫�鏂欐暟閲�" prop="returnQty">
-          <el-input-number
-            v-model="returnForm.returnQty"
-            :min="0.001"
-            :precision="3"
-            :step="1"
-            style="width: 100%;"
-          />
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <span class="dialog-footer">
-          <el-button type="primary" :loading="returnSubmitting" @click="handleSubmitReturn">纭畾</el-button>
-          <el-button @click="returnDialogVisible = 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" />
@@ -108,8 +98,8 @@
 import {
   listWorkOrderMaterialLedger,
   addWorkOrderMaterialSupplement,
-  addWorkOrderMaterialReturn,
   listWorkOrderMaterialSupplementRecord,
+  pickWorkOrderMaterial,
 } from "@/api/productionManagement/workOrder.js";
 
 const props = defineProps({
@@ -134,6 +124,7 @@
 const materialTableData = ref([]);
 const currentMaterialRow = ref(null);
 const currentMaterialOrderRow = ref(null);
+const pickSubmitting = ref(false);
 
 const supplementDialogVisible = ref(false);
 const supplementSubmitting = ref(false);
@@ -141,13 +132,6 @@
 const supplementForm = reactive({
   supplementQty: null,
   supplementReason: "",
-});
-
-const returnDialogVisible = ref(false);
-const returnSubmitting = ref(false);
-const returnFormRef = ref(null);
-const returnForm = reactive({
-  returnQty: null,
 });
 
 const supplementRecordDialogVisible = ref(false);
@@ -158,10 +142,6 @@
   supplementQty: [{ required: true, message: "璇疯緭鍏ヨˉ鏂欐暟閲�", trigger: "blur" }],
   supplementReason: [{ required: true, message: "璇疯緭鍏ヨˉ鏂欏師鍥�", trigger: "blur" }],
 };
-const returnRules = {
-  returnQty: [{ required: true, message: "璇疯緭鍏ラ��鏂欐暟閲�", trigger: "blur" }],
-};
-
 const loadMaterialTable = async row => {
   if (!row?.id) return;
   currentMaterialOrderRow.value = row;
@@ -234,49 +214,6 @@
   });
 };
 
-const openReturnDialog = row => {
-  currentMaterialRow.value = row;
-  returnForm.returnQty = null;
-  returnDialogVisible.value = true;
-  nextTick(() => {
-    returnFormRef.value?.clearValidate();
-  });
-};
-
-const handleSubmitReturn = () => {
-  returnFormRef.value?.validate(async valid => {
-    if (!valid || !currentMaterialRow.value?.id) {
-      ElMessage.warning("缂哄皯鐗╂枡鏄庣粏ID");
-      return;
-    }
-    const returnQty = Number(returnForm.returnQty);
-    const minQty =
-      Number(currentMaterialRow.value.pickQty || 0) +
-      Number(currentMaterialRow.value.supplementQty || 0);
-    if (returnQty < minQty) {
-      ElMessage.warning(`閫�鏂欐暟閲忎笉鑳戒綆浜庨鐢ㄦ暟閲�+琛ユ枡鏁伴噺锛�${minQty}锛塦);
-      return;
-    }
-    returnSubmitting.value = true;
-    try {
-      await addWorkOrderMaterialReturn({
-        materialLedgerId: currentMaterialRow.value.id,
-        returnQty,
-        workOrderId: currentMaterialOrderRow.value?.id,
-      });
-      returnDialogVisible.value = false;
-      await loadMaterialTable(currentMaterialOrderRow.value);
-      ElMessage.success("閫�鏂欐垚鍔�");
-      emit("refresh");
-    } catch (e) {
-      console.error("閫�鏂欏け璐�", e);
-      ElMessage.error("閫�鏂欏け璐�");
-    } finally {
-      returnSubmitting.value = false;
-    }
-  });
-};
-
 const openSupplementRecordDialog = async row => {
   supplementRecordDialogVisible.value = true;
   supplementRecordLoading.value = true;
@@ -293,4 +230,49 @@
     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 === "");
+  if (invalidRow) {
+    return { valid: false, message: "璇峰~鍐欏疄闄呮暟閲忓悗鍐嶉鐢�" };
+  }
+  const exceedRow = materialTableData.value.find(item => {
+    const maxQty = Number(item.pickQty || 0) + Number(item.supplementQty || 0);
+    return Number(item.actualQty || 0) > maxQty;
+  });
+  if (exceedRow) {
+    return { valid: false, message: "瀹為檯鏁伴噺涓嶈兘澶т簬棰嗙敤鏁伴噺+琛ユ枡鏁伴噺" };
+  }
+  return { valid: true, message: "" };
+};
+
+const handleSubmitPick = async () => {
+  if (!currentMaterialOrderRow.value?.id) return;
+  const validateResult = validatePickRows();
+  if (!validateResult.valid) {
+    ElMessage.warning(validateResult.message);
+    return;
+  }
+  pickSubmitting.value = true;
+  try {
+    await pickWorkOrderMaterial({
+      workOrderId: currentMaterialOrderRow.value.id,
+      items: materialTableData.value.map(item => ({
+        materialLedgerId: item.id,
+        actualQty: Number(item.actualQty || 0),
+      })),
+    });
+    ElMessage.success("棰嗙敤鎴愬姛");
+    await loadMaterialTable(currentMaterialOrderRow.value);
+    emit("refresh");
+  } catch (e) {
+    console.error("棰嗙敤澶辫触", e);
+    ElMessage.error("棰嗙敤澶辫触");
+  } finally {
+    pickSubmitting.value = false;
+  }
+};
 </script>
diff --git a/src/views/productionManagement/workOrderManagement/index.vue b/src/views/productionManagement/workOrderManagement/index.vue
index 9d18813..48f8839 100644
--- a/src/views/productionManagement/workOrderManagement/index.vue
+++ b/src/views/productionManagement/workOrderManagement/index.vue
@@ -289,17 +289,17 @@
           },
         },
         {
+          name: "鐗╂枡",
+          clickFun: row => {
+            openMaterialDialog(row);
+          },
+        },
+        {
           name: "鎶ュ伐",
           clickFun: row => {
             showReportDialog(row);
           },
           disabled: row => row.planQuantity <= 0,
-        },
-        {
-          name: "鐗╂枡",
-          clickFun: row => {
-            openMaterialDialog(row);
-          },
         },
       ],
     },

--
Gitblit v1.9.3