From e7303d861603e68146a45fff68c1afcc25fac10e Mon Sep 17 00:00:00 2001
From: gongchunyi <deslre0381@gmail.com>
Date: 星期三, 02 九月 2026 00:45:34 +0800
Subject: [PATCH] fix: 生成订单领料可重复领料
---
src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue | 346 ++++++++++++++++++++++++++++++++-------------------------
1 files changed, 196 insertions(+), 150 deletions(-)
diff --git a/src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue b/src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue
index 6c175f9..739facb 100644
--- a/src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue
+++ b/src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue
@@ -2,7 +2,7 @@
<div>
<el-dialog v-model="dialogVisible"
title="棰嗘枡鍙拌处"
- width="1350px"
+ width="1450px"
@close="handleClose">
<div class="material-toolbar">
<el-button type="primary"
@@ -13,7 +13,7 @@
border
row-key="tempId">
<el-table-column label="宸ュ簭鍚嶇О"
- min-width="80">
+ min-width="100">
<template #default="{ row }">
<span v-if="row.bom === true">{{ row.operationName || "-" }}</span>
<el-select v-else
@@ -43,13 +43,13 @@
</template>
</el-table-column>
<el-table-column label="鍘熸枡鍨嬪彿"
- min-width="120">
+ min-width="110">
<template #default="{ row }">
{{ row.materialModel || "-" }}
</template>
</el-table-column>
<!-- 鎵瑰彿澶氶�� -->
- <el-table-column min-width="200"
+ <el-table-column min-width="180"
label="鎵瑰彿">
<template #default="{ row }">
<el-select v-model="row.batchNo"
@@ -66,7 +66,7 @@
</template>
</el-table-column>
<el-table-column label="闇�姹傛暟閲�"
- min-width="100">
+ min-width="95">
<template #default="{ row }">
<span v-if="row.bom === true">{{ stripTrailingZeros(row.demandedQuantity) ?? "-" }}</span>
<el-input-number v-else
@@ -78,6 +78,20 @@
:parser="value => parseFloat(value) || 0"
style="width: 100%;"
@change="val => handleRequiredQtyChange(row, val)" />
+ </template>
+ </el-table-column>
+ <el-table-column label="宸查鏁伴噺"
+ min-width="90">
+ <template #default="{ row }">
+ {{ stripTrailingZeros(row.pickedQty) ?? 0 }}
+ </template>
+ </el-table-column>
+ <el-table-column label="鍓╀綑鍙"
+ min-width="90">
+ <template #default="{ row }">
+ <span :class="{ 'remaining-empty': Number(row.remainingQty) <= 0 }">
+ {{ stripTrailingZeros(row.remainingQty) ?? 0 }}
+ </span>
</template>
</el-table-column>
<el-table-column label="璁¢噺鍗曚綅"
@@ -102,7 +116,7 @@
width="90"
fixed="right">
<template #default="{ $index, row }">
- <el-button v-if="row.bom !== true"
+ <el-button v-if="row.bom !== true || Number(row.pickQty) > 0"
type="danger"
link
@click="handleDeleteMaterialRow($index)">鍒犻櫎</el-button>
@@ -130,18 +144,12 @@
import { computed, ref, watch } from "vue";
import { ElMessage } from "element-plus";
import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
- import {
- findProductProcessRouteItemList,
- listMain,
- } from "@/api/productionManagement/productProcessRoute.js";
+ import { findProductProcessRouteItemList } from "@/api/productionManagement/productProcessRoute.js";
import {
listMaterialPickingDetail,
listMaterialPickingBom,
- listMaterialPickingLedger,
saveMaterialPickingLedger,
- updateMaterialPickingLedger,
} from "@/api/productionManagement/productionOrder.js";
- import { queryList2 } from "@/api/productionManagement/productStructure.js";
const props = defineProps({
modelValue: { type: Boolean, default: false },
@@ -160,62 +168,61 @@
const materialTableData = ref([]);
const isSaveDisabled = computed(() => {
+ if (materialTableLoading.value) return true;
if (materialTableData.value.length === 0) return true;
- return !materialTableData.value.some(row => {
- // 妫�鏌ユ槸鍚︽湁浠讳綍鐢ㄦ埛杈撳叆鍐呭
- const hasBatch = Array.isArray(row.batchNo) && row.batchNo.length > 0;
- const hasPickQty =
- row.pickQty !== null && row.pickQty !== undefined && row.pickQty !== 0;
-
- if (row.bom) {
- // 瀵逛簬鏉ヨ嚜BOM鐨勮锛岃緭鍏ユ鍙湁鈥滄壒鍙封�濆拰鈥滈鐢ㄦ暟閲忊��
- return hasBatch || hasPickQty;
- } else {
- // 瀵逛簬鏂板琛岋紝杈撳叆妗嗗寘鎷�滃伐搴忊�濄�佲�滃師鏂欌�濄�佲�滈渶姹傛暟閲忊�濄�佲�滄壒鍙封�濆拰鈥滈鐢ㄦ暟閲忊��
- const hasOperation = !!row.operationName;
- const hasMaterial = !!row.materialName;
- const hasDemanded =
- row.demandedQuantity !== null &&
- row.demandedQuantity !== undefined &&
- row.demandedQuantity !== 0;
- return (
- hasBatch || hasPickQty || hasOperation || hasMaterial || hasDemanded
- );
- }
- });
+ // 鍙湁瀛樺湪鏈棰嗙敤鏁伴噺澶т簬0鐨勮鎵嶅厑璁镐繚瀛�
+ return !materialTableData.value.some(item => Number(item.pickQty) > 0);
});
const processOptions = ref([]);
const currentMaterialSelectRowIndex = ref(-1);
let materialTempId = 0;
- const getDefaultBatchNo = batchNoList => {
- if (!Array.isArray(batchNoList) || batchNoList.length === 0) return [];
- return [batchNoList[0]];
+ const toNumber = val => {
+ const n = Number(val);
+ return Number.isFinite(n) ? n : 0;
+ };
+
+ // 淇濈暀6浣嶅皬鏁帮紝閬垮厤娴偣璇樊
+ const roundQuantity = val => Math.round(toNumber(val) * 1e6) / 1e6;
+
+ // 闇�姹傛暟閲忓垎缁勯敭锛氫笌鍚庣淇濇寔涓�鑷达紙宸ュ簭ID浼樺厛锛岀己澶辨椂鐢ㄥ伐搴忓悕绉帮級
+ const buildDemandKey = (technologyOperationId, operationName, productModelId) =>
+ `${technologyOperationId != null && technologyOperationId !== undefined ? "T" + technologyOperationId : "N" + (operationName || "")}#${productModelId ?? ""}`;
+
+ const stripTrailingZeros = val => {
+ if (val === null || val === undefined || val === "") return "0";
+ const str = String(val);
+ if (str.includes(".")) {
+ return parseFloat(str).toString();
+ }
+ return str;
};
const createMaterialRow = (row = {}) => {
const batchNoList = Array.isArray(row.batchNoList) ? row.batchNoList : [];
- const batchNo = row.batchNo
- ? typeof row.batchNo === "string"
- ? row.batchNo.split(",")
- : row.batchNo
- : getDefaultBatchNo(batchNoList);
+ const pickedEntry = row.pickedEntry || null;
+ const pickedQty = pickedEntry ? roundQuantity(pickedEntry.pickedQty) : 0;
+ const returnedQty = pickedEntry ? roundQuantity(pickedEntry.returnedQty) : 0;
+ const demandedQuantity = roundQuantity(row.demandedQuantity ?? 0);
+ const remainingQty = Math.max(roundQuantity(demandedQuantity - pickedQty + returnedQty), 0);
return {
- tempId: row.id || `temp_${++materialTempId}`,
- id: row.id,
- processId: row.processId || row.technologyOperationId,
- technologyOperationId: row.technologyOperationId || row.processId,
- operationName: row.operationName || "",
+ tempId: `temp_${++materialTempId}`,
+ id: null,
bom: row.bom === true,
- materialModelId: row.materialModelId || row.productModelId,
- materialName: row.materialName || row.productName || "",
- materialModel: row.materialModel || row.model || "",
- demandedQuantity: Number(row.requiredQty ?? row.demandedQuantity ?? 0),
+ technologyOperationId: row.technologyOperationId ?? null,
+ operationName: row.operationName || "",
+ materialModelId: row.materialModelId ?? null,
+ materialName: row.materialName || "",
+ materialModel: row.materialModel || "",
unit: row.unit || "",
- pickQty: Number(row.pickQty ?? row.pickQuantity ?? row.requiredQty ?? row.demandedQuantity ?? 0),
- batchNo,
+ demandedQuantity,
+ pickedQty,
+ returnedQty,
+ remainingQty,
+ pickQty: 0,
+ batchNo: [],
batchNoList,
};
};
@@ -237,37 +244,112 @@
if (!processMap.has(key)) {
processMap.set(key, {
id: processId,
+ technologyOperationId: processId,
name: operationName,
});
}
});
processOptions.value = Array.from(processMap.values());
};
- const isDetail = ref(true);
const loadMaterialData = async () => {
if (!props.orderRow?.id) return;
materialTableLoading.value = true;
materialTableData.value = [];
- await getProcessOptions();
try {
- const detailRes = await listMaterialPickingDetail(props.orderRow.id);
+ await getProcessOptions();
+ // 鍚屾椂鑾峰彇BOM闇�姹傛暟鎹笌宸查鏂欐槑缁嗭紝鐢ㄤ簬璁$畻鈥滃凡棰嗘暟閲�/鍓╀綑鍙鈥�
+ const [bomRes, detailRes] = await Promise.all([
+ listMaterialPickingBom(props.orderRow.id),
+ listMaterialPickingDetail(props.orderRow.id),
+ ]);
+ const bomList = Array.isArray(bomRes?.data)
+ ? bomRes.data
+ : bomRes?.data?.records || [];
const detailList = Array.isArray(detailRes?.data)
? detailRes.data
: detailRes?.data?.records || [];
- if (detailList.length > 0) {
- isDetail.value = true;
- materialTableData.value = detailList.map(item => createMaterialRow(item));
- return;
- } else {
- isDetail.value = false;
- const bomRes = await listMaterialPickingBom(props.orderRow.id);
- const bomList = Array.isArray(bomRes?.data)
- ? bomRes.data
- : bomRes?.data?.records || [];
- materialTableData.value = bomList.map(item => createMaterialRow(item));
- return;
- }
+
+ // 鎸夆�滃伐搴�+鍘熸枡鈥濇眹鎬诲巻鍙查鏂欙細宸查鏁伴噺銆侀��鏂欐暟閲�
+ const pickedMap = new Map();
+ detailList.forEach(item => {
+ if (!item) return;
+ const key = buildDemandKey(
+ item.technologyOperationId,
+ item.operationName,
+ item.productModelId
+ );
+ const entry = pickedMap.get(key) || {
+ pickedQty: 0,
+ returnedQty: 0,
+ manualDemand: 0,
+ technologyOperationId: item.technologyOperationId,
+ operationName: item.operationName || "",
+ materialModelId: item.productModelId,
+ materialName: item.productName || "",
+ materialModel: item.model || "",
+ unit: item.unit || "",
+ batchNoList: Array.isArray(item.batchNoList)
+ ? item.batchNoList
+ : [],
+ };
+ entry.pickedQty = roundQuantity(entry.pickedQty + toNumber(item.pickQuantity));
+ entry.returnedQty = roundQuantity(entry.returnedQty + toNumber(item.returnQty));
+ // 鎵嬪伐鏂板琛岋紙闈濨OM锛夌殑闇�姹傛暟閲忎互棰嗘枡蹇収涓哄噯
+ if (item.bom !== true && item.demandedQuantity != null) {
+ entry.manualDemand = Math.max(
+ toNumber(entry.manualDemand),
+ toNumber(item.demandedQuantity)
+ );
+ }
+ pickedMap.set(key, entry);
+ });
+
+ const rows = [];
+ const bomKeySet = new Set();
+ // BOM琛岋細闇�姹傛暟閲忓彇BOM锛屽凡棰嗘暟閲忓彇鍘嗗彶棰嗘枡姹囨��
+ bomList.forEach(item => {
+ const materialModelId = item.productModelId ?? item.materialModelId;
+ const key = buildDemandKey(
+ item.technologyOperationId,
+ item.operationName,
+ materialModelId
+ );
+ bomKeySet.add(key);
+ rows.push(
+ createMaterialRow({
+ bom: true,
+ technologyOperationId: item.technologyOperationId,
+ operationName: item.operationName || "",
+ materialModelId,
+ materialName: item.productName || item.materialName || "",
+ materialModel: item.model || item.materialModel || "",
+ unit: item.unit || "",
+ demandedQuantity: item.demandedQuantity ?? item.requiredQty ?? 0,
+ pickedEntry: pickedMap.get(key),
+ batchNoList: item.batchNoList || [],
+ })
+ );
+ });
+ // 涓嶅湪BOM涓殑鍘嗗彶棰嗘枡锛堟墜宸ユ柊澧炶锛夛紝涔熷睍绀哄嚭鏉ユ敮鎸佺户缁鏂�
+ pickedMap.forEach((entry, key) => {
+ if (bomKeySet.has(key)) return;
+ rows.push(
+ createMaterialRow({
+ bom: false,
+ technologyOperationId: entry.technologyOperationId,
+ operationName: entry.operationName,
+ materialModelId: entry.materialModelId,
+ materialName: entry.materialName,
+ materialModel: entry.materialModel,
+ unit: entry.unit,
+ demandedQuantity: entry.manualDemand,
+ pickedEntry: entry,
+ batchNoList: entry.batchNoList,
+ })
+ );
+ });
+ materialTableData.value = rows;
} finally {
materialTableLoading.value = false;
}
@@ -281,14 +363,6 @@
}
}
);
-
- const stripTrailingZeros = val => {
- const str = String(val);
- if (str.includes(".")) {
- return parseFloat(str).toString();
- }
- return str;
- };
const handleClose = () => {
materialTableData.value = [];
@@ -311,11 +385,12 @@
};
const handleRequiredQtyChange = (row, val) => {
- const required = Number(val ?? 0);
+ const required = roundQuantity(val);
row.demandedQuantity = required;
- if (!row.pickQty || Number(row.pickQty) === 0) {
- row.pickQty = required;
- }
+ row.remainingQty = Math.max(
+ roundQuantity(required - toNumber(row.pickedQty) + toNumber(row.returnedQty)),
+ 0
+ );
};
const openMaterialProductSelect = row => {
@@ -326,8 +401,6 @@
};
const handleMaterialProductConfirm = products => {
- console.log(products, "products");
-
if (!products || products.length === 0) return;
const index = currentMaterialSelectRowIndex.value;
if (index < 0 || !materialTableData.value[index]) return;
@@ -340,7 +413,7 @@
row.materialModel = product.materialModel || product.model || "";
row.unit = product.unit || product.measureUnit || "";
row.batchNoList = Array.isArray(product.batchNoList) ? product.batchNoList : [];
- row.batchNo = getDefaultBatchNo(row.batchNoList);
+ row.batchNo = [];
currentMaterialSelectRowIndex.value = -1;
materialProductDialogVisible.value = false;
};
@@ -349,33 +422,22 @@
if (materialTableData.value.length === 0) {
return { valid: false, message: "璇峰厛鏂板棰嗘枡鏁版嵁" };
}
- const invalidNewRow = materialTableData.value.find(
- item => item.bom !== true && (!item.operationName || !item.materialName)
+ const pickRows = materialTableData.value.filter(
+ item => toNumber(item.pickQty) > 0
);
- if (invalidNewRow) {
- return { valid: false, message: "鏂板琛岀殑宸ュ簭鍚嶇О鍜屽師鏂欏悕绉颁负蹇呭~椤�" };
+ if (pickRows.length === 0) {
+ return { valid: false, message: "璇疯嚦灏戝~鍐欎竴琛屽ぇ浜�0鐨勯鐢ㄦ暟閲�" };
}
- const invalidRow = materialTableData.value.find(
- item =>
- !item.operationName ||
- !item.materialName ||
- (Number(item.pickQty) > 0 &&
- (!item.batchNo || item.batchNo.length === 0)) ||
- (item.batchNo && item.batchNo.length > 0 && Number(item.pickQty) <= 0) ||
- item.demandedQuantity === null ||
- item.demandedQuantity === undefined ||
- item.pickQty === null ||
- item.pickQty === undefined
- );
- if (invalidRow) {
- if (
- invalidRow.batchNo &&
- invalidRow.batchNo.length > 0 &&
- Number(invalidRow.pickQty) <= 0
- ) {
- return { valid: false, message: "閫夋嫨浜嗘壒鍙锋椂锛岄鐢ㄦ暟閲忓繀椤诲ぇ浜庨浂" };
+ for (let i = 0; i < materialTableData.value.length; i++) {
+ const item = materialTableData.value[i];
+ const rowNo = i + 1;
+ if (toNumber(item.pickQty) <= 0) continue;
+ if (item.bom !== true && (!item.operationName || !item.materialName)) {
+ return { valid: false, message: `绗�${rowNo}琛岀殑宸ュ簭鍚嶇О鍜屽師鏂欏悕绉颁负蹇呭~椤筦 };
}
- return { valid: false, message: "璇峰畬鍠勫伐搴忋�佸師鏂欍�佹壒鍙峰拰鏁伴噺鍚庡啀淇濆瓨" };
+ if (!Array.isArray(item.batchNo) || item.batchNo.length === 0) {
+ return { valid: false, message: `绗�${rowNo}琛岃閫夋嫨鎵瑰彿` };
+ }
}
return { valid: true, message: "" };
};
@@ -389,47 +451,26 @@
}
materialSaving.value = true;
try {
- if (isDetail.value) {
- await updateMaterialPickingLedger({
- productionOrderId: props.orderRow.id,
- productionOrderPickDto: materialTableData.value.map(item => ({
- id: item.id,
- // processId: item.operationName,
- technologyOperationId: item.technologyOperationId,
- operationName: item.operationName,
- bom: item.bom === true,
- productModelId: item.materialModelId,
- // materialName: item.materialName,
- // materialModel: item.materialModel,
- demandedQuantity: item.demandedQuantity,
- unit: item.unit,
- pickQuantity: item.pickQty,
- batchNo: Array.isArray(item.batchNo)
- ? item.batchNo.join(",")
- : item.batchNo,
- })),
- });
- } else {
- await saveMaterialPickingLedger({
- productionOrderId: props.orderRow.id,
- productionOrderPickDto: materialTableData.value.map(item => ({
- id: item.id,
- // processId: item.operationName,
- technologyOperationId: item.technologyOperationId,
- operationName: item.operationName,
- bom: item.bom === true,
- productModelId: item.materialModelId,
- // materialName: item.materialName,
- // materialModel: item.materialModel,
- demandedQuantity: item.demandedQuantity,
- unit: item.unit,
- pickQuantity: item.pickQty,
- batchNo: Array.isArray(item.batchNo)
- ? item.batchNo.join(",")
- : item.batchNo,
- })),
- });
- }
+ // 姣忔淇濆瓨閮芥槸涓�娆$嫭绔嬬殑棰嗘枡锛氬彧鎻愪氦鏈棰嗙敤鏁伴噺澶т簬0鐨勮锛�
+ // 鍑哄簱鍙拌处鎸夋湰娆¢鏂欐暟閲忚璐︼紝鍘嗗彶棰嗘枡璁板綍淇濇寔涓嶅彉銆�
+ const pickRows = materialTableData.value.filter(
+ item => toNumber(item.pickQty) > 0
+ );
+ await saveMaterialPickingLedger({
+ productionOrderId: props.orderRow.id,
+ productionOrderPickDto: pickRows.map(item => ({
+ technologyOperationId: item.technologyOperationId,
+ operationName: item.operationName,
+ bom: item.bom === true,
+ productModelId: item.materialModelId,
+ demandedQuantity: item.demandedQuantity,
+ unit: item.unit,
+ pickQuantity: toNumber(item.pickQty),
+ batchNo: Array.isArray(item.batchNo)
+ ? item.batchNo.join(",")
+ : item.batchNo,
+ })),
+ });
ElMessage({ message: "棰嗘枡鎴愬姛", type: "success" });
emit("saved");
@@ -445,4 +486,9 @@
margin-bottom: 12px;
text-align: right;
}
+
+ .remaining-empty {
+ color: #f56c6c;
+ font-weight: 600;
+ }
</style>
--
Gitblit v1.9.3