From 0c2901e13dcc61ebae45aa7433d71b43bbf56d25 Mon Sep 17 00:00:00 2001
From: zhangwencui <1064582902@qq.com>
Date: 星期三, 22 四月 2026 15:42:17 +0800
Subject: [PATCH] Merge branch 'dev_New' into dev_NEW_pro
---
src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue | 298 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 298 insertions(+), 0 deletions(-)
diff --git a/src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue b/src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue
new file mode 100644
index 0000000..9e1a852
--- /dev/null
+++ b/src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue
@@ -0,0 +1,298 @@
+<template>
+ <div>
+ <el-dialog v-model="dialogVisible" title="棰嗘枡鍙拌处" width="1200px" @close="handleClose">
+ <div class="material-toolbar">
+ <el-button type="primary" @click="handleAddMaterialRow">鏂板</el-button>
+ </div>
+ <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-else
+ v-model="row.processName"
+ placeholder="璇烽�夋嫨宸ュ簭"
+ clearable
+ filterable
+ style="width: 100%;"
+ @change="val => handleProcessNameChange(row, val)"
+ >
+ <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 }">
+ <span v-if="row.bom === true">{{ row.materialName || "-" }}</span>
+ <el-button v-else type="primary" link @click="openMaterialProductSelect(row)">
+ {{ row.materialName || "閫夋嫨鍘熸枡" }}
+ </el-button>
+ </template>
+ </el-table-column>
+ <el-table-column label="鍘熸枡鍨嬪彿" min-width="180">
+ <template #default="{ row }">
+ {{ row.materialModel || "-" }}
+ </template>
+ </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"
+ :step="1"
+ controls-position="right"
+ style="width: 100%;"
+ @change="val => handleRequiredQtyChange(row, val)"
+ />
+ </template>
+ </el-table-column>
+ <el-table-column label="璁¢噺鍗曚綅" width="120">
+ <template #default="{ row }">
+ {{ row.unit || "-" }}
+ </template>
+ </el-table-column>
+ <el-table-column label="棰嗙敤鏁伴噺" min-width="120">
+ <template #default="{ row }">
+ <el-input-number
+ v-model="row.pickQty"
+ :min="0"
+ :precision="3"
+ :step="1"
+ controls-position="right"
+ style="width: 100%;"
+ />
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" width="90" fixed="right">
+ <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>
+ <template #footer>
+ <span class="dialog-footer">
+ <el-button type="primary" :loading="materialSaving" @click="handleMaterialSave">淇濆瓨</el-button>
+ <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+ </span>
+ </template>
+ </el-dialog>
+
+ <ProductSelectDialog
+ 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 { findProductProcessRouteItemList } from "@/api/productionManagement/productProcessRoute.js";
+import {
+ listMaterialPickingDetail,
+ listMaterialPickingLedger,
+ saveMaterialPickingLedger,
+} from "@/api/productionManagement/productionOrder.js";
+
+const props = defineProps({
+ modelValue: { type: Boolean, default: false },
+ orderRow: { type: Object, default: null },
+});
+const emit = defineEmits(["update:modelValue", "saved"]);
+
+const dialogVisible = computed({
+ get: () => props.modelValue,
+ set: val => emit("update:modelValue", val),
+});
+
+const materialProductDialogVisible = ref(false);
+const materialTableLoading = ref(false);
+const materialSaving = ref(false);
+const materialTableData = ref([]);
+const processOptions = ref([]);
+const currentMaterialSelectRowIndex = ref(-1);
+let materialTempId = 0;
+
+const createMaterialRow = (row = {}) => ({
+ 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 || "",
+ requiredQty: Number(row.requiredQty ?? 0),
+ unit: row.unit || "",
+ pickQty: Number(row.pickQty ?? row.requiredQty ?? 0),
+});
+
+const getProcessOptions = async () => {
+ 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 () => {
+ if (!props.orderRow?.id) return;
+ materialTableLoading.value = true;
+ materialTableData.value = [];
+ await getProcessOptions();
+ try {
+ 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;
+ }
+};
+
+watch(
+ () => dialogVisible.value,
+ visible => {
+ if (visible) {
+ loadMaterialData();
+ }
+ }
+);
+
+const handleClose = () => {
+ materialTableData.value = [];
+ currentMaterialSelectRowIndex.value = -1;
+};
+
+const handleAddMaterialRow = () => {
+ materialTableData.value.push(createMaterialRow());
+};
+
+const handleDeleteMaterialRow = index => {
+ materialTableData.value.splice(index, 1);
+};
+
+const handleProcessNameChange = (row, processName) => {
+ const process = processOptions.value.find(item => item.name === processName);
+ row.productProcessId = process?.id;
+};
+
+const handleRequiredQtyChange = (row, val) => {
+ const required = Number(val ?? 0);
+ row.requiredQty = required;
+ if (!row.pickQty || Number(row.pickQty) === 0) {
+ row.pickQty = required;
+ }
+};
+
+const openMaterialProductSelect = row => {
+ currentMaterialSelectRowIndex.value = materialTableData.value.findIndex(item => item.tempId === row.tempId);
+ materialProductDialogVisible.value = true;
+};
+
+const handleMaterialProductConfirm = products => {
+ if (!products || products.length === 0) return;
+ const index = currentMaterialSelectRowIndex.value;
+ if (index < 0 || !materialTableData.value[index]) return;
+ const product = products[0];
+ const row = materialTableData.value[index];
+ 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 { 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.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) 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.processName,
+ productProcessId: item.productProcessId,
+ processName: item.processName,
+ bom: item.bom === true,
+ materialModelId: item.materialModelId,
+ materialName: item.materialName,
+ materialModel: item.materialModel,
+ requiredQty: item.requiredQty,
+ unit: item.unit,
+ pickQty: item.pickQty,
+ })),
+ });
+ emit("saved");
+ dialogVisible.value = false;
+ } finally {
+ materialSaving.value = false;
+ }
+};
+</script>
+
+<style scoped lang="scss">
+.material-toolbar {
+ margin-bottom: 12px;
+ text-align: right;
+}
+</style>
--
Gitblit v1.9.3