From 9840e565b021ca4ed199ea40d2c8b47bc9feeb3f Mon Sep 17 00:00:00 2001
From: gongchunyi <deslre0381@gmail.com>
Date: 星期四, 03 九月 2026 14:50:49 +0800
Subject: [PATCH] fix: 金额优化
---
src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue | 733 +++++++++++++++++++++++++++++++++++++------------------
1 files changed, 488 insertions(+), 245 deletions(-)
diff --git a/src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue b/src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue
index 9e1a852..ca25853 100644
--- a/src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue
+++ b/src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue
@@ -1,298 +1,541 @@
<template>
<div>
- <el-dialog v-model="dialogVisible" title="棰嗘枡鍙拌处" width="1200px" @close="handleClose">
+ <el-dialog v-model="dialogVisible"
+ title="棰嗘枡鍙拌处"
+ width="1450px"
+ @close="handleClose">
<div class="material-toolbar">
- <el-button type="primary" @click="handleAddMaterialRow">鏂板</el-button>
+ <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">
+ <el-table v-loading="materialTableLoading"
+ :data="materialTableData"
+ border
+ row-key="tempId">
+ <el-table-column label="宸ュ簭鍚嶇О"
+ min-width="100">
<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" />
+ <span v-if="row.bom === true">{{ row.operationName || "-" }}</span>
+ <el-select v-else
+ v-model="row.operationName"
+ placeholder="璇烽�夋嫨宸ュ簭"
+ clearable
+ filterable
+ style="width: 100%;"
+ @change="val => handleProcessNameChange(row, val)">
+ <el-option v-for="item in processOptions"
+ :key="item.technologyOperationId"
+ :label="item.name"
+ :value="item.name" />
</el-select>
</template>
</el-table-column>
- <el-table-column label="鍘熸枡鍚嶇О" min-width="160">
+ <el-table-column label="鍘熸枡鍚嶇О"
+ min-width="120">
<template #default="{ row }">
<span v-if="row.bom === true">{{ row.materialName || "-" }}</span>
- <el-button v-else type="primary" link @click="openMaterialProductSelect(row)">
+ <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">
+ <el-table-column label="鍘熸枡鍨嬪彿"
+ min-width="110">
<template #default="{ row }">
{{ row.materialModel || "-" }}
</template>
</el-table-column>
- <el-table-column label="闇�姹傛暟閲�" min-width="120">
+ <!-- 鎵瑰彿澶氶�� -->
+ <el-table-column min-width="180"
+ label="鎵瑰彿">
<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)"
- />
+ <el-select v-model="row.batchNo"
+ multiple
+ collapse-tags
+ collapse-tags-indicator
+ placeholder="璇烽�夋嫨鎵瑰彿"
+ style="width: 100%;">
+ <el-option v-for="item in row.batchNoList"
+ :key="item"
+ :label="item"
+ :value="item" />
+ </el-select>
</template>
</el-table-column>
- <el-table-column label="璁¢噺鍗曚綅" width="120">
+ <el-table-column label="闇�姹傛暟閲�"
+ min-width="95">
+ <template #default="{ row }">
+ <span v-if="row.bom === true">{{ stripTrailingZeros(row.demandedQuantity) ?? "-" }}</span>
+ <el-input-number v-else
+ v-model="row.demandedQuantity"
+ :min="0"
+ :step="1"
+ controls-position="right"
+ :formatter="value => stripTrailingZeros(value)"
+ :parser="value => parseFloat(value) || 0"
+ style="width: 100%;"
+ @change="val => handleRequiredQtyChange(row, val)" />
+ </template>
+ </el-table-column>
+ <el-table-column label="搴撳瓨浣欓噺"
+ min-width="90">
+ <template #default="{ row }">
+ <span v-if="row.stockQty === null">-</span>
+ <span v-else>{{ stripTrailingZeros(row.stockQty) }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="宸查鏁伴噺"
+ min-width="90">
+ <template #default="{ row }">
+ {{ stripTrailingZeros(row.pickedQty) ?? 0 }}
+ </template>
+ </el-table-column>
+ <el-table-column label="鍓╀綑鍙"
+ min-width="90">
+ <template #default="{ row }">
+ <span :class="{ 'remaining-empty': Number(row.remainingQty) <= 0 }">
+ {{ stripTrailingZeros(row.remainingQty) ?? 0 }}
+ </span>
+ </template>
+ </el-table-column>
+ <el-table-column label="璁¢噺鍗曚綅"
+ width="80">
<template #default="{ row }">
{{ row.unit || "-" }}
</template>
</el-table-column>
- <el-table-column label="棰嗙敤鏁伴噺" min-width="120">
+ <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%;"
- />
+ <el-input-number v-model="row.pickQty"
+ :min="0"
+ :step="1"
+ controls-position="right"
+ :formatter="value => stripTrailingZeros(value)"
+ :parser="value => parseFloat(value) || 0"
+ style="width: 100%;" />
</template>
</el-table-column>
- <el-table-column label="鎿嶄綔" width="90" fixed="right">
+ <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>
+ <el-button v-if="row.bom !== true || Number(row.pickQty) > 0"
+ 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 type="primary"
+ :loading="materialSaving"
+ :disabled="isSaveDisabled"
+ @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"
- />
+ <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";
+ 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 { getStockInventoryByModelId } from "@/api/inventoryManagement/stockInventory.js";
+ import {
+ listMaterialPickingDetail,
+ listMaterialPickingBom,
+ 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,
- });
- }
+ const props = defineProps({
+ modelValue: { type: Boolean, default: false },
+ orderRow: { type: Object, default: null },
});
- processOptions.value = Array.from(processMap.values());
-};
+ const emit = defineEmits(["update:modelValue", "saved"]);
-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));
+ 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 isSaveDisabled = computed(() => {
+ if (materialTableLoading.value) return true;
+ if (materialTableData.value.length === 0) return true;
+ // 鍙湁瀛樺湪鏈棰嗙敤鏁伴噺澶т簬0鐨勮鎵嶅厑璁镐繚瀛�
+ return !materialTableData.value.some(item => Number(item.pickQty) > 0);
+ });
+
+ const processOptions = ref([]);
+ const currentMaterialSelectRowIndex = ref(-1);
+ let materialTempId = 0;
+
+ const toNumber = val => {
+ const n = Number(val);
+ return Number.isFinite(n) ? n : 0;
+ };
+
+ // 淇濈暀6浣嶅皬鏁帮紝閬垮厤娴偣璇樊
+ const roundQuantity = val => Math.round(toNumber(val) * 1e6) / 1e6;
+
+ // 闇�姹傛暟閲忓垎缁勯敭锛氫笌鍚庣淇濇寔涓�鑷达紙宸ュ簭ID浼樺厛锛岀己澶辨椂鐢ㄥ伐搴忓悕绉帮級
+ const buildDemandKey = (technologyOperationId, operationName, productModelId) =>
+ `${technologyOperationId != null && technologyOperationId !== undefined ? "T" + technologyOperationId : "N" + (operationName || "")}#${productModelId ?? ""}`;
+
+ const stripTrailingZeros = val => {
+ if (val === null || val === undefined || val === "") return "0";
+ const str = String(val);
+ if (str.includes(".")) {
+ return parseFloat(str).toString();
+ }
+ return str;
+ };
+
+ const createMaterialRow = (row = {}) => {
+ const batchNoList = Array.isArray(row.batchNoList) ? row.batchNoList : [];
+ const pickedEntry = row.pickedEntry || null;
+ const pickedQty = pickedEntry ? roundQuantity(pickedEntry.pickedQty) : 0;
+ const returnedQty = pickedEntry ? roundQuantity(pickedEntry.returnedQty) : 0;
+ const demandedQuantity = roundQuantity(row.demandedQuantity ?? 0);
+ const remainingQty = Math.max(roundQuantity(demandedQuantity - pickedQty + returnedQty), 0);
+
+ return {
+ tempId: `temp_${++materialTempId}`,
+ id: null,
+ bom: row.bom === true,
+ technologyOperationId: row.technologyOperationId ?? null,
+ operationName: row.operationName || "",
+ materialModelId: row.materialModelId ?? null,
+ materialName: row.materialName || "",
+ materialModel: row.materialModel || "",
+ unit: row.unit || "",
+ demandedQuantity,
+ pickedQty,
+ returnedQty,
+ remainingQty,
+ pickQty: 0,
+ stockQty: null,
+ batchNo: [],
+ batchNoList,
+ };
+ };
+
+ 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.technologyOperationId;
+ const operationName = item.operationName;
+ if (!processId || !operationName) return;
+ const key = `${processId}_${operationName}`;
+ if (!processMap.has(key)) {
+ processMap.set(key, {
+ id: processId,
+ technologyOperationId: processId,
+ name: operationName,
+ });
+ }
+ });
+ processOptions.value = Array.from(processMap.values());
+ };
+
+ const loadMaterialData = async () => {
+ if (!props.orderRow?.id) return;
+ materialTableLoading.value = true;
+ materialTableData.value = [];
+ try {
+ await getProcessOptions();
+ // 鍚屾椂鑾峰彇BOM闇�姹傛暟鎹笌宸查鏂欐槑缁嗭紝鐢ㄤ簬璁$畻鈥滃凡棰嗘暟閲�/鍓╀綑鍙鈥�
+ const [bomRes, detailRes] = await Promise.all([
+ listMaterialPickingBom(props.orderRow.id),
+ listMaterialPickingDetail(props.orderRow.id),
+ ]);
+ const bomList = Array.isArray(bomRes?.data)
+ ? bomRes.data
+ : bomRes?.data?.records || [];
+ const detailList = Array.isArray(detailRes?.data)
+ ? detailRes.data
+ : detailRes?.data?.records || [];
+
+ // 鎸夆�滃伐搴�+鍘熸枡鈥濇眹鎬诲巻鍙查鏂欙細宸查鏁伴噺銆侀��鏂欐暟閲�
+ const pickedMap = new Map();
+ detailList.forEach(item => {
+ if (!item) return;
+ const key = buildDemandKey(
+ item.technologyOperationId,
+ item.operationName,
+ item.productModelId
+ );
+ const entry = pickedMap.get(key) || {
+ pickedQty: 0,
+ returnedQty: 0,
+ manualDemand: 0,
+ technologyOperationId: item.technologyOperationId,
+ operationName: item.operationName || "",
+ materialModelId: item.productModelId,
+ materialName: item.productName || "",
+ materialModel: item.model || "",
+ unit: item.unit || "",
+ batchNoList: Array.isArray(item.batchNoList)
+ ? item.batchNoList
+ : [],
+ };
+ entry.pickedQty = roundQuantity(entry.pickedQty + toNumber(item.pickQuantity));
+ entry.returnedQty = roundQuantity(entry.returnedQty + toNumber(item.returnQty));
+ // 鎵嬪伐鏂板琛岋紙闈濨OM锛夌殑闇�姹傛暟閲忎互棰嗘枡蹇収涓哄噯
+ if (item.bom !== true && item.demandedQuantity != null) {
+ entry.manualDemand = Math.max(
+ toNumber(entry.manualDemand),
+ toNumber(item.demandedQuantity)
+ );
+ }
+ pickedMap.set(key, entry);
+ });
+
+ const rows = [];
+ const bomKeySet = new Set();
+ // BOM琛岋細闇�姹傛暟閲忓彇BOM锛屽凡棰嗘暟閲忓彇鍘嗗彶棰嗘枡姹囨��
+ bomList.forEach(item => {
+ const materialModelId = item.productModelId ?? item.materialModelId;
+ const key = buildDemandKey(
+ item.technologyOperationId,
+ item.operationName,
+ materialModelId
+ );
+ bomKeySet.add(key);
+ rows.push(
+ createMaterialRow({
+ bom: true,
+ technologyOperationId: item.technologyOperationId,
+ operationName: item.operationName || "",
+ materialModelId,
+ materialName: item.productName || item.materialName || "",
+ materialModel: item.model || item.materialModel || "",
+ unit: item.unit || "",
+ demandedQuantity: item.demandedQuantity ?? item.requiredQty ?? 0,
+ pickedEntry: pickedMap.get(key),
+ batchNoList: item.batchNoList || [],
+ })
+ );
+ });
+ // 涓嶅湪BOM涓殑鍘嗗彶棰嗘枡锛堟墜宸ユ柊澧炶锛夛紝涔熷睍绀哄嚭鏉ユ敮鎸佺户缁鏂�
+ pickedMap.forEach((entry, key) => {
+ if (bomKeySet.has(key)) return;
+ rows.push(
+ createMaterialRow({
+ bom: false,
+ technologyOperationId: entry.technologyOperationId,
+ operationName: entry.operationName,
+ materialModelId: entry.materialModelId,
+ materialName: entry.materialName,
+ materialModel: entry.materialModel,
+ unit: entry.unit,
+ demandedQuantity: entry.manualDemand,
+ pickedEntry: entry,
+ batchNoList: entry.batchNoList,
+ })
+ );
+ });
+ // 鍏堟煡鍑哄簱瀛樹綑閲忓啀鍏ヨ〃锛屼繚璇侀娆℃墦寮�鍗冲睍绀猴紙rows 灏氭湭杞负鍝嶅簲寮忎唬鐞嗭紝
+ // 鍏ヨ〃鍚庡啀鏀瑰師濮嬪璞′笉浼氳Е鍙戣鍥炬洿鏂帮級
+ await loadStockQtyForRows(rows);
+ materialTableData.value = rows;
+ } finally {
+ materialTableLoading.value = false;
+ }
+ };
+
+ watch(
+ () => dialogVisible.value,
+ visible => {
+ if (visible) {
+ loadMaterialData();
+ }
+ }
+ );
+
+ const handleClose = () => {
+ materialTableData.value = [];
+ currentMaterialSelectRowIndex.value = -1;
+ };
+
+ // 鎸夊瀷鍙锋眹鎬绘墍鏈夋壒娆$殑鍘熸枡鍙敤搴撳瓨浣欓噺
+ const loadStockQtyForRows = async rows => {
+ const modelIds = [
+ ...new Set(rows.map(row => row.materialModelId).filter(Boolean)),
+ ];
+ const stockMap = new Map();
+ await Promise.all(
+ modelIds.map(async modelId => {
+ try {
+ const res = await getStockInventoryByModelId(modelId);
+ const list = Array.isArray(res?.data)
+ ? res.data
+ : res?.data?.records || res?.data?.rows || [];
+ const total = list.reduce(
+ (sum, item) => sum + toNumber(item?.qualitity ?? item?.quantity),
+ 0
+ );
+ stockMap.set(modelId, roundQuantity(total));
+ } catch (e) {
+ stockMap.set(modelId, 0);
+ }
+ })
+ );
+ rows.forEach(row => {
+ if (!row.materialModelId) {
+ row.stockQty = null;
+ return;
+ }
+ row.stockQty = stockMap.has(row.materialModelId)
+ ? stockMap.get(row.materialModelId)
+ : 0;
+ });
+ };
+
+ const handleAddMaterialRow = () => {
+ materialTableData.value.push(createMaterialRow());
+ };
+
+ const handleDeleteMaterialRow = index => {
+ materialTableData.value.splice(index, 1);
+ };
+
+ const handleProcessNameChange = (row, operationName) => {
+ const process = processOptions.value.find(
+ item => item.name === operationName
+ );
+ row.technologyOperationId = process?.technologyOperationId;
+ };
+
+ const handleRequiredQtyChange = (row, val) => {
+ const required = roundQuantity(val);
+ row.demandedQuantity = required;
+ row.remainingQty = Math.max(
+ roundQuantity(required - toNumber(row.pickedQty) + toNumber(row.returnedQty)),
+ 0
+ );
+ };
+
+ 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 || "";
+ row.batchNoList = Array.isArray(product.batchNoList) ? product.batchNoList : [];
+ row.batchNo = [];
+ currentMaterialSelectRowIndex.value = -1;
+ materialProductDialogVisible.value = false;
+ loadStockQtyForRows([row]);
+ };
+
+ const validateMaterialRows = () => {
+ if (materialTableData.value.length === 0) {
+ return { valid: false, message: "璇峰厛鏂板棰嗘枡鏁版嵁" };
+ }
+ const pickRows = materialTableData.value.filter(
+ item => toNumber(item.pickQty) > 0
+ );
+ if (pickRows.length === 0) {
+ return { valid: false, message: "璇疯嚦灏戝~鍐欎竴琛屽ぇ浜�0鐨勯鐢ㄦ暟閲�" };
+ }
+ for (let i = 0; i < materialTableData.value.length; i++) {
+ const item = materialTableData.value[i];
+ const rowNo = i + 1;
+ if (toNumber(item.pickQty) <= 0) continue;
+ if (item.bom !== true && (!item.operationName || !item.materialName)) {
+ return { valid: false, message: `绗�${rowNo}琛岀殑宸ュ簭鍚嶇О鍜屽師鏂欏悕绉颁负蹇呭~椤筦 };
+ }
+ if (!Array.isArray(item.batchNo) || item.batchNo.length === 0) {
+ return { valid: false, message: `绗�${rowNo}琛岃閫夋嫨鎵瑰彿` };
+ }
+ }
+ return { valid: true, message: "" };
+ };
+
+ const handleMaterialSave = async () => {
+ if (!props.orderRow?.id) return;
+ const validateResult = validateMaterialRows();
+ if (!validateResult.valid) {
+ ElMessage.warning(validateResult.message);
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;
- }
-};
+ materialSaving.value = true;
+ try {
+ // 姣忔淇濆瓨閮芥槸涓�娆$嫭绔嬬殑棰嗘枡锛氬彧鎻愪氦鏈棰嗙敤鏁伴噺澶т簬0鐨勮锛�
+ // 鍑哄簱鍙拌处鎸夋湰娆¢鏂欐暟閲忚璐︼紝鍘嗗彶棰嗘枡璁板綍淇濇寔涓嶅彉銆�
+ const pickRows = materialTableData.value.filter(
+ item => toNumber(item.pickQty) > 0
+ );
+ await saveMaterialPickingLedger({
+ productionOrderId: props.orderRow.id,
+ productionOrderPickDto: pickRows.map(item => ({
+ technologyOperationId: item.technologyOperationId,
+ operationName: item.operationName,
+ bom: item.bom === true,
+ productModelId: item.materialModelId,
+ demandedQuantity: item.demandedQuantity,
+ unit: item.unit,
+ pickQuantity: toNumber(item.pickQty),
+ batchNo: Array.isArray(item.batchNo)
+ ? item.batchNo.join(",")
+ : item.batchNo,
+ })),
+ });
-watch(
- () => dialogVisible.value,
- visible => {
- if (visible) {
- loadMaterialData();
+ ElMessage({ message: "棰嗘枡鎴愬姛", type: "success" });
+ emit("saved");
+ dialogVisible.value = false;
+ } finally {
+ materialSaving.value = false;
}
- }
-);
-
-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;
-}
+ .material-toolbar {
+ margin-bottom: 12px;
+ text-align: right;
+ }
+
+ .remaining-empty {
+ color: #f56c6c;
+ font-weight: 600;
+ }
</style>
--
Gitblit v1.9.3