From dacc95761cf7090c628fc37a5d4f8bb825ccbbb0 Mon Sep 17 00:00:00 2001
From: yyb <995253665@qq.com>
Date: 星期六, 16 五月 2026 15:41:45 +0800
Subject: [PATCH] 企业新闻和通知公告

---
 src/views/productionManagement/productionOrder/components/MaterialDetailDialog.vue |  462 ++++++++++++++++++++++++++++++---------------------------
 1 files changed, 241 insertions(+), 221 deletions(-)

diff --git a/src/views/productionManagement/productionOrder/components/MaterialDetailDialog.vue b/src/views/productionManagement/productionOrder/components/MaterialDetailDialog.vue
index f93199a..370815e 100644
--- a/src/views/productionManagement/productionOrder/components/MaterialDetailDialog.vue
+++ b/src/views/productionManagement/productionOrder/components/MaterialDetailDialog.vue
@@ -1,43 +1,97 @@
 <template>
   <div>
-    <el-dialog v-model="dialogVisible" title="棰嗘枡璇︽儏" width="1400px" @close="handleClose">
-      <el-table v-loading="materialDetailLoading" :data="materialDetailTableData" border row-key="id">
-        <el-table-column label="宸ュ簭鍚嶇О" prop="processName" min-width="180" />
-        <el-table-column label="鍘熸枡鍚嶇О" prop="materialName" min-width="160" />
-        <el-table-column label="鍘熸枡鍨嬪彿" prop="materialModel" min-width="180" />
-        <el-table-column label="闇�姹傛暟閲�" prop="requiredQty" min-width="110" />
-        <el-table-column label="璁¢噺鍗曚綅" prop="unit" width="100" />
-        <el-table-column label="棰嗙敤鏁伴噺" prop="pickQty" min-width="110" />
-        <el-table-column label="琛ユ枡鏁伴噺" min-width="120">
+    <el-dialog v-model="dialogVisible"
+               title="棰嗘枡璇︽儏"
+               width="1400px"
+               @close="handleClose">
+      <el-table v-loading="materialDetailLoading"
+                :data="materialDetailTableData"
+                border
+                row-key="id">
+        <el-table-column label="宸ュ簭鍚嶇О"
+                         prop="operationName"
+                         min-width="180" />
+        <el-table-column label="鍘熸枡鍚嶇О"
+                         prop="productName"
+                         min-width="160" />
+        <el-table-column label="鍘熸枡鍨嬪彿"
+                         prop="model"
+                         min-width="180" />
+        <el-table-column label="鎵瑰彿"
+                         prop="batchNo"
+                         min-width="150" />
+        <el-table-column label="闇�姹傛暟閲�"
+                         prop="demandedQuantity"
+                         min-width="110" />
+        <el-table-column label="璁¢噺鍗曚綅"
+                         prop="unit"
+                         width="100" />
+        <el-table-column label="棰嗙敤鏁伴噺"
+                         prop="pickQuantity"
+                         min-width="110" />
+        <el-table-column label="琛ユ枡鏁伴噺"
+                         min-width="120">
           <template #default="{ row }">
-            <el-button type="primary" link @click="handleViewSupplementRecord(row)">
-              {{ row.supplementQty ?? 0 }}
+            <el-button type="primary"
+                       link
+                       @click="handleViewSupplementRecord(row)">
+              {{ row.feedingQty ?? 0 }}
             </el-button>
           </template>
         </el-table-column>
-        <el-table-column label="閫�鏂欐暟閲�" prop="returnQty" min-width="110" />
-        <el-table-column label="瀹為檯鏁伴噺" prop="actualQty" min-width="110" />
+        <el-table-column label="閫�鏂欐暟閲�"
+                         min-width="110">
+          <template #default="{ row }">
+            {{ row.returnQty ?? 0 }}
+          </template>
+        </el-table-column>
+        <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"
+                             placeholder="杈撳叆瀹為檯鏁伴噺"
+                             style="width: 100%;"
+                             :disabled="row.returned || orderRow?.end"
+                             @change="val => handleActualQtyChange(row, val)" />
+          </template>
+        </el-table-column>
       </el-table>
       <template #footer>
         <span class="dialog-footer">
-          <el-button
-            type="warning"
-            :loading="materialReturnConfirming"
-            :disabled="!canOpenReturnSummary"
-            @click="openReturnSummaryDialog"
-          >
+          <el-button v-if="!orderRow?.end"
+                     type="warning"
+                     :loading="materialReturnConfirming"
+                     :disabled="!canOpenReturnSummary"
+                     @click="openReturnSummaryDialog">
             閫�鏂欑‘璁�
           </el-button>
           <el-button @click="dialogVisible = false">鍙栨秷</el-button>
         </span>
       </template>
     </el-dialog>
-
-    <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-dialog v-model="supplementRecordDialogVisible"
+               title="琛ユ枡璁板綍"
+               width="800px">
+      <el-table v-loading="supplementRecordLoading"
+                :data="supplementRecordTableData"
+                border
+                row-key="id">
+        <el-table-column label="琛ユ枡鏁伴噺"
+                         prop="pickQuantity"
+                         min-width="120" />
+        <el-table-column label="琛ユ枡浜�"
+                         prop="supplementUserName"
+                         min-width="120" />
+        <el-table-column label="琛ユ枡鏃ユ湡"
+                         prop="supplementTime"
+                         min-width="160" />
+        <el-table-column label="琛ユ枡鍘熷洜"
+                         prop="feedingReason"
+                         min-width="200" />
       </el-table>
       <template #footer>
         <span class="dialog-footer">
@@ -45,41 +99,30 @@
         </span>
       </template>
     </el-dialog>
-
-    <el-dialog v-model="returnSummaryDialogVisible" title="閫�鏂欐眹鎬荤‘璁�" width="900px">
-      <el-table :data="returnSummaryList" border row-key="summaryKey">
-        <el-table-column label="鍘熸枡鍚嶇О" prop="materialName" min-width="180" />
-        <el-table-column label="鍘熸枡鍨嬪彿" prop="materialModel" min-width="180" />
-        <el-table-column label="璁¢噺鍗曚綅" prop="unit" min-width="100" />
-        <el-table-column label="閫�鏂欐眹鎬绘暟閲�" prop="returnQtyTotal" min-width="140" />
+    <el-dialog v-model="returnSummaryDialogVisible"
+               title="閫�鏂欐眹鎬荤‘璁�"
+               width="900px">
+      <el-table :data="returnSummaryList"
+                border
+                row-key="summaryKey">
+        <el-table-column label="鍘熸枡鍚嶇О"
+                         prop="materialName"
+                         min-width="180" />
+        <el-table-column label="鍘熸枡鍨嬪彿"
+                         prop="materialModel"
+                         min-width="180" />
+        <el-table-column label="璁¢噺鍗曚綅"
+                         prop="unit"
+                         min-width="100" />
+        <el-table-column label="閫�鏂欐眹鎬绘暟閲�"
+                         prop="returnQtyTotal"
+                         min-width="140" />
       </el-table>
-
-      <el-card class="approver-card" shadow="never">
-        <template #header>
-          <div class="card-header-wrapper">
-            <span class="card-title">瀹℃壒浜洪�夋嫨</span>
-            <el-button type="primary" size="small" @click="addApproverNode">鏂板鑺傜偣</el-button>
-          </div>
-        </template>
-        <div class="approver-nodes-container">
-          <div v-for="(node, index) in approverNodes" :key="node.id" class="approver-node-item">
-            <div class="approver-node-label">
-              <span class="node-step">{{ index + 1 }}</span>
-              <span class="node-text">瀹℃壒浜�</span>
-            </div>
-            <el-select v-model="node.userId" placeholder="閫夋嫨浜哄憳" class="approver-select" clearable>
-              <el-option v-for="user in userList" :key="user.userId" :label="user.nickName" :value="user.userId" />
-            </el-select>
-            <el-button v-if="approverNodes.length > 1" type="danger" size="small" @click="removeApproverNode(index)">
-              鍒犻櫎
-            </el-button>
-          </div>
-        </div>
-      </el-card>
-
       <template #footer>
         <span class="dialog-footer">
-          <el-button type="primary" :loading="materialReturnConfirming" @click="handleReturnConfirm">纭鎻愪氦</el-button>
+          <el-button type="primary"
+                     :loading="materialReturnConfirming"
+                     @click="handleReturnConfirm">纭鎻愪氦</el-button>
           <el-button @click="returnSummaryDialogVisible = false">鍙栨秷</el-button>
         </span>
       </template>
@@ -88,177 +131,154 @@
 </template>
 
 <script setup>
-import { computed, ref, watch } from "vue";
-import { ElMessage } from "element-plus";
-import { listMaterialPickingDetail, listMaterialSupplementRecord, confirmMaterialReturn } from "@/api/productionManagement/productionOrder.js";
-import { userListNoPageByTenantId } from "@/api/system/user.js";
+  import { computed, ref, watch } from "vue";
+  import { ElMessage } from "element-plus";
+  import {
+    listMaterialPickingDetail,
+    listMaterialSupplementRecord,
+    updateMaterialPickingLedger,
+  } from "@/api/productionManagement/productionOrder.js";
 
-const props = defineProps({
-  modelValue: { type: Boolean, default: false },
-  orderRow: { type: Object, default: null },
-});
-const emit = defineEmits(["update:modelValue", "confirmed"]);
-
-const dialogVisible = computed({
-  get: () => props.modelValue,
-  set: val => emit("update:modelValue", val),
-});
-
-const materialDetailLoading = ref(false);
-const materialDetailTableData = ref([]);
-const materialReturnConfirming = ref(false);
-const supplementRecordDialogVisible = ref(false);
-const supplementRecordLoading = ref(false);
-const supplementRecordTableData = ref([]);
-const returnSummaryDialogVisible = ref(false);
-const returnSummaryList = ref([]);
-const userList = ref([]);
-const approverNodes = ref([{ id: Date.now(), userId: undefined }]);
-const canOpenReturnSummary = computed(() =>
-  materialDetailTableData.value.some(item => Number(item.returnQty || 0) > 0)
-);
-
-const loadDetailList = async () => {
-  if (!props.orderRow?.id) return;
-  materialDetailLoading.value = true;
-  materialDetailTableData.value = [];
-  try {
-    const res = await listMaterialPickingDetail({ orderId: props.orderRow.id });
-    materialDetailTableData.value = res.data || [];
-  } finally {
-    materialDetailLoading.value = false;
-  }
-};
-
-watch(
-  () => dialogVisible.value,
-  visible => {
-    if (visible) {
-      loadDetailList();
-    }
-  }
-);
-
-const handleClose = () => {
-  materialDetailTableData.value = [];
-};
-
-const handleViewSupplementRecord = async row => {
-  if (!row?.id) return;
-  supplementRecordDialogVisible.value = true;
-  supplementRecordLoading.value = true;
-  supplementRecordTableData.value = [];
-  try {
-    const res = await listMaterialSupplementRecord({ materialDetailId: row.id });
-    supplementRecordTableData.value = res.data || [];
-  } finally {
-    supplementRecordLoading.value = false;
-  }
-};
-
-const buildReturnSummary = () => {
-  const map = new Map();
-  materialDetailTableData.value.forEach(item => {
-    const key = `${item.materialModelId || ""}_${item.materialName || ""}_${item.materialModel || ""}_${item.unit || ""}`;
-    const old = map.get(key) || {
-      summaryKey: key,
-      materialName: item.materialName || "",
-      materialModel: item.materialModel || "",
-      unit: item.unit || "",
-      returnQtyTotal: 0,
-    };
-    old.returnQtyTotal += Number(item.returnQty || 0);
-    map.set(key, old);
+  const props = defineProps({
+    modelValue: { type: Boolean, default: false },
+    orderRow: { type: Object, default: null },
   });
-  return Array.from(map.values());
-};
+  const emit = defineEmits(["update:modelValue", "confirmed"]);
 
-const loadUserList = async () => {
-  if (userList.value.length > 0) return;
-  const res = await userListNoPageByTenantId();
-  userList.value = res.data || [];
-};
+  const dialogVisible = computed({
+    get: () => props.modelValue,
+    set: val => emit("update:modelValue", val),
+  });
 
-const openReturnSummaryDialog = async () => {
-  if (!canOpenReturnSummary.value) {
-    ElMessage.warning("閫�鏂欐暟閲忓ぇ浜�0鏃舵墠鑳介��鏂欑‘璁�");
-    return;
-  }
-  returnSummaryList.value = buildReturnSummary();
-  approverNodes.value = [{ id: Date.now(), userId: undefined }];
-  await loadUserList();
-  returnSummaryDialogVisible.value = true;
-};
+  const materialDetailLoading = ref(false);
+  const materialDetailTableData = ref([]);
+  const materialReturnConfirming = ref(false);
+  const supplementRecordDialogVisible = ref(false);
+  const supplementRecordLoading = ref(false);
+  const supplementRecordTableData = ref([]);
+  const returnSummaryDialogVisible = ref(false);
+  const returnSummaryList = ref([]);
+  const calcReturnQty = item =>
+    Number(item.pickQuantity || 0) +
+    Number(item.feedingQty || 0) -
+    Number(item.actualQty || 0);
+  const canOpenReturnSummary = computed(() =>
+    materialDetailTableData.value.some(
+      item => item.returned !== true && calcReturnQty(item) > 0
+    )
+  );
 
-const addApproverNode = () => {
-  approverNodes.value.push({ id: Date.now() + Math.random(), userId: undefined });
-};
+  const loadDetailList = async () => {
+    if (!props.orderRow?.id) return;
+    materialDetailLoading.value = true;
+    materialDetailTableData.value = [];
+    try {
+      const res = await listMaterialPickingDetail(props.orderRow.id);
+      materialDetailTableData.value = (res.data || []).map(item => ({
+        ...item,
+        actualQty:
+          item.actualQty ??
+          Number(item.pickQuantity || 0) + Number(item.feedingQty || 0),
+        returnQty: item.returnQty ?? 0,
+      }));
+    } finally {
+      materialDetailLoading.value = false;
+    }
+  };
 
-const removeApproverNode = index => {
-  approverNodes.value.splice(index, 1);
-};
+  watch(
+    () => dialogVisible.value,
+    visible => {
+      if (visible) {
+        loadDetailList();
+      }
+    }
+  );
 
-const handleReturnConfirm = async () => {
-  if (!props.orderRow?.id) return;
-  const approverList = approverNodes.value
-    .filter(item => item.userId)
-    .map((item, index) => ({ userId: item.userId, sort: index + 1 }));
-  if (approverList.length === 0) {
-    ElMessage.warning("璇疯嚦灏戦�夋嫨涓�浣嶅鎵逛汉");
-    return;
-  }
-  materialReturnConfirming.value = true;
-  try {
-    await confirmMaterialReturn({
-      orderId: props.orderRow.id,
-      returnSummaryList: returnSummaryList.value,
-      approverList,
+  const handleClose = () => {
+    materialDetailTableData.value = [];
+  };
+
+  const handleActualQtyChange = (row, val) => {
+    row.returnQty = calcReturnQty(row);
+  };
+
+  const handleViewSupplementRecord = async row => {
+    if (!row?.id) return;
+    supplementRecordDialogVisible.value = true;
+    supplementRecordLoading.value = true;
+    supplementRecordTableData.value = [];
+    try {
+      const res = await listMaterialSupplementRecord({
+        pickId: row.id,
+        productionOrderId: props.orderRow.id,
+      });
+      supplementRecordTableData.value = res.data || [];
+    } finally {
+      supplementRecordLoading.value = false;
+    }
+  };
+
+  const buildReturnSummary = () => {
+    const map = new Map();
+    materialDetailTableData.value.forEach(item => {
+      const returnQty = calcReturnQty(item);
+      if (returnQty <= 0) return;
+      const key = `${item.productModelId || ""}_${item.productName || ""}_${
+        item.model || ""
+      }_${item.unit || ""}`;
+      const old = map.get(key) || {
+        summaryKey: key,
+        materialName: item.productName || "",
+        materialModel: item.model || "",
+        unit: item.unit || "",
+        returnQtyTotal: 0,
+      };
+      old.returnQtyTotal += returnQty;
+      map.set(key, old);
     });
-    returnSummaryDialogVisible.value = false;
-    dialogVisible.value = false;
-    emit("confirmed");
-  } finally {
-    materialReturnConfirming.value = false;
-  }
-};
+    return Array.from(map.values());
+  };
+
+  const openReturnSummaryDialog = async () => {
+    if (!canOpenReturnSummary.value) {
+      ElMessage.warning("閫�鏂欐暟閲�=棰嗙敤鏁伴噺+琛ユ枡鏁伴噺-瀹為檯鏁伴噺锛屼笖闇�澶т簬0");
+      return;
+    }
+    returnSummaryList.value = buildReturnSummary();
+    returnSummaryDialogVisible.value = true;
+  };
+
+  const handleReturnConfirm = async () => {
+    if (!props.orderRow?.id) return;
+    materialReturnConfirming.value = true;
+    try {
+      await updateMaterialPickingLedger({
+        productionOrderId: props.orderRow.id,
+        productionOrderPickDto: materialDetailTableData.value.map(item => ({
+          id: item.id,
+          technologyOperationId: item.technologyOperationId,
+          operationName: item.operationName,
+          bom: item.bom === true,
+          productModelId: item.productModelId,
+          demandedQuantity: item.demandedQuantity,
+          unit: item.unit,
+          pickQuantity: item.pickQuantity,
+          batchNo: item.batchNo,
+          feedingQty: item.feedingQty,
+          returnQty: item.returnQty,
+          actualQty: item.actualQty,
+          feedingReason: item.feedingReason,
+          returned: true,
+        })),
+      });
+      returnSummaryDialogVisible.value = false;
+      dialogVisible.value = false;
+      emit("confirmed");
+    } finally {
+      materialReturnConfirming.value = false;
+    }
+  };
 </script>
 
-<style scoped lang="scss">
-.approver-card {
-  margin-top: 12px;
-}
-.card-header-wrapper {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-}
-.approver-nodes-container {
-  display: flex;
-  flex-direction: column;
-  gap: 8px;
-}
-.approver-node-item {
-  display: flex;
-  gap: 8px;
-  align-items: center;
-}
-.approver-node-label {
-  display: flex;
-  gap: 4px;
-  min-width: 88px;
-  align-items: center;
-}
-.node-step {
-  width: 20px;
-  height: 20px;
-  line-height: 20px;
-  text-align: center;
-  border-radius: 50%;
-  background: #409eff;
-  color: #fff;
-  font-size: 12px;
-}
-.approver-select {
-  flex: 1;
-}
-</style>
+<style scoped lang="scss"></style>

--
Gitblit v1.9.3