<template>
|
<div>
|
<el-dialog v-model="dialogVisible" title="领料详情" width="1400px" @close="handleClose">
|
<el-table v-loading="materialDetailLoading" :data="materialDetailTableData" border row-key="id">
|
<el-table-column label="工序名称" prop="processName" min-width="180" />
|
<el-table-column label="原料名称" prop="materialName" min-width="160" />
|
<el-table-column label="原料型号" prop="materialModel" min-width="180" />
|
<el-table-column label="需求数量" prop="requiredQty" min-width="110" />
|
<el-table-column label="计量单位" prop="unit" width="100" />
|
<el-table-column label="领用数量" prop="pickQty" min-width="110" />
|
<el-table-column label="补料数量" min-width="120">
|
<template #default="{ row }">
|
<el-button type="primary" link @click="handleViewSupplementRecord(row)">
|
{{ row.supplementQty ?? 0 }}
|
</el-button>
|
</template>
|
</el-table-column>
|
<el-table-column label="退料数量" prop="returnQty" min-width="110" />
|
<el-table-column label="实际数量" prop="actualQty" min-width="110" />
|
</el-table>
|
<template #footer>
|
<span class="dialog-footer">
|
<el-button
|
type="warning"
|
:loading="materialReturnConfirming"
|
:disabled="!canOpenReturnSummary"
|
@click="openReturnSummaryDialog"
|
>
|
退料确认
|
</el-button>
|
<el-button @click="dialogVisible = false">取消</el-button>
|
</span>
|
</template>
|
</el-dialog>
|
|
<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>
|
<template #footer>
|
<span class="dialog-footer">
|
<el-button @click="supplementRecordDialogVisible = false">关闭</el-button>
|
</span>
|
</template>
|
</el-dialog>
|
|
<el-dialog v-model="returnSummaryDialogVisible" title="退料汇总确认" width="900px">
|
<el-table :data="returnSummaryList" border row-key="summaryKey">
|
<el-table-column label="原料名称" prop="materialName" min-width="180" />
|
<el-table-column label="原料型号" prop="materialModel" min-width="180" />
|
<el-table-column label="计量单位" prop="unit" min-width="100" />
|
<el-table-column label="退料汇总数量" prop="returnQtyTotal" min-width="140" />
|
</el-table>
|
|
<el-card class="approver-card" shadow="never">
|
<template #header>
|
<div class="card-header-wrapper">
|
<span class="card-title">审批人选择</span>
|
<el-button type="primary" size="small" @click="addApproverNode">新增节点</el-button>
|
</div>
|
</template>
|
<div class="approver-nodes-container">
|
<div v-for="(node, index) in approverNodes" :key="node.id" class="approver-node-item">
|
<div class="approver-node-label">
|
<span class="node-step">{{ index + 1 }}</span>
|
<span class="node-text">审批人</span>
|
</div>
|
<el-select v-model="node.userId" placeholder="选择人员" class="approver-select" clearable>
|
<el-option v-for="user in userList" :key="user.userId" :label="user.nickName" :value="user.userId" />
|
</el-select>
|
<el-button v-if="approverNodes.length > 1" type="danger" size="small" @click="removeApproverNode(index)">
|
删除
|
</el-button>
|
</div>
|
</div>
|
</el-card>
|
|
<template #footer>
|
<span class="dialog-footer">
|
<el-button type="primary" :loading="materialReturnConfirming" @click="handleReturnConfirm">确认提交</el-button>
|
<el-button @click="returnSummaryDialogVisible = false">取消</el-button>
|
</span>
|
</template>
|
</el-dialog>
|
</div>
|
</template>
|
|
<script setup>
|
import { computed, ref, watch } from "vue";
|
import { ElMessage } from "element-plus";
|
import { listMaterialPickingDetail, listMaterialSupplementRecord, confirmMaterialReturn } from "@/api/productionManagement/productionOrder.js";
|
import { userListNoPageByTenantId } from "@/api/system/user.js";
|
|
const props = defineProps({
|
modelValue: { type: Boolean, default: false },
|
orderRow: { type: Object, default: null },
|
});
|
const emit = defineEmits(["update:modelValue", "confirmed"]);
|
|
const dialogVisible = computed({
|
get: () => props.modelValue,
|
set: val => emit("update:modelValue", val),
|
});
|
|
const materialDetailLoading = ref(false);
|
const materialDetailTableData = ref([]);
|
const materialReturnConfirming = ref(false);
|
const supplementRecordDialogVisible = ref(false);
|
const supplementRecordLoading = ref(false);
|
const supplementRecordTableData = ref([]);
|
const returnSummaryDialogVisible = ref(false);
|
const returnSummaryList = ref([]);
|
const userList = ref([]);
|
const approverNodes = ref([{ id: Date.now(), userId: undefined }]);
|
const canOpenReturnSummary = computed(() =>
|
materialDetailTableData.value.some(item => Number(item.returnQty || 0) > 0)
|
);
|
|
const loadDetailList = async () => {
|
if (!props.orderRow?.id) return;
|
materialDetailLoading.value = true;
|
materialDetailTableData.value = [];
|
try {
|
const res = await listMaterialPickingDetail({ orderId: props.orderRow.id });
|
materialDetailTableData.value = res.data || [];
|
} finally {
|
materialDetailLoading.value = false;
|
}
|
};
|
|
watch(
|
() => dialogVisible.value,
|
visible => {
|
if (visible) {
|
loadDetailList();
|
}
|
}
|
);
|
|
const handleClose = () => {
|
materialDetailTableData.value = [];
|
};
|
|
const handleViewSupplementRecord = async row => {
|
if (!row?.id) return;
|
supplementRecordDialogVisible.value = true;
|
supplementRecordLoading.value = true;
|
supplementRecordTableData.value = [];
|
try {
|
const res = await listMaterialSupplementRecord({ materialDetailId: row.id });
|
supplementRecordTableData.value = res.data || [];
|
} finally {
|
supplementRecordLoading.value = false;
|
}
|
};
|
|
const buildReturnSummary = () => {
|
const map = new Map();
|
materialDetailTableData.value.forEach(item => {
|
const key = `${item.materialModelId || ""}_${item.materialName || ""}_${item.materialModel || ""}_${item.unit || ""}`;
|
const old = map.get(key) || {
|
summaryKey: key,
|
materialName: item.materialName || "",
|
materialModel: item.materialModel || "",
|
unit: item.unit || "",
|
returnQtyTotal: 0,
|
};
|
old.returnQtyTotal += Number(item.returnQty || 0);
|
map.set(key, old);
|
});
|
return Array.from(map.values());
|
};
|
|
const loadUserList = async () => {
|
if (userList.value.length > 0) return;
|
const res = await userListNoPageByTenantId();
|
userList.value = res.data || [];
|
};
|
|
const openReturnSummaryDialog = async () => {
|
if (!canOpenReturnSummary.value) {
|
ElMessage.warning("退料数量大于0时才能退料确认");
|
return;
|
}
|
returnSummaryList.value = buildReturnSummary();
|
approverNodes.value = [{ id: Date.now(), userId: undefined }];
|
await loadUserList();
|
returnSummaryDialogVisible.value = true;
|
};
|
|
const addApproverNode = () => {
|
approverNodes.value.push({ id: Date.now() + Math.random(), userId: undefined });
|
};
|
|
const removeApproverNode = index => {
|
approverNodes.value.splice(index, 1);
|
};
|
|
const handleReturnConfirm = async () => {
|
if (!props.orderRow?.id) return;
|
const approverList = approverNodes.value
|
.filter(item => item.userId)
|
.map((item, index) => ({ userId: item.userId, sort: index + 1 }));
|
if (approverList.length === 0) {
|
ElMessage.warning("请至少选择一位审批人");
|
return;
|
}
|
materialReturnConfirming.value = true;
|
try {
|
await confirmMaterialReturn({
|
orderId: props.orderRow.id,
|
returnSummaryList: returnSummaryList.value,
|
approverList,
|
});
|
returnSummaryDialogVisible.value = false;
|
dialogVisible.value = false;
|
emit("confirmed");
|
} finally {
|
materialReturnConfirming.value = false;
|
}
|
};
|
</script>
|
|
<style scoped lang="scss">
|
.approver-card {
|
margin-top: 12px;
|
}
|
.card-header-wrapper {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
}
|
.approver-nodes-container {
|
display: flex;
|
flex-direction: column;
|
gap: 8px;
|
}
|
.approver-node-item {
|
display: flex;
|
gap: 8px;
|
align-items: center;
|
}
|
.approver-node-label {
|
display: flex;
|
gap: 4px;
|
min-width: 88px;
|
align-items: center;
|
}
|
.node-step {
|
width: 20px;
|
height: 20px;
|
line-height: 20px;
|
text-align: center;
|
border-radius: 50%;
|
background: #409eff;
|
color: #fff;
|
font-size: 12px;
|
}
|
.approver-select {
|
flex: 1;
|
}
|
</style>
|