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/processRoute/ItemsForm.vue | 112 +-
src/views/productionManagement/productStructure/Detail/index.vue | 514 +++------------
src/views/productionManagement/productionOrder/components/MaterialDetailDialog.vue | 235 +------
src/views/productionManagement/workOrderManagement/components/MaterialDialog.vue | 478 ++++++++------
src/api/productionManagement/processRouteItem.js | 9
src/api/productionManagement/workOrder.js | 55 -
src/views/productionManagement/processRoute/processRouteItem/index.vue | 405 +----------
src/views/productionManagement/productionOrder/index.vue | 96 --
src/api/productionManagement/productionOrder.js | 6
src/views/productionManagement/workOrderManagement/index.vue | 47
10 files changed, 571 insertions(+), 1,386 deletions(-)
diff --git a/src/api/productionManagement/processRouteItem.js b/src/api/productionManagement/processRouteItem.js
index 3bf4a6e..1488510 100644
--- a/src/api/productionManagement/processRouteItem.js
+++ b/src/api/productionManagement/processRouteItem.js
@@ -17,6 +17,15 @@
data: data,
});
}
+
+// 鍏ㄩ噺淇濆瓨宸ヨ壓璺嚎宸ュ簭锛堟寜鎻愪氦椤哄簭纭畾宸ュ簭椤哄簭锛�
+export function saveProcessRouteItems(data) {
+ return request({
+ url: "/technologyRoutingOperation/saveRouteOperations",
+ method: "post",
+ data: data,
+ });
+}
export function addOrUpdateProcessRouteItem1(data) {
return request({
url: "/technologyRoutingOperation",
diff --git a/src/api/productionManagement/productionOrder.js b/src/api/productionManagement/productionOrder.js
index b84a188..dba11c2 100644
--- a/src/api/productionManagement/productionOrder.js
+++ b/src/api/productionManagement/productionOrder.js
@@ -62,12 +62,12 @@
});
}
-// 鐢熶骇璁㈠崟-鏌ヨ浜у搧缁撴瀯鍒楄〃
+// 鐢熶骇璁㈠崟-鏌ヨ璁㈠崟BOM蹇収鐨勬墎骞崇墿鏂欐槑缁嗭紙鐗╂枡瑙勬牸 + 娑堣�楀伐搴忥級
export function listProcessBom(query) {
+ const orderId = typeof query === "object" && query !== null ? query.orderId : query;
return request({
- url: "/productOrder/listProcessBom",
+ url: "/productionBomStructure/listByOrderId/" + orderId,
method: "get",
- params: query,
});
}
diff --git a/src/api/productionManagement/workOrder.js b/src/api/productionManagement/workOrder.js
index b3050c9..248b8d3 100644
--- a/src/api/productionManagement/workOrder.js
+++ b/src/api/productionManagement/workOrder.js
@@ -42,51 +42,40 @@
});
}
-// 宸ュ崟-褰撳墠宸ュ簭鐗╂枡鍙拌处
-export function listWorkOrderMaterialLedger(query) {
+// 宸ュ崟-褰撳墠宸ュ簭鍙鐢ㄧ墿鏂� + 宸查鏂欒褰�
+export function listWorkOrderMaterialLedger(productionOperationTaskId) {
return request({
- url: "/productOrderMaterial/reportMaterials",
+ url: "/productionOrderPick/taskMaterials/" + productionOperationTaskId,
method: "get",
- params: query,
});
}
-// 宸ュ崟-琛ユ枡
-export function addWorkOrderMaterialSupplement(data) {
- return request({
- url: "/productionOperationTask/material/supplement",
- method: "post",
- data,
- });
-}
-
-// 宸ュ崟-閫�鏂�
-export function addWorkOrderMaterialReturn(data) {
- return request({
- url: "/productionOperationTask/material/return",
- method: "post",
- data,
- });
-}
-
-// 宸ュ崟-琛ユ枡璁板綍
-export function listWorkOrderMaterialSupplementRecord(query) {
- return request({
- url: "/productionOperationTask/material/supplementRecord",
- method: "get",
- params: query,
- });
-}
-
-// 宸ュ崟-棰嗙敤锛堟彁浜ゅ疄闄呴鐢ㄦ暟閲忥級
+// 宸ュ崟-宸ュ簭缁熶竴棰嗘枡锛堢珛鍗虫墸鍑忓簱瀛橈紝鍙噸澶嶉鏂欙級
export function pickWorkOrderMaterial(data) {
return request({
- url: "/productionOperationTask/material/pick",
+ url: "/productionOrderPick/savePickByTask",
method: "post",
data,
});
}
+// 宸ュ崟-閫�鍥炲綋鍓嶅伐搴忓凡棰嗙墿鏂�
+export function returnWorkOrderMaterial(data) {
+ return request({
+ url: "/productionOrderPick/updatePick",
+ method: "post",
+ data,
+ });
+}
+
+// 宸ュ崟-褰撳墠宸ュ簭鏄惁宸叉湁鏈夋晥棰嗘枡锛堟姤宸ュ墠缃牎楠岋紝浠呯敤浜庡墠绔彁绀猴級
+export function checkWorkOrderMaterialPicked(productionOperationTaskId) {
+ return request({
+ url: "/productionOrderPick/taskPicked/" + productionOperationTaskId,
+ method: "get",
+ });
+}
+
// 鑾峰彇宸ュ簭缁熻鏁版嵁
export function getOperationStatistics(query) {
return request({
diff --git a/src/views/productionManagement/processRoute/ItemsForm.vue b/src/views/productionManagement/processRoute/ItemsForm.vue
index ed6e499..4d3229f 100644
--- a/src/views/productionManagement/processRoute/ItemsForm.vue
+++ b/src/views/productionManagement/processRoute/ItemsForm.vue
@@ -7,15 +7,7 @@
@close="closeModal"
>
<div class="operate-button">
- <el-button
- type="primary"
- @click="isShowProductSelectDialog = true"
- class="mb5"
- style="margin-bottom: 10px;"
- >
- 閫夋嫨浜у搧
- </el-button>
-
+ <span class="route-tip">鍚庣鏍规嵁BOM鐢熸垚宸ヨ壓璺嚎锛屽彲鍦ㄦ璋冩暣宸ュ簭鍜岄『搴�</span>
<el-switch
v-model="isTable"
inline-prompt
@@ -68,6 +60,7 @@
<el-select
v-model="scope.row[item.prop]"
style="width: 100%;"
+ @change="value => handleProcessChange(scope.row, value)"
@mousedown.stop
>
<el-option
@@ -102,16 +95,17 @@
<div class="step-content">
<div class="step-number">{{ index + 1 }}</div>
<el-card
- :header="item.productName"
+ :header="getProcessName(item)"
class="step-card"
style="cursor: move;"
>
<div class="step-card-content">
- <p>{{ item.model }}</p>
- <p>{{ item.unit }}</p>
+ <p>{{ item.productName || '-' }}</p>
+ <p>{{ item.model || '-' }}</p>
<el-select
v-model="item.processId"
style="width: 100%;"
+ @change="value => handleProcessChange(item, value)"
@mousedown.stop
>
<el-option
@@ -139,18 +133,12 @@
</div>
</template>
</el-dialog>
-
- <ProductSelectDialog
- v-model="isShowProductSelectDialog"
- @confirm="handelSelectProducts"
- />
</div>
</template>
<script setup>
import { ref, computed, getCurrentInstance, onMounted, onUnmounted, nextTick } from "vue";
-import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
-import { findProcessRouteItemList, addOrUpdateProcessRouteItem } from "@/api/productionManagement/processRouteItem.js";
+import { findProcessRouteItemList, saveProcessRouteItems } from "@/api/productionManagement/processRouteItem.js";
import { processList } from "@/api/productionManagement/productionProcess.js";
import Sortable from 'sortablejs';
@@ -171,7 +159,6 @@
const processOptions = ref([]);
const tableLoading = ref(false);
-const isShowProductSelectDialog = ref(false);
const routeItems = ref([]);
let tableSortable = null;
let stepsSortable = null;
@@ -191,8 +178,7 @@
const tableColumn = ref([
{ label: "浜у搧鍚嶇О", prop: "productName", width: 180 },
{ label: "瑙勬牸鍚嶇О", prop: "model", width: 150 },
- { label: "鍗曚綅", prop: "unit", width: 80 },
- { label: "宸ュ簭鍚嶇О", prop: "processId", width: 180 },
+ { label: "宸ュ簭鍚嶇О", prop: "processId", width: 220 },
{
dataType: "action",
label: "鎿嶄綔",
@@ -215,6 +201,21 @@
}
]);
+const getProcessName = item => {
+ if (item.processName || item.operationName) {
+ return item.processName || item.operationName;
+ }
+ const process = processOptions.value.find(process => process.id === item.processId);
+ return process?.name || "璇烽�夋嫨宸ュ簭";
+};
+
+const handleProcessChange = (row, value) => {
+ const process = processOptions.value.find(process => process.id === value);
+ row.processName = process?.name || "";
+ row.operationName = row.processName;
+ row.operationId = value;
+};
+
const removeItem = (index) => {
routeItems.value.splice(index, 1);
nextTick(() => initSortable());
@@ -232,46 +233,18 @@
isShow.value = false;
};
-const handelSelectProducts = (products) => {
- destroySortable();
-
- const newData = products.map(({ id, ...product }) => ({
- ...product,
- productModelId: id,
- routeId: props.record.id,
- id: `${Date.now()}-${Math.random().toString(36).slice(2)}`,
- processId: undefined
- }));
-
- console.log('閫夋嫨浜у搧鍓嶆暟缁�:', routeItems.value);
- routeItems.value.push(...newData);
- routeItems.value = [...routeItems.value];
- console.log('閫夋嫨浜у搧鍚庢暟缁�:', routeItems.value);
-
- // 寤惰繜鍒濆鍖栵紝纭繚DOM瀹屽叏娓叉煋
- nextTick(() => {
- // 寮哄埗閲嶆柊娓叉煋缁勪欢
- if (proxy?.$forceUpdate) {
- proxy.$forceUpdate();
- }
-
- const temp = [...routeItems.value];
- routeItems.value = [];
- nextTick(() => {
- routeItems.value = temp;
- initSortable();
- });
- });
-};
-
const findProcessRouteItems = () => {
tableLoading.value = true;
findProcessRouteItemList({ routeId: props.record.id })
.then(res => {
tableLoading.value = false;
- routeItems.value = res.data.map(item => ({
+ // 鍚庣杩斿洖 technologyOperationId / technologyOperationName锛岄〉闈㈢粺涓�鐢� processId / processName
+ routeItems.value = (res.data || []).map(item => ({
...item,
- processId: item.processId === 0 ? undefined : item.processId
+ productName: item.productName || "",
+ model: item.model || "",
+ processName: item.technologyOperationName || item.processName || "",
+ processId: item.technologyOperationId || (item.processId === 0 ? undefined : item.processId)
}));
// 寤惰繜鍒濆鍖栵紝纭繚DOM瀹屽叏娓叉煋
nextTick(() => {
@@ -302,10 +275,20 @@
proxy?.$modal?.msgError("璇蜂负鎵�鏈夐」鐩�夋嫨宸ュ簭");
return;
}
+ const processIds = routeItems.value.map(item => item.processId);
+ if (new Set(processIds).size !== processIds.length) {
+ proxy?.$modal?.msgError("鍚屼竴閬撳伐搴忎笉鑳介噸澶嶅嚭鐜板湪宸ヨ壓璺嚎涓�");
+ return;
+ }
- addOrUpdateProcessRouteItem({
- routeId: props.record.id,
- processRouteItem: routeItems.value.map(({ id, ...item }) => item)
+ // 鍏ㄩ噺鎻愪氦锛氬伐搴忛『搴忓嵆褰撳墠鍒楄〃椤哄簭锛屾湭鍥炰紶鐨勫伐搴忎細琚悗绔垹闄�
+ saveProcessRouteItems({
+ technologyRoutingId: props.record.id,
+ operationList: routeItems.value.map((item, index) => ({
+ id: item.id,
+ technologyOperationId: item.processId,
+ dragSort: index + 1
+ }))
})
.then(res => {
isShow.value = false;
@@ -349,7 +332,6 @@
const moveItem = routeItems.value.splice(evt.oldIndex, 1)[0];
routeItems.value.splice(evt.newIndex, 0, moveItem);
routeItems.value = [...routeItems.value];
- console.log('鎺掑簭鍚庢暟缁�:', routeItems.value);
}
});
} else {
@@ -385,10 +367,6 @@
routeItems.value = [...routeItems.value];
}
});
-
- // 璋冭瘯锛氭墦鍗板鍣ㄥ拰瀹炰緥锛岀‘璁ょ粦瀹氭垚鍔�
- console.log('姝ラ鏉℃嫋鎷藉鍣�:', stepsList);
- console.log('Sortable瀹炰緥:', stepsSortable);
}
};
@@ -438,6 +416,12 @@
display: flex;
align-items: center;
justify-content: space-between;
+ margin-bottom: 10px;
+}
+
+.route-tip {
+ color: #606266;
+ font-size: 13px;
}
/* 淇敼锛氳嚜瀹氫箟姝ラ鏉″鍣ㄦ牱寮� */
diff --git a/src/views/productionManagement/processRoute/processRouteItem/index.vue b/src/views/productionManagement/processRoute/processRouteItem/index.vue
index 41e2109..24b9899 100644
--- a/src/views/productionManagement/processRoute/processRouteItem/index.vue
+++ b/src/views/productionManagement/processRoute/processRouteItem/index.vue
@@ -265,6 +265,12 @@
缂栬緫
</el-button>
<el-button v-if="bomDataValue.isEdit"
+ type="primary"
+ plain
+ @click="addBomItem()">
+ 娣诲姞鐗╂枡
+ </el-button>
+ <el-button v-if="bomDataValue.isEdit"
@click="cancelEditBom">
鍙栨秷
</el-button>
@@ -287,8 +293,6 @@
:model="bomDataValue">
<el-table :data="bomDataValue.dataList"
row-key="tempId"
- default-expand-all
- :tree-props="{children: 'children', hasChildren: 'hasChildren'}"
style="width: 100%">
<el-table-column prop="productName"
label="浜у搧" />
@@ -301,7 +305,7 @@
<el-select v-model="row.model"
placeholder="璇烽�夋嫨瑙勬牸"
clearable
- :disabled="!bomDataValue.isEdit || bomDataValue.dataList.some(item => (item).tempId === row.tempId)"
+ :disabled="!bomDataValue.isEdit"
style="width: 100%"
@visible-change="(v) => { if (v) openBomDialog(row.tempId) }">
<el-option v-if="row.model"
@@ -315,7 +319,7 @@
label="娑堣�楀伐搴�">
<template #default="{ row }">
<el-form-item v-if="pageType === 'order' && bomDataValue.isEdit"
- :rules="bomDataValue.dataList.some(item => (item).tempId === row.tempId) ? [] : [{ required: true, message: '璇烽�夋嫨娑堣�楀伐搴�', trigger: 'change' }]"
+ :rules="[{ required: true, message: '璇烽�夋嫨娑堣�楀伐搴�', trigger: 'change' }]"
style="margin: 0">
<el-select v-model="row.processId"
placeholder="璇烽�夋嫨"
@@ -323,7 +327,7 @@
clearable
style="width: 100%"
@change="value => handleBomProcessChange(row, value)"
- :disabled="!bomDataValue.isEdit || bomDataValue.dataList.some(item => (item).tempId === row.tempId)">
+ :disabled="!bomDataValue.isEdit">
<el-option v-for="item in bomDataValue.processOptions"
:key="item.id"
:label="item.name"
@@ -332,65 +336,14 @@
</el-form-item>
</template>
</el-table-column>
- <el-table-column prop="unitQuantity"
- label="鍗曚綅浜у嚭鎵�闇�鏁伴噺">
- <template #default="{ row }">
- <el-form-item v-if="pageType === 'order' && bomDataValue.isEdit"
- :rules="[{ required: true, message: '璇疯緭鍏ュ崟浣嶄骇鍑烘墍闇�鏁伴噺', trigger: ['blur','change'] }]"
- style="margin: 0">
- <el-input-number v-model="row.unitQuantity"
- :min="0"
- :step="1"
- controls-position="right"
- style="width: 100%"
- @change="handleUnitQuantityChange"
- :disabled="!bomDataValue.isEdit || bomDataValue.dataList.some(item => (item).tempId === row.tempId)" />
- </el-form-item>
- </template>
- </el-table-column>
- <el-table-column v-if="pageType === 'order'"
- prop="demandedQuantity"
- label="闇�姹傛�婚噺">
- <template #default="{ row }">
- <el-form-item v-if="pageType === 'order' && bomDataValue.isEdit"
- :rules="[{ required: true, message: '璇疯緭鍏ラ渶姹傛�婚噺', trigger: ['blur','change'] }]"
- style="margin: 0">
- <el-input-number v-model="row.demandedQuantity"
- :min="0"
- :step="1"
- controls-position="right"
- style="width: 100%"
- :disabled="true" />
- </el-form-item>
- </template>
- </el-table-column>
- <el-table-column prop="unit"
- label="鍗曚綅">
- <template #default="{ row }">
- <el-form-item v-if="pageType === 'order' && bomDataValue.isEdit"
- :rules="[{ required: true, message: '璇疯緭鍏ュ崟浣�', trigger: ['blur','change'] }]"
- style="margin: 0">
- <el-input v-model="row.unit"
- placeholder="璇疯緭鍏ュ崟浣�"
- clearable
- :disabled="!bomDataValue.isEdit || bomDataValue.dataList.some(item => (item).tempId === row.tempId)" />
- </el-form-item>
- </template>
- </el-table-column>
<el-table-column label="鎿嶄綔"
fixed="right"
- width="200"
+ width="100"
v-if="pageType === 'order' && bomDataValue.isEdit">
<template #default="{ row }">
- <el-button v-if="bomDataValue.isEdit && !bomDataValue.dataList.some(item => (item).tempId === row.tempId)"
- type="danger"
+ <el-button type="danger"
text
@click="removeBomItem(row.tempId)">鍒犻櫎
- </el-button>
- <el-button v-if="bomDataValue.isEdit"
- type="primary"
- text
- @click="addBomItem(row.tempId)">娣诲姞
</el-button>
</template>
</el-table-column>
@@ -1186,60 +1139,11 @@
item.operationName = processName;
};
- const normalizeTreeData = items => {
+ const normalizeBomData = items => {
items.forEach(item => {
item.tempId = item.tempId || item.id || `${Date.now()}_${Math.random()}`;
syncProcessOperationFields(item);
- if (Array.isArray(item.children) && item.children.length > 0) {
- normalizeTreeData(item.children);
- }
});
- };
-
- const toQuantityNumber = value => {
- const numberValue = Number(value);
- if (!Number.isFinite(numberValue)) {
- return 0;
- }
- return Number(numberValue.toFixed(4));
- };
-
- const syncDemandedQuantityTree = (items, parentDemandedQuantity = null) => {
- items.forEach(item => {
- if (parentDemandedQuantity !== null) {
- item.demandedQuantity = toQuantityNumber(
- parentDemandedQuantity * toQuantityNumber(item.unitQuantity)
- );
- }
-
- if (Array.isArray(item.children) && item.children.length > 0) {
- syncDemandedQuantityTree(
- item.children,
- toQuantityNumber(item.demandedQuantity)
- );
- }
- });
- };
-
- const recalculateDemandedQuantities = () => {
- if (pageType.value !== "order") {
- return;
- }
-
- const rootDemandedQuantity = routeInfo.value.quantity;
- if (
- rootDemandedQuantity === undefined ||
- rootDemandedQuantity === null ||
- rootDemandedQuantity === ""
- ) {
- syncDemandedQuantityTree(bomDataValue.value.dataList);
- return;
- }
-
- syncDemandedQuantityTree(
- bomDataValue.value.dataList,
- toQuantityNumber(rootDemandedQuantity)
- );
};
const processChange = value => {
@@ -1252,40 +1156,9 @@
});
};
- const findSiblings = (items, tempId) => {
- if (!items || items.length === 0) return null;
- // 妫�鏌ュ綋鍓嶅眰绾�
- if (items.some(item => item.tempId === tempId)) {
- return items;
- }
- // 閫掑綊鏌ユ壘瀛愮骇
- for (const item of items) {
- if (item.children && item.children.length > 0) {
- const result = findSiblings(item.children, tempId);
- if (result) return result;
- }
- }
- return null;
- };
-
const handleBomProcessChange = (row, value) => {
row.processId = value || "";
syncProcessOperationFields(row);
-
- // 妫�鏌ュ悓涓�灞傜骇鏄惁宸茬粡鏈夊叾浠栦笉鍚岀殑宸ュ簭琚�変腑
- const siblings = findSiblings(bomDataValue.value.dataList, row.tempId);
- if (siblings && value) {
- const hasDifferentProcess = siblings.some(sibling => {
- return (
- sibling.tempId !== row.tempId &&
- sibling.processId &&
- sibling.processId !== value
- );
- });
- if (hasDifferentProcess) {
- ElMessage.warning("鍚屼竴灞傜骇宸插瓨鍦ㄤ笉鍚岀殑宸ュ簭锛岃鍏堢粺涓�宸ュ簭鍚庡啀杩涜淇敼");
- }
- }
};
const openBomDialog = tempId => {
@@ -1300,29 +1173,10 @@
routeInfo.value.bomId
);
bomDataValue.value.dataList = data || [];
- normalizeTreeData(bomDataValue.value.dataList);
- recalculateDemandedQuantities();
+ normalizeBomData(bomDataValue.value.dataList);
} catch (err) {
console.error("鑾峰彇BOM鏁版嵁澶辫触锛�", err);
}
- };
-
- const childItem = (item, tempId, productData) => {
- if (item.tempId === tempId) {
- item.productName = productData.productName;
- item.model = productData.model;
- item.productModelId = productData.id;
- item.unit = productData.unit || "";
- return true;
- }
- if (item.children && item.children.length > 0) {
- for (let child of item.children) {
- if (childItem(child, tempId, productData)) {
- return true;
- }
- }
- }
- return false;
};
const handleBomProduct = row => {
@@ -1331,219 +1185,51 @@
return;
}
const productData = row[row.length - 1];
-
- const isTopLevel = bomDataValue.value.dataList.some(
+ const currentItem = bomDataValue.value.dataList.find(
item => item.tempId === bomDataValue.value.currentRowName
);
- if (isTopLevel) {
- if (
- productData.productName === bomTableData.value[0].productName &&
- productData.model === bomTableData.value[0].model
- ) {
- const hasOther = bomDataValue.value.dataList.some(
- item =>
- item.tempId !== bomDataValue.value.currentRowName &&
- item.productName === bomTableData.value[0].productName &&
- item.model === bomTableData.value[0].model
- );
- if (hasOther) {
- ElMessage.warning("鏈�澶栧眰鍜屽綋鍓嶄骇鍝佷竴鏍风殑涓�绾у彧鑳芥湁涓�涓�");
- return;
- }
- }
+ if (currentItem) {
+ currentItem.productName = productData.productName;
+ currentItem.model = productData.model;
+ currentItem.productModelId = productData.id;
}
- bomDataValue.value.dataList.forEach(item => {
- if (item.tempId === bomDataValue.value.currentRowName) {
- item.productName = productData.productName;
- item.model = productData.model;
- item.productModelId = productData.id;
- item.unit = productData.unit || "";
- return;
- }
- childItem(item, bomDataValue.value.currentRowName, productData);
- });
bomDataValue.value.showProductDialog = false;
};
const removeBomItem = tempId => {
- const topIndex = bomDataValue.value.dataList.findIndex(
+ const index = bomDataValue.value.dataList.findIndex(
item => item.tempId === tempId
);
- if (topIndex !== -1) {
- bomDataValue.value.dataList.splice(topIndex, 1);
- return;
+ if (index !== -1) {
+ bomDataValue.value.dataList.splice(index, 1);
}
-
- const delchildItem = (items, tempId) => {
- for (let i = 0; i < items.length; i++) {
- const item = items[i];
- if (item.tempId === tempId) {
- items.splice(i, 1);
- return true;
- }
- if (item.children && item.children.length > 0) {
- if (delchildItem(item.children, tempId)) {
- return true;
- }
- }
- }
- return false;
- };
-
- bomDataValue.value.dataList.forEach(item => {
- if (item.children && item.children.length > 0) {
- delchildItem(item.children, tempId);
- }
- });
};
- const handleUnitQuantityChange = () => {
- recalculateDemandedQuantities();
- };
-
- const addchildItem = (item, tempId) => {
- if (item.tempId === tempId) {
- if (!item.children) {
- item.children = [];
- }
- item.children.push({
- parentId: item.id || "",
- parentTempId: item.tempId || "",
- productName: "",
- productId: "",
- model: undefined,
- productModelId: undefined,
- processId: "",
- processName: "",
- [pageType.value === "order" ? "technologyOperationId" : "operationId"]:
- "",
- operationName: "",
- unitQuantity: 1,
- demandedQuantity: 0,
- children: [],
- unit: "",
- tempId: new Date().getTime(),
- });
- recalculateDemandedQuantities();
- return true;
- }
- if (item.children && item.children.length > 0) {
- for (let child of item.children) {
- if (addchildItem(child, tempId)) {
- return true;
- }
- }
- }
- return false;
- };
-
- const addBomItem = tempId => {
- bomDataValue.value.dataList.forEach(item => {
- if (item.tempId === tempId) {
- if (!item.children) {
- item.children = [];
- }
- item.children.push({
- parentId: item.id || "",
- parentTempId: item.tempId || "",
- productName: "",
- productId: "",
- model: undefined,
- productModelId: undefined,
- processId: "",
- processName: "",
- [pageType.value === "order" ? "technologyOperationId" : "operationId"]:
- "",
- operationName: "",
- unitQuantity: 1,
- demandedQuantity: 0,
- unit: "",
- children: [],
- tempId: new Date().getTime(),
- });
- recalculateDemandedQuantities();
- return;
- }
- addchildItem(item, tempId);
+ const addBomItem = () => {
+ bomDataValue.value.dataList.push({
+ productName: "",
+ model: undefined,
+ productModelId: undefined,
+ processId: "",
+ processName: "",
+ technologyOperationId: "",
+ operationName: "",
+ tempId: `${Date.now()}_${Math.random()}`,
});
};
const validateAllBom = () => {
- let isValid = true;
- const isOrderPage = pageType.value === "order";
-
- const validateItem = (item, isTopLevel = false) => {
- if (!item.model) {
- ElMessage.error("璇烽�夋嫨瑙勬牸");
- isValid = false;
- return;
+ for (const item of bomDataValue.value.dataList) {
+ if (!item.productModelId) {
+ ElMessage.error("璇烽�夋嫨鐗╂枡瑙勬牸");
+ return false;
}
- if (!isTopLevel && !item.processId) {
+ if (!item.processId) {
ElMessage.error("璇烽�夋嫨娑堣�楀伐搴�");
- isValid = false;
- return;
+ return false;
}
- if (!item.unitQuantity) {
- ElMessage.error("璇疯緭鍏ュ崟浣嶄骇鍑烘墍闇�鏁伴噺");
- isValid = false;
- return;
- }
- if (isOrderPage && !item.demandedQuantity) {
- ElMessage.error("璇疯緭鍏ラ渶姹傛�婚噺");
- isValid = false;
- return;
- }
-
- if (item.children && item.children.length > 0) {
- item.children.forEach(child => {
- validateItem(child, false);
- });
- }
- };
-
- // 鏍¢獙鍚屼竴灞傜骇鐨勫伐搴忔槸鍚︿竴鑷�
- const validateProcessConsistency = items => {
- if (!items || items.length === 0) return;
-
- // 妫�鏌ュ綋鍓嶅眰绾�
- const processes = items
- .filter(item => item.processId)
- .map(item => item.processId);
- if (processes.length > 1) {
- const uniqueProcesses = [...new Set(processes)];
- if (uniqueProcesses.length > 1) {
- ElMessage.error("鍚屼竴灞傜骇鐨勫伐搴忓繀椤讳竴鑷�");
- isValid = false;
- return;
- }
- }
-
- // 閫掑綊妫�鏌ュ瓙绾�
- items.forEach(item => {
- if (item.children && item.children.length > 0) {
- validateProcessConsistency(item.children);
- }
- });
- };
-
- bomDataValue.value.dataList.forEach(item => {
- validateItem(item, true);
- });
-
- validateProcessConsistency(bomDataValue.value.dataList);
-
- return isValid;
- };
-
- const buildSubmitTree = items => {
- return items.map(item => {
- const current = { ...item };
- syncProcessOperationFields(current);
- current.children = Array.isArray(current.children)
- ? buildSubmitTree(current.children)
- : [];
- return current;
- });
+ }
+ return true;
};
const cancelEditBom = () => {
@@ -1553,17 +1239,18 @@
const handleSaveBom = () => {
bomDataValue.value.loading = true;
- console.log(bomDataValue.value.dataList, "bomDataValue.value.dataList");
-
- normalizeTreeData(bomDataValue.value.dataList);
- recalculateDemandedQuantities();
+ normalizeBomData(bomDataValue.value.dataList);
const valid = validateAllBom();
if (valid) {
add2({
- // bomId: Number(routeInfo.value.bomId),
productionOrderBomId: Number(routeInfo.value.bomId) || null,
- children: buildSubmitTree(bomDataValue.value.dataList || []),
+ productionOrderId: Number(orderId.value) || null,
+ detailList: bomDataValue.value.dataList.map(item => ({
+ id: item.id || undefined,
+ productModelId: item.productModelId,
+ technologyOperationId: item.technologyOperationId || item.processId,
+ })),
})
.then(() => {
ElMessage.success("BOM淇濆瓨鎴愬姛");
diff --git a/src/views/productionManagement/productStructure/Detail/index.vue b/src/views/productionManagement/productStructure/Detail/index.vue
index ec18ddf..f8c359f 100644
--- a/src/views/productionManagement/productStructure/Detail/index.vue
+++ b/src/views/productionManagement/productStructure/Detail/index.vue
@@ -27,23 +27,27 @@
<template #default="props">
<el-form ref="form"
:model="dataValue">
+ <div v-if="dataValue.isEdit && !isOrderPage"
+ class="mb10"
+ style="text-align: right;">
+ <el-button type="primary"
+ @click="addItem">娣诲姞鐗╂枡</el-button>
+ </div>
<el-table :data="dataValue.dataList"
row-key="tempId"
- default-expand-all
- :tree-props="{children: 'children', hasChildren: 'hasChildren'}"
style="width: 100%">
<el-table-column prop="productName"
- label="浜у搧" />
+ label="鐗╂枡鍚嶇О" />
<el-table-column prop="model"
- label="瑙勬牸">
- <template #default="{ row, $index }">
+ label="鐗╂枡瑙勬牸">
+ <template #default="{ row }">
<el-form-item v-if="dataValue.isEdit"
- :rules="[{ required: true, message: '璇烽�夋嫨瑙勬牸', trigger: ['blur','change'] }]"
+ :rules="[{ required: true, message: '璇烽�夋嫨鐗╂枡瑙勬牸', trigger: ['blur','change'] }]"
style="margin: 0">
<el-select v-model="row.model"
- placeholder="璇烽�夋嫨瑙勬牸"
+ placeholder="璇烽�夋嫨鐗╂枡瑙勬牸"
clearable
- :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)"
+ :disabled="!dataValue.isEdit"
style="width: 100%"
@visible-change="(v) => { if (v) openDialog(row.tempId) }">
<el-option v-if="row.model"
@@ -51,13 +55,14 @@
:value="row.model" />
</el-select>
</el-form-item>
+ <span v-else>{{ row.model }}</span>
</template>
</el-table-column>
<el-table-column prop="processName"
label="娑堣�楀伐搴�">
- <template #default="{ row, $index }">
+ <template #default="{ row }">
<el-form-item v-if="dataValue.isEdit"
- :rules="dataValue.dataList.some(item => (item as any).tempId === row.tempId) ? [] : [{ required: true, message: '璇烽�夋嫨娑堣�楀伐搴�', trigger: 'change' }]"
+ :rules="[{ required: true, message: '璇烽�夋嫨娑堣�楀伐搴�', trigger: 'change' }]"
style="margin: 0">
<el-select v-model="row.processId"
placeholder="璇烽�夋嫨"
@@ -65,73 +70,24 @@
clearable
style="width: 100%"
@change="value => handleProcessChange(row, value)"
- :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)">
+ :disabled="!dataValue.isEdit">
<el-option v-for="item in dataValue.processOptions"
:key="item.id"
:label="item.name"
:value="item.id" />
</el-select>
</el-form-item>
+ <span v-else>{{ row.processName }}</span>
</template>
</el-table-column>
- <el-table-column prop="unitQuantity"
- label="鍗曚綅浜у嚭鎵�闇�鏁伴噺">
- <template #default="{ row, $index }">
- <el-form-item v-if="dataValue.isEdit"
- :rules="[{ required: true, message: '璇疯緭鍏ュ崟浣嶄骇鍑烘墍闇�鏁伴噺', trigger: ['blur','change'] }]"
- style="margin: 0">
- <el-input-number v-model="row.unitQuantity"
- :min="0"
- :step="1"
- controls-position="right"
- style="width: 100%"
- @change="handleUnitQuantityChange"
- :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" />
- </el-form-item>
- </template>
- </el-table-column>
- <el-table-column v-if="isOrderPage"
- prop="demandedQuantity"
- label="闇�姹傛�婚噺">
- <template #default="{ row, $index }">
- <el-form-item v-if="dataValue.isEdit"
- :rules="[{ required: true, message: '璇疯緭鍏ラ渶姹傛�婚噺', trigger: ['blur','change'] }]"
- style="margin: 0">
- <el-input-number v-model="row.demandedQuantity"
- :min="0"
- :step="1"
- controls-position="right"
- style="width: 100%"
- :disabled="true" />
- </el-form-item>
- </template>
- </el-table-column>
- <el-table-column prop="unit"
- label="鍗曚綅">
- <template #default="{ row, $index }">
- <el-form-item v-if="dataValue.isEdit"
- :rules="[{ required: true, message: '璇疯緭鍏ュ崟浣�', trigger: ['blur','change'] }]"
- style="margin: 0">
- <el-input v-model="row.unit"
- placeholder="璇疯緭鍏ュ崟浣�"
- clearable
- :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" />
- </el-form-item>
- </template>
- </el-table-column>
- <el-table-column label="鎿嶄綔"
+ <el-table-column v-if="dataValue.isEdit && !isOrderPage"
+ label="鎿嶄綔"
fixed="right"
- width="200">
- <template #default="{ row, $index }">
- <el-button v-if="dataValue.isEdit && !dataValue.dataList.some(item => (item as any).tempId === row.tempId)"
- type="danger"
+ width="120">
+ <template #default="{ row }">
+ <el-button type="danger"
text
@click="removeItem(row.tempId)">鍒犻櫎
- </el-button>
- <el-button v-if="dataValue.isEdit"
- type="primary"
- text
- @click="addItem2(row.tempId)">娣诲姞
</el-button>
</template>
</el-table-column>
@@ -238,7 +194,7 @@
};
const syncProcessOperationFields = (item: any) => {
- const processId = item.processId ?? item.operationId ?? "";
+ const processId = item.processId ?? item.operationId ?? item.technologyOperationId ?? "";
if (!processId) {
item.processId = "";
item.operationId = "";
@@ -257,97 +213,27 @@
item.operationName = processName;
};
- const normalizeTreeData = (items: any[]) => {
- items.forEach((item: any) => {
- item.tempId = item.tempId || item.id || `${Date.now()}_${Math.random()}`;
- syncProcessOperationFields(item);
- if (Array.isArray(item.children) && item.children.length > 0) {
- normalizeTreeData(item.children);
- }
- });
+ const createTempId = () => `${Date.now()}_${Math.random()}`;
+
+ // BOM 鍙淮鎶も�滅墿鏂欒鏍� + 娑堣�楀伐搴忊�濓紝tempId 浠呬綔涓鸿〃鏍兼湰鍦拌閿紝涓嶈〃杈句换浣曞眰绾у叧绯�
+ const normalizeMaterialItem = (item: any) => {
+ const material = {
+ id: item.id ?? null,
+ tempId: item.tempId || item.id || createTempId(),
+ productName: item.productName || item.materialName || "",
+ model: item.model || item.materialModel || undefined,
+ productModelId: item.productModelId || item.materialModelId || undefined,
+ processId: item.processId ?? item.operationId ?? item.technologyOperationId ?? "",
+ processName: item.processName || item.operationName || "",
+ operationId: item.operationId ?? item.processId ?? item.technologyOperationId ?? "",
+ operationName: item.operationName || item.processName || "",
+ };
+ syncProcessOperationFields(material);
+ return material;
};
- const toQuantityNumber = (value: any) => {
- const numberValue = Number(value);
- if (!Number.isFinite(numberValue)) {
- return 0;
- }
- return Number(numberValue.toFixed(4));
- };
-
- const syncDemandedQuantityTree = (
- items: any[],
- parentDemandedQuantity: number | null = null
- ) => {
- items.forEach((item: any) => {
- if (parentDemandedQuantity !== null) {
- item.demandedQuantity = toQuantityNumber(
- parentDemandedQuantity * toQuantityNumber(item.unitQuantity)
- );
- }
-
- if (Array.isArray(item.children) && item.children.length > 0) {
- syncDemandedQuantityTree(
- item.children,
- toQuantityNumber(item.demandedQuantity)
- );
- }
- });
- };
-
- const recalculateDemandedQuantities = () => {
- if (!isOrderPage.value) {
- return;
- }
-
- syncDemandedQuantityTree(dataValue.dataList);
- };
-
- const buildSubmitTree = (items: any[]) => {
- return items.map((item: any) => {
- const current = { ...item };
- syncProcessOperationFields(current);
- current.children = Array.isArray(current.children)
- ? buildSubmitTree(current.children)
- : [];
- return current;
- });
- };
-
- const findSiblings = (items: any[], tempId: string): any[] | null => {
- if (!items || items.length === 0) return null;
- // 妫�鏌ュ綋鍓嶅眰绾�
- if (items.some(item => item.tempId === tempId)) {
- return items;
- }
- // 閫掑綊鏌ユ壘瀛愮骇
- for (const item of items) {
- if (item.children && item.children.length > 0) {
- const result = findSiblings(item.children, tempId);
- if (result) return result;
- }
- }
- return null;
- };
-
- const handleProcessChange = (row: any, value: any) => {
- row.processId = value || "";
- syncProcessOperationFields(row);
-
- // 妫�鏌ュ悓涓�灞傜骇鏄惁宸茬粡鏈夊叾浠栦笉鍚岀殑宸ュ簭琚�変腑
- const siblings = findSiblings(dataValue.dataList, row.tempId);
- if (siblings && value) {
- const hasDifferentProcess = siblings.some(sibling => {
- return sibling.tempId !== row.tempId && sibling.processId && sibling.processId !== value;
- });
- if (hasDifferentProcess) {
- ElMessage.warning("鍚屼竴灞傜骇宸插瓨鍦ㄤ笉鍚岀殑宸ュ簭锛岃鍏堢粺涓�宸ュ簭鍚庡啀杩涜淇敼");
- }
- }
- };
-
- const handleUnitQuantityChange = () => {
- recalculateDemandedQuantities();
+ const normalizeMaterialList = (items: any[]) => {
+ return normalizeListData(items).map(item => normalizeMaterialItem(item));
};
const tableData = reactive([
@@ -359,7 +245,6 @@
]);
const openDialog = (tempId: any) => {
- console.log(tempId, "tempId");
dataValue.currentRowName = tempId;
dataValue.showProductDialog = true;
};
@@ -368,188 +253,94 @@
if (isOrderPage.value) {
// 璁㈠崟鎯呭喌锛氫娇鐢ㄨ鍗曠殑浜у搧缁撴瀯鎺ュ彛
const { data } = await listProcessBom({ orderId: routeOrderId.value });
- dataValue.dataList = (data as any) || [];
- normalizeTreeData(dataValue.dataList);
- recalculateDemandedQuantities();
+ dataValue.dataList = normalizeMaterialList((data as any) || []);
} else {
// 闈炶鍗曟儏鍐碉細浣跨敤鍘熸潵鐨勬帴鍙�
const { data } = await queryList(routeId.value);
- dataValue.dataList = (data as any) || [];
- console.log(dataValue);
- normalizeTreeData(dataValue.dataList);
- console.log(dataValue.dataList, "dataValue.dataList");
+ dataValue.dataList = normalizeMaterialList((data as any) || []);
}
};
const fetchProcessOptions = async () => {
const { data } = await list({});
- console.log(data, "dataValue.dataList");
dataValue.processOptions = normalizeListData(data);
};
const handleProduct = (row: any) => {
if (!Array.isArray(row) || row.length === 0) {
- ElMessage.warning("璇烽�夋嫨涓�涓骇鍝�");
+ ElMessage.warning("璇烽�夋嫨涓�涓墿鏂�");
return;
}
// 鍙厑璁镐竴涓細濡傛灉涓婃父杩斿洖浜嗗涓紝榛樿浣跨敤鏈�鍚庝竴娆¢�夋嫨骞惰鐩栧綋鍓嶅��
const productData = row[row.length - 1];
- // 鏈�澶栧眰缁勪欢涓紝涓庡綋鍓嶄骇鍝佺浉鍚岀殑浜у搧鍙兘鏈変竴涓�
- const isTopLevel = dataValue.dataList.some(
- item => (item as any).tempId === dataValue.currentRowName
+ const current = dataValue.dataList.find(
+ item => item.tempId === dataValue.currentRowName
);
- if (isTopLevel) {
- if (
- productData.productName === tableData[0].productName &&
- productData.model === tableData[0].model
- ) {
- // 鏌ユ壘鏄惁宸茬粡鏈夊叾浠栭《灞傝宸茬粡鏄繖涓骇鍝�
- const hasOther = dataValue.dataList.some(
- item =>
- (item as any).tempId !== dataValue.currentRowName &&
- (item as any).productName === tableData[0].productName &&
- (item as any).model === tableData[0].model
- );
- if (hasOther) {
- ElMessage.warning("鏈�澶栧眰鍜屽綋鍓嶄骇鍝佷竴鏍风殑涓�绾у彧鑳芥湁涓�涓�");
- return;
- }
- }
+ if (current) {
+ current.productName = productData.productName;
+ current.model = productData.model;
+ current.productModelId = productData.id;
}
- // dataValue.dataList[dataValue.currentRowIndex].productName =
- // row[0].productName;
- // dataValue.dataList[dataValue.currentRowIndex].model = row[0].model;
- // dataValue.dataList[dataValue.currentRowIndex].productModelId = row[0].id;
- // dataValue.dataList[dataValue.currentRowIndex].unit = row[0].unit || "";
- dataValue.dataList.map(item => {
- if (item.tempId === dataValue.currentRowName) {
- item.productName = productData.productName;
- item.model = productData.model;
- item.productModelId = productData.id;
- item.unit = productData.unit || "";
- return;
- }
- childItem(item, dataValue.currentRowName, productData);
- });
dataValue.showProductDialog = false;
};
- const childItem = (item: any, tempId: any, productData: any) => {
- if (item.tempId === tempId) {
- item.productName = productData.productName;
- item.model = productData.model;
- item.productModelId = productData.id;
- item.unit = productData.unit || "";
- return true;
+
+ const validateAll = () => {
+ if (dataValue.dataList.length === 0) {
+ ElMessage.error("璇锋坊鍔犵墿鏂�");
+ return false;
}
- if (item.children && item.children.length > 0) {
- for (let child of item.children) {
- if (childItem(child, tempId, productData)) {
- return true;
- }
+
+ const invalidItem = dataValue.dataList.find(
+ item => !item.model || !item.productModelId || !item.processId
+ );
+ if (invalidItem) {
+ ElMessage.error("璇烽�夋嫨鐗╂枡瑙勬牸鍜屾秷鑰楀伐搴�");
+ return false;
+ }
+
+ const relationKeys = new Set();
+ const duplicated = dataValue.dataList.some(item => {
+ const key = `${item.productModelId}#${item.processId}`;
+ if (relationKeys.has(key)) {
+ return true;
}
+ relationKeys.add(key);
+ return false;
+ });
+ if (duplicated) {
+ ElMessage.error("鍚屼竴鐗╂枡瑙勬牸鍦ㄥ悓涓�宸ュ簭涓嬩笉鑳介噸澶嶉厤缃�");
+ return false;
}
- return false;
+
+ return true;
};
- // 閫掑綊鏍¢獙鎵�鏈夊眰绾х殑琛ㄥ崟鏁版嵁
- const validateAll = () => {
- let isValid = true;
-
- // 鏍¢獙涓�缁勫厔寮熻妭鐐圭殑宸ュ簭鏄惁閮界浉鍚�
- const checkProcessUniqueness = (items: any[]) => {
- if (!items || items.length === 0 || !isValid) return;
-
- // 鑾峰彇绗竴涓潪绌虹殑宸ュ簭ID浣滀负鍙傝��
- const firstProcessId = items.find(item => item.processId)?.processId;
-
- // 濡傛灉鏈夊伐搴廔D锛屾鏌ユ墍鏈夐」鏄惁閮戒娇鐢ㄧ浉鍚岀殑宸ュ簭
- if (firstProcessId) {
- for (const item of items) {
- if (item.processId && item.processId !== firstProcessId) {
- const option1 = getProcessOptionById(firstProcessId);
- const option2 = getProcessOptionById(item.processId);
- const processName1 = option1?.name || "鏈煡宸ュ簭";
- const processName2 = option2?.name || "鏈煡宸ュ簭";
- ElMessage.error(
- `褰撳墠灞傜骇涓嬪伐搴忎笉涓�鑷达紝璇蜂娇鐢ㄧ浉鍚岀殑宸ュ簭銆傚瓨鍦ㄣ��${processName1}銆嶅拰銆�${processName2}銆峘
- );
- isValid = false;
- return;
- }
- }
- }
-
- // 閫掑綊鏍¢獙瀛愮骇鐨勫厔寮熻妭鐐�
- for (const item of items) {
- if (item.children && item.children.length > 0) {
- checkProcessUniqueness(item.children);
- }
- }
- };
-
- // 鏍¢獙鍑芥暟
- const validateItem = (item: any, isTopLevel = false) => {
- if (!isValid) return;
- // 鏍¢獙褰撳墠椤圭殑蹇呭~瀛楁
- if (!item.model) {
- ElMessage.error("璇烽�夋嫨瑙勬牸");
- isValid = false;
- return;
- }
- if (!isTopLevel && !item.processId) {
- ElMessage.error("璇烽�夋嫨娑堣�楀伐搴�");
- isValid = false;
- return;
- }
- if (!item.unitQuantity) {
- ElMessage.error("璇疯緭鍏ュ崟浣嶄骇鍑烘墍闇�鏁伴噺");
- isValid = false;
- return;
- }
- if (isOrderPage.value && !item.demandedQuantity) {
- ElMessage.error("璇疯緭鍏ラ渶姹傛�婚噺");
- isValid = false;
- return;
- }
- // if (!item.unit) {
- // ElMessage.error("璇疯緭鍏ュ崟浣�");
- // isValid = false;
- // return;
- // }
-
- // 閫掑綊鏍¢獙瀛愰」瀛楁
- if (item.children && item.children.length > 0) {
- item.children.forEach(child => {
- validateItem(child, false);
- });
- }
- };
-
- // 1. 棣栧厛鏍¢獙鍚屼竴鐖剁骇涓嬬殑鍚屽眰娑堣�楀伐搴忔槸鍚﹀敮涓�
- checkProcessUniqueness(dataValue.dataList);
- if (!isValid) return false;
-
- // 2. 鐒跺悗閬嶅巻鏍¢獙鎵�鏈夐《灞傞」鐨勫瓧娈靛繀濉儏鍐�
- dataValue.dataList.forEach(item => {
- validateItem(item, true);
+ // 鎻愪氦缁欏悗绔殑鍙湁鐗╂枡瑙勬牸鍜屾秷鑰楀伐搴忥紝涓嶅惈鏁伴噺銆佸崟浣嶅拰灞傜骇瀛楁
+ const buildSubmitList = (items: any[]) => {
+ return items.map((item: any) => {
+ const current = normalizeMaterialItem(item);
+ return {
+ id: current.id || undefined,
+ productModelId: current.productModelId,
+ operationId: current.operationId,
+ };
});
+ };
- return isValid;
+ const handleProcessChange = (row: any, value: any) => {
+ row.processId = value || "";
+ syncProcessOperationFields(row);
};
const submit = () => {
dataValue.loading = true;
- normalizeTreeData(dataValue.dataList);
- recalculateDemandedQuantities();
- // 鍏堣繘琛岃〃鍗曟牎楠�
const valid = validateAll();
- console.log(dataValue.dataList, "dataValue.dataList");
if (valid) {
addBomDetail({
bomId: routeId.value,
- children: buildSubmitTree(dataValue.dataList || []),
+ detailList: buildSubmitList(dataValue.dataList || []),
})
.then(res => {
router.go(-1);
@@ -565,117 +356,28 @@
};
const removeItem = (tempId: string) => {
- // 鍏堝皾璇曚粠椤跺眰鍒犻櫎
- const topIndex = dataValue.dataList.findIndex(item => item.tempId === tempId);
- if (topIndex !== -1) {
- dataValue.dataList.splice(topIndex, 1);
- return;
+ const index = dataValue.dataList.findIndex(item => item.tempId === tempId);
+ if (index !== -1) {
+ dataValue.dataList.splice(index, 1);
}
+ };
- // 閫掑綊鍒犻櫎瀛愰」
- const delchildItem = (items: any[], tempId: any) => {
- for (let i = 0; i < items.length; i++) {
- const item = items[i];
- if (item.tempId === tempId) {
- items.splice(i, 1);
- return true;
- }
- if (item.children && item.children.length > 0) {
- if (delchildItem(item.children, tempId)) {
- return true;
- }
- }
- }
- return false;
- };
-
- dataValue.dataList.forEach(item => {
- if (item.children && item.children.length > 0) {
- delchildItem(item.children, tempId);
- }
+ const addItem = () => {
+ dataValue.dataList.push({
+ id: null,
+ productName: "",
+ model: undefined,
+ productModelId: undefined,
+ processId: "",
+ processName: "",
+ operationId: "",
+ operationName: "",
+ tempId: createTempId(),
});
- };
- const addItem2 = tempId => {
- dataValue.dataList.map(item => {
- if (item.tempId === tempId) {
- if (!item.children) {
- item.children = [];
- }
- item.children.push({
- parentId: item.id || "",
- parentTempId: item.tempId || "",
- productName: "",
- productId: "",
- model: undefined,
- productModelId: undefined,
- processId: "",
- processName: "",
- operationId: "",
- operationName: "",
- unitQuantity: 1,
- demandedQuantity: 0,
- unit: "",
- children: [],
-
- tempId: new Date().getTime(),
- });
- recalculateDemandedQuantities();
- return;
- }
- addchildItem(item, tempId);
- });
- };
- const addchildItem = (item: any, tempId: any) => {
- if (item.tempId === tempId) {
- console.log(item, "item");
- if (!item.children) {
- item.children = [];
- }
- item.children.push({
- parentId: item.id || "",
- parentTempId: item.tempId || "",
- productName: "",
- productId: "",
- model: undefined,
- productModelId: undefined,
- processId: "",
- processName: "",
- operationId: "",
- operationName: "",
- unitQuantity: 1,
- demandedQuantity: 0,
- children: [],
- unit: "",
- tempId: new Date().getTime(),
- });
- recalculateDemandedQuantities();
- return true;
- }
- if (item.children && item.children.length > 0) {
- for (let child of item.children) {
- if (addchildItem(child, tempId)) {
- return true;
- }
- }
- }
- return false;
- };
-
- const getPropPath = (row, field) => {
- // 涓烘瘡涓猺ow鐢熸垚鍞竴鐨勮矾寰�
- // 浣跨敤row.id鎴栫储寮曚綔涓哄敮涓�鏍囪瘑
- let path = "dataList";
-
- // 绠�鍗曞疄鐜帮細浣跨敤row鐨刬d鎴栦竴涓敮涓�鏍囪瘑
- const uniqueId = row.id || Math.floor(Math.random() * 10000);
- path += `.${uniqueId}`;
-
- return path + `.${field}`;
};
const cancelEdit = () => {
dataValue.isEdit = false;
- // dataValue.dataList = dataValue.dataList.filter(item => item.id !== undefined);
fetchData();
};
@@ -694,4 +396,4 @@
await fetchProcessOptions();
await fetchData();
});
-</script>
\ No newline at end of file
+</script>
diff --git a/src/views/productionManagement/productionOrder/components/MaterialDetailDialog.vue b/src/views/productionManagement/productionOrder/components/MaterialDetailDialog.vue
index 3373374..805389b 100644
--- a/src/views/productionManagement/productionOrder/components/MaterialDetailDialog.vue
+++ b/src/views/productionManagement/productionOrder/components/MaterialDetailDialog.vue
@@ -4,28 +4,28 @@
title="棰嗘枡璇︽儏"
width="1400px"
@close="handleClose">
+ <div class="material-detail-tip">
+ 灞曠ず璇ョ敓浜ц鍗曚笅鎵�鏈夊伐搴忕殑棰嗘枡璁板綍銆�
+ </div>
<el-table v-loading="materialDetailLoading"
:data="materialDetailTableData"
border
row-key="id">
<el-table-column label="宸ュ簭鍚嶇О"
prop="operationName"
- min-width="180" />
+ min-width="160" />
+ <el-table-column label="宸ュ崟缂栧彿"
+ prop="workOrderNo"
+ min-width="150" />
<el-table-column label="鍘熸枡鍚嶇О"
- prop="productName"
+ prop="materialName"
min-width="160" />
<el-table-column label="鍘熸枡鍨嬪彿"
- prop="model"
+ prop="materialModel"
min-width="180" />
<el-table-column label="鎵瑰彿"
prop="batchNo"
min-width="150" />
- <el-table-column label="闇�姹傛暟閲�"
- min-width="110">
- <template #default="{ row }">
- {{ stripTrailingZeros(row.demandedQuantity) }}
- </template>
- </el-table-column>
<el-table-column label="璁¢噺鍗曚綅"
prop="unit"
width="100" />
@@ -35,105 +35,28 @@
{{ stripTrailingZeros(row.pickQuantity) }}
</template>
</el-table-column>
- <el-table-column label="琛ユ枡鏁伴噺"
- min-width="120">
- <template #default="{ row }">
- <el-button type="primary"
- link
- @click="handleViewSupplementRecord(row)">
- {{ stripTrailingZeros(row.feedingQty) ?? 0 }}
- </el-button>
- </template>
- </el-table-column>
<el-table-column label="閫�鏂欐暟閲�"
min-width="110">
<template #default="{ row }">
- {{ stripTrailingZeros(row.returnQty) ?? 0 }}
+ {{ stripTrailingZeros(row.returnQty) || 0 }}
</template>
</el-table-column>
- <el-table-column label="瀹為檯鏁伴噺"
- min-width="140">
+ <el-table-column label="鍑�棰嗘枡鏁伴噺"
+ min-width="130">
<template #default="{ row }">
- <el-input-number v-model="row.actualQty"
- :min="0"
- :step="1"
- controls-position="right"
- placeholder="杈撳叆瀹為檯鏁伴噺"
- :formatter="value => stripTrailingZeros(value)"
- :parser="value => parseFloat(value) || 0"
- style="width: 100%;"
- :disabled="row.returned || orderRow?.end"
- @change="val => handleActualQtyChange(row, val)" />
+ {{ stripTrailingZeros(row.effectiveQuantity) || 0 }}
</template>
</el-table-column>
- </el-table>
- <template #footer>
- <span class="dialog-footer">
- <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="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">
- <el-button @click="supplementRecordDialogVisible = false">鍏抽棴</el-button>
- </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="閫�鏂欐眹鎬绘暟閲�"
- min-width="140">
+ <el-table-column label="棰嗘枡鏃堕棿"
+ min-width="120">
<template #default="{ row }">
- {{ stripTrailingZeros(row.returnQtyTotal) }}
+ {{ formatDate(row.createTime) }}
</template>
</el-table-column>
</el-table>
<template #footer>
<span class="dialog-footer">
- <el-button type="primary"
- :loading="materialReturnConfirming"
- @click="handleReturnConfirm">纭鎻愪氦</el-button>
- <el-button @click="returnSummaryDialogVisible = false">鍙栨秷</el-button>
+ <el-button @click="dialogVisible = false">鍏抽棴</el-button>
</span>
</template>
</el-dialog>
@@ -142,12 +65,9 @@
<script setup>
import { computed, ref, watch } from "vue";
- import { ElMessage } from "element-plus";
- import {
- listMaterialPickingDetail,
- listMaterialSupplementRecord,
- updateMaterialPickingLedger,
- } from "@/api/productionManagement/productionOrder.js";
+ import { listMaterialPickingDetail } from "@/api/productionManagement/productionOrder.js";
+
+ const formatDate = value => (value ? String(value).slice(0, 10) : "-");
const stripTrailingZeros = val => {
const str = String(val ?? "");
@@ -170,21 +90,6 @@
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 loadDetailList = async () => {
if (!props.orderRow?.id) return;
@@ -192,12 +97,15 @@
materialDetailTableData.value = [];
try {
const res = await listMaterialPickingDetail(props.orderRow.id);
- materialDetailTableData.value = (res.data || []).map(item => ({
+ const records = Array.isArray(res.data) ? res.data : res.data?.records || [];
+ materialDetailTableData.value = records.map(item => ({
...item,
- actualQty:
- item.actualQty ??
- Number(item.pickQuantity || 0) + Number(item.feedingQty || 0),
+ operationName: item.operationName || item.processName || "",
+ materialName: item.productName || item.materialName || "",
+ materialModel: item.model || item.materialModel || "",
+ pickQuantity: item.pickQuantity ?? item.quantity ?? 0,
returnQty: item.returnQty ?? 0,
+ effectiveQuantity: item.effectiveQuantity ?? 0,
}));
} finally {
materialDetailLoading.value = false;
@@ -216,87 +124,12 @@
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);
- });
- 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"></style>
+<style scoped lang="scss">
+ .material-detail-tip {
+ margin-bottom: 12px;
+ color: #606266;
+ font-size: 13px;
+ }
+</style>
diff --git a/src/views/productionManagement/productionOrder/index.vue b/src/views/productionManagement/productionOrder/index.vue
index 5c3d4cf..733c22e 100644
--- a/src/views/productionManagement/productionOrder/index.vue
+++ b/src/views/productionManagement/productionOrder/index.vue
@@ -192,24 +192,12 @@
</div>
</div>
</el-dialog>
- <MaterialLedgerDialog v-model="materialDialogVisible"
- :order-row="currentMaterialOrder"
- />
<MaterialDetailDialog v-model="materialDetailDialogVisible"
:order-row="currentMaterialDetailOrder"
@confirmed="getList" />
- <MaterialSupplementDialog v-model="materialSupplementDialogVisible"
- :order-row="currentMaterialSupplementOrder"
- @saved="getList" />
<new-product-order v-if="isShowNewModal"
v-model:visible="isShowNewModal"
@completed="handleQuery" />
- <!-- 鎵撳嵃棰嗘枡鍗曠粍浠� -->
- <div class="print-requisition-wrapper">
- <PrintMaterialRequisition ref="printRef"
- :order-row="printOrderRow"
- :material-list="printMaterialList" />
- </div>
</div>
</template>
@@ -236,16 +224,9 @@
updateProductOrder,
} from "@/api/productionManagement/productionOrder.js";
import { listMain as getOrderProcessRouteMain } from "@/api/productionManagement/productProcessRoute.js";
- import MaterialLedgerDialog from "@/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue";
import MaterialDetailDialog from "@/views/productionManagement/productionOrder/components/MaterialDetailDialog.vue";
- import MaterialSupplementDialog from "@/views/productionManagement/productionOrder/components/MaterialSupplementDialog.vue";
- import PrintMaterialRequisition from "@/views/productionManagement/productionOrder/components/PrintMaterialRequisition.vue";
import PIMTable from "@/components/PIMTable/PIMTable.vue";
import { listPage } from "@/api/productionManagement/processRoute.js";
- import {
- listMaterialPickingDetail,
- listMaterialPickingBom,
- } from "@/api/productionManagement/productionOrder.js";
const NewProductOrder = defineAsyncComponent(() =>
import("@/views/productionManagement/productionOrder/New.vue")
);
@@ -398,38 +379,11 @@
},
},
{
- name: "棰嗘枡",
- type: "text",
- color: "#5EC7AB",
- showHide: row => !row.endOrder && !row.returned,
- clickFun: row => {
- openMaterialDialog(row);
- },
- },
- {
- name: "琛ユ枡",
- type: "text",
- color: "#5EC7AB",
- showHide: row => !row.endOrder && !row.returned,
- clickFun: row => {
- openMaterialSupplementDialog(row);
- },
- },
- {
name: "棰嗘枡璇︽儏",
type: "text",
color: "#5EC7AB",
clickFun: row => {
openMaterialDetailDialog(row);
- },
- },
- {
- name: "鎵撳嵃棰嗘枡鍗�",
- type: "text",
- color: "#5EC7AB",
- showHide: row => !row.endOrder,
- clickFun: row => {
- handlePrint(row);
},
},
{
@@ -524,48 +478,8 @@
orderId: null,
routeId: null,
});
- const materialDialogVisible = ref(false);
- const currentMaterialOrder = ref(null);
const materialDetailDialogVisible = ref(false);
const currentMaterialDetailOrder = ref(null);
- const materialSupplementDialogVisible = ref(false);
- const currentMaterialSupplementOrder = ref(null);
-
- // 鎵撳嵃鐩稿叧
- const printOrderRow = ref(null);
- const printMaterialList = ref([]);
- const handlePrint = async row => {
- printOrderRow.value = row;
- proxy.$modal.loading("姝e湪鑾峰彇棰嗘枡鏁版嵁...");
- try {
- printMaterialList.value = [];
- const detailRes = await listMaterialPickingDetail(row.id);
- const detailList = Array.isArray(detailRes?.data)
- ? detailRes.data
- : detailRes?.data?.records || [];
-
- if (detailList.length > 0) {
- printMaterialList.value = detailList;
- }
-
- if (printMaterialList.value.length === 0) {
- proxy.$modal.msgWarning("鏆傛棤棰嗘枡鏁版嵁");
- return;
- }
-
- // 绛夊緟 DOM 鏇存柊鍚庢墽琛屾墦鍗�
- proxy.$nextTick(() => {
- setTimeout(() => {
- window.print();
- }, 800);
- });
- } catch (e) {
- console.error("鑾峰彇棰嗘枡鏁版嵁澶辫触锛�", e);
- proxy.$modal.msgError("鑾峰彇棰嗘枡鏁版嵁澶辫触");
- } finally {
- proxy.$modal.closeLoading();
- }
- };
const openBindRouteDialog = async (row, type) => {
bindForm.orderId = row.id;
@@ -611,19 +525,9 @@
}
};
- const openMaterialDialog = row => {
- currentMaterialOrder.value = row;
- materialDialogVisible.value = true;
- };
-
const openMaterialDetailDialog = async row => {
currentMaterialDetailOrder.value = row;
materialDetailDialogVisible.value = true;
- };
-
- const openMaterialSupplementDialog = row => {
- currentMaterialSupplementOrder.value = row;
- materialSupplementDialogVisible.value = true;
};
const handleReset = () => {
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>
diff --git a/src/views/productionManagement/workOrderManagement/index.vue b/src/views/productionManagement/workOrderManagement/index.vue
index b2ac432..16731c9 100644
--- a/src/views/productionManagement/workOrderManagement/index.vue
+++ b/src/views/productionManagement/workOrderManagement/index.vue
@@ -270,8 +270,8 @@
productWorkOrderPage,
addProductMain,
downProductWorkOrder,
+ checkWorkOrderMaterialPicked,
} from "@/api/productionManagement/workOrder.js";
- import { listMaterialPickingDetail } from "@/api/productionManagement/productionOrder.js";
import { findProcessParamListOrder } from "@/api/productionManagement/productProcessRoute.js";
import { getUserProfile, userListNoPageByTenantId } from "@/api/system/user.js";
import { getDicts } from "@/api/system/dict/data";
@@ -376,12 +376,13 @@
openWorkOrderFiles(row);
},
},
- // {
- // name: "鐗╂枡",
- // clickFun: row => {
- // openMaterialDialog(row);
- // },
- // },
+ {
+ name: "棰嗘枡",
+ clickFun: row => {
+ openMaterialDialog(row);
+ },
+ showHide: row => !row.endOrder,
+ },
{
name: "鎶ュ伐",
clickFun: row => {
@@ -643,20 +644,26 @@
fileDialogVisible.value = true;
};
- const showReportDialog = async row => {
- if (row.productionOrderId) {
- try {
- const res = await listMaterialPickingDetail(row.productionOrderId);
- const records = Array.isArray(res.data)
- ? res.data
- : res.data?.records || [];
- if (res.code === 200 && records.length === 0) {
- proxy.$modal.msgError("鏈鏂欐棤娉曟姤宸�");
- return;
- }
- } catch (error) {
- console.error("鏌ヨ棰嗘枡璇︽儏澶辫触:", error);
+ // 浠呯敤浜庡嵆鏃舵彁绀猴細鎶ュ伐鏉冮檺鏈�缁堢敱鍚庣鍦ㄦ姤宸ヤ簨鍔″唴鏍¢獙褰撳墠宸ュ簭鏄惁宸查鏂�
+ const checkCurrentProcessPicked = async row => {
+ try {
+ const res = await checkWorkOrderMaterialPicked(row.id);
+ if (res.code === 200 && res.data !== true) {
+ proxy.$modal.msgError("褰撳墠宸ュ簭鏈鏂欙紝鏃犳硶鎶ュ伐");
+ return false;
}
+ return true;
+ } catch (error) {
+ console.error("鏌ヨ褰撳墠宸ュ簭棰嗘枡澶辫触:", error);
+ proxy.$modal.msgError("鏌ヨ褰撳墠宸ュ簭棰嗘枡澶辫触");
+ return false;
+ }
+ };
+
+ const showReportDialog = async row => {
+ const picked = await checkCurrentProcessPicked(row);
+ if (!picked) {
+ return;
}
currentReportRowData.value = row;
reportForm.planQuantity = subtractQuantity(
--
Gitblit v1.9.3