<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>
|