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/workOrderManagement/components/MaterialDialog.vue | 478 ++++++++++++++++++++++++++++++++++-------------------------
1 files changed, 274 insertions(+), 204 deletions(-)
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>
--
Gitblit v1.9.3