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