| src/views/productionManagement/productionOrder/components/MaterialDetailDialog.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/productionManagement/productionOrder/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/productionManagement/workOrderManagement/components/MaterialDialog.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/productionManagement/workOrderManagement/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
src/views/productionManagement/productionOrder/components/MaterialDetailDialog.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,264 @@ <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> src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,241 @@ <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 }"> <el-select v-model="row.processId" placeholder="è¯·éæ©å·¥åº" clearable filterable style="width: 100%;" @change="val => handleProcessChange(row, val)" > <el-option v-for="item in processOptions" :key="item.id" :label="item.name" :value="item.id" /> </el-select> </template> </el-table-column> <el-table-column label="åæåç§°" min-width="160"> <template #default="{ row }"> <el-button 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 }"> <el-input-number 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 }"> <el-button 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 /> </div> </template> <script setup> import { computed, ref, watch } from "vue"; import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue"; import { processList } from "@/api/productionManagement/productionProcess.js"; import { 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, processName: row.processName || "", 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 (processOptions.value.length > 0) return; const res = await processList({}); processOptions.value = res.data || []; }; const loadMaterialData = async () => { if (!props.orderRow?.id) return; materialTableLoading.value = true; materialTableData.value = []; await getProcessOptions(); try { const res = await listMaterialPickingLedger({ orderId: props.orderRow.id }); materialTableData.value = (res.data || []).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 handleProcessChange = (row, processId) => { const process = processOptions.value.find(item => item.id === processId); row.processName = process?.name || ""; }; 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.id; row.materialName = product.productName || ""; row.materialModel = product.model || ""; row.unit = product.unit || ""; currentMaterialSelectRowIndex.value = -1; materialProductDialogVisible.value = false; }; const validateMaterialRows = () => { if (materialTableData.value.length === 0) return false; return !materialTableData.value.find( item => !item.processId || !item.materialModelId || item.requiredQty === null || item.requiredQty === undefined || item.pickQty === null || item.pickQty === undefined ); }; const handleMaterialSave = async () => { if (!props.orderRow?.id || !validateMaterialRows()) return; materialSaving.value = true; try { await saveMaterialPickingLedger({ orderId: props.orderRow.id, items: materialTableData.value.map(item => ({ id: item.id, processId: item.processId, processName: item.processName, 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> src/views/productionManagement/productionOrder/index.vue
@@ -91,161 +91,16 @@ </template> </el-dialog> <el-dialog <MaterialLedgerDialog v-model="materialDialogVisible" title="颿å°è´¦" width="1200px" @close="handleMaterialDialogClose" > <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 }"> <el-select v-model="row.processId" placeholder="è¯·éæ©å·¥åº" clearable filterable style="width: 100%;" @change="val => handleProcessChange(row, val)" > <el-option v-for="item in processOptions" :key="item.id" :label="item.name" :value="item.id" /> </el-select> </template> </el-table-column> <el-table-column label="åæåç§°" min-width="160"> <template #default="{ row }"> <el-button 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 }"> <el-input-number 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 }"> <el-button 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="materialDialogVisible = false">åæ¶</el-button> </span> </template> </el-dialog> <ProductSelectDialog v-model="materialProductDialogVisible" @confirm="handleMaterialProductConfirm" single :order-row="currentMaterialOrder" @saved="getList" /> <el-dialog <MaterialDetailDialog v-model="materialDetailDialogVisible" title="é¢æè¯¦æ " width="1400px" @close="handleMaterialDetailDialogClose" > <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" @click="handleReturnConfirm"> éæç¡®è®¤ </el-button> <el-button @click="materialDetailDialogVisible = 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> :order-row="currentMaterialDetailOrder" @confirmed="getList" /> <new-product-order v-if="isShowNewModal" v-model:visible="isShowNewModal" @@ -258,20 +113,15 @@ import { ElMessageBox } from "element-plus"; import dayjs from "dayjs"; import { useRouter } from "vue-router"; import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue"; import { productOrderListPage, listProcessRoute, bindingRoute, listProcessBom, delProductOrder, listMaterialPickingLedger, saveMaterialPickingLedger, listMaterialPickingDetail, listMaterialSupplementRecord, confirmMaterialReturn, } from "@/api/productionManagement/productionOrder.js"; import { listMain as getOrderProcessRouteMain } from "@/api/productionManagement/productProcessRoute.js"; import { processList } from "@/api/productionManagement/productionProcess.js"; import MaterialLedgerDialog from "@/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue"; import MaterialDetailDialog from "@/views/productionManagement/productionOrder/components/MaterialDetailDialog.vue"; import PIMTable from "@/components/PIMTable/PIMTable.vue"; const NewProductOrder = defineAsyncComponent(() => import("@/views/productionManagement/productionOrder/New.vue")); @@ -454,35 +304,9 @@ routeId: null, }); const materialDialogVisible = ref(false); const materialProductDialogVisible = ref(false); const materialTableLoading = ref(false); const materialSaving = ref(false); const materialTableData = ref([]); const processOptions = ref([]); const currentMaterialOrder = ref(null); const currentMaterialSelectRowIndex = ref(-1); const materialDetailDialogVisible = ref(false); const materialDetailLoading = ref(false); const materialDetailTableData = ref([]); const materialReturnConfirming = ref(false); const currentMaterialDetailOrder = ref(null); const supplementRecordDialogVisible = ref(false); const supplementRecordLoading = ref(false); const supplementRecordTableData = ref([]); let materialTempId = 0; const createMaterialRow = (row = {}) => ({ tempId: row.id || `temp_${++materialTempId}`, id: row.id, processId: row.processId, processName: row.processName || "", 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 openBindRouteDialog = async row => { bindForm.orderId = row.id; @@ -528,199 +352,14 @@ } }; const getProcessOptions = async () => { if (processOptions.value.length > 0) return; try { const res = await processList({}); processOptions.value = res.data || []; } catch (e) { console.error("è·åå·¥åºå表失败ï¼", e); proxy.$modal.msgError("è·åå·¥åºå表失败"); } }; const openMaterialDialog = async row => { const openMaterialDialog = row => { currentMaterialOrder.value = row; materialDialogVisible.value = true; materialTableLoading.value = true; materialTableData.value = []; await getProcessOptions(); try { const res = await listMaterialPickingLedger({ orderId: row.id }); const list = res.data || []; materialTableData.value = list.map(item => createMaterialRow(item)); } catch (e) { console.error("è·å颿å°è´¦å¤±è´¥ï¼", e); proxy.$modal.msgError("è·å颿å°è´¦å¤±è´¥"); } finally { materialTableLoading.value = false; } }; const handleMaterialDialogClose = () => { materialTableData.value = []; currentMaterialOrder.value = null; currentMaterialSelectRowIndex.value = -1; }; const handleAddMaterialRow = () => { materialTableData.value.push(createMaterialRow()); }; const handleDeleteMaterialRow = index => { materialTableData.value.splice(index, 1); }; const handleProcessChange = (row, processId) => { const process = processOptions.value.find(item => item.id === processId); row.processName = process?.name || ""; }; 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.id; row.materialName = product.productName || ""; row.materialModel = product.model || ""; row.unit = product.unit || ""; currentMaterialSelectRowIndex.value = -1; materialProductDialogVisible.value = false; }; const validateMaterialRows = () => { if (materialTableData.value.length === 0) { proxy.$modal.msgWarning("请è³å°æ°å¢ä¸æ¡é¢æè®°å½"); return false; } const invalidRow = materialTableData.value.find( item => !item.processId || !item.materialModelId || item.requiredQty === null || item.requiredQty === undefined || item.pickQty === null || item.pickQty === undefined ); if (invalidRow) { proxy.$modal.msgWarning("请å®å颿å°è´¦å¿ å¡«åæ®µ"); return false; } return true; }; const handleMaterialSave = async () => { if (!currentMaterialOrder.value?.id) { proxy.$modal.msgWarning("æªè·åå°å½åç产订å"); return; } if (!validateMaterialRows()) return; materialSaving.value = true; try { await saveMaterialPickingLedger({ orderId: currentMaterialOrder.value.id, items: materialTableData.value.map(item => ({ id: item.id, processId: item.processId, processName: item.processName, materialModelId: item.materialModelId, materialName: item.materialName, materialModel: item.materialModel, requiredQty: item.requiredQty, unit: item.unit, pickQty: item.pickQty, })), }); proxy.$modal.msgSuccess("ä¿åæå"); materialDialogVisible.value = false; } catch (e) { console.error("ä¿å颿å°è´¦å¤±è´¥ï¼", e); proxy.$modal.msgError("ä¿å颿å°è´¦å¤±è´¥"); } finally { materialSaving.value = false; } }; const openMaterialDetailDialog = async row => { currentMaterialDetailOrder.value = row; materialDetailDialogVisible.value = true; materialDetailLoading.value = true; materialDetailTableData.value = []; try { const res = await listMaterialPickingDetail({ orderId: row.id }); materialDetailTableData.value = res.data || []; } catch (e) { console.error("è·åé¢æè¯¦æ 失败ï¼", e); proxy.$modal.msgError("è·åé¢æè¯¦æ 失败"); } finally { materialDetailLoading.value = false; } }; const handleMaterialDetailDialogClose = () => { materialDetailTableData.value = []; currentMaterialDetailOrder.value = null; }; const handleViewSupplementRecord = async row => { if (!row?.id) { proxy.$modal.msgWarning("缺å°é¢ææç»IDï¼æ æ³æ¥çè¡¥æè®°å½"); return; } supplementRecordDialogVisible.value = true; supplementRecordLoading.value = true; supplementRecordTableData.value = []; try { const res = await listMaterialSupplementRecord({ materialDetailId: row.id }); supplementRecordTableData.value = res.data || []; } catch (e) { console.error("è·åè¡¥æè®°å½å¤±è´¥ï¼", e); proxy.$modal.msgError("è·åè¡¥æè®°å½å¤±è´¥"); } finally { supplementRecordLoading.value = false; } }; const handleReturnConfirm = async () => { if (!currentMaterialDetailOrder.value?.id) { proxy.$modal.msgWarning("æªè·åå°å½åç产订å"); return; } try { await ElMessageBox.confirm("确认æ§è¡éæç¡®è®¤ï¼", "æç¤º", { confirmButtonText: "确认", cancelButtonText: "åæ¶", type: "warning", }); } catch (e) { return; } materialReturnConfirming.value = true; try { await confirmMaterialReturn({ orderId: currentMaterialDetailOrder.value.id }); proxy.$modal.msgSuccess("éæç¡®è®¤æå"); openMaterialDetailDialog(currentMaterialDetailOrder.value); } catch (e) { console.error("éæç¡®è®¤å¤±è´¥ï¼", e); proxy.$modal.msgError("éæç¡®è®¤å¤±è´¥"); } finally { materialReturnConfirming.value = false; } }; // æ¥è¯¢å表 @@ -875,8 +514,4 @@ margin-top: unset; } .material-toolbar { margin-bottom: 12px; text-align: right; } </style> src/views/productionManagement/workOrderManagement/components/MaterialDialog.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,296 @@ <template> <div> <el-dialog v-model="dialogVisible" title="ç©æ" width="1200px" @close="handleCloseMaterialDialog" > <el-table v-loading="materialTableLoading" :data="materialTableData" border row-key="id"> <el-table-column label="å·¥åºåç§°" prop="processName" min-width="140" /> <el-table-column label="åæåç§°" prop="materialName" min-width="140" /> <el-table-column label="åæåå·" prop="materialModel" 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="éææ°é" prop="returnQty" min-width="100" /> <el-table-column label="å®é æ°é" prop="actualQty" min-width="100" /> <el-table-column label="æä½" align="center" fixed="right" width="220"> <template #default="{ row }"> <el-button type="primary" link @click="openSupplementDialog(row)">è¡¥æ</el-button> <el-button type="warning" link @click="openReturnDialog(row)">éæ</el-button> <el-button type="info" link @click="openSupplementRecordDialog(row)">è¡¥æè®°å½</el-button> </template> </el-table-column> </el-table> </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> <FormDialog v-model="returnDialogVisible" title="éæ" width="500px" @confirm="handleSubmitReturn" > <el-form ref="returnFormRef" :model="returnForm" :rules="returnRules" label-width="120px"> <el-form-item label="éææ°é" prop="returnQty"> <el-input-number v-model="returnForm.returnQty" :min="0.001" :precision="3" :step="1" style="width: 100%;" /> </el-form-item> </el-form> <template #footer> <span class="dialog-footer"> <el-button type="primary" :loading="returnSubmitting" @click="handleSubmitReturn">ç¡®å®</el-button> <el-button @click="returnDialogVisible = 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> </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 { listWorkOrderMaterialLedger, addWorkOrderMaterialSupplement, addWorkOrderMaterialReturn, listWorkOrderMaterialSupplementRecord, } from "@/api/productionManagement/workOrder.js"; const props = defineProps({ modelValue: { type: Boolean, default: false, }, rowData: { type: Object, default: () => null, }, }); const emit = defineEmits(["update:modelValue", "refresh"]); const dialogVisible = computed({ get: () => props.modelValue, set: val => emit("update:modelValue", val), }); const materialTableLoading = ref(false); const materialTableData = ref([]); const currentMaterialRow = ref(null); const currentMaterialOrderRow = ref(null); const supplementDialogVisible = ref(false); const supplementSubmitting = ref(false); const supplementFormRef = ref(null); const supplementForm = reactive({ supplementQty: null, supplementReason: "", }); const returnDialogVisible = ref(false); const returnSubmitting = ref(false); const returnFormRef = ref(null); const returnForm = reactive({ returnQty: null, }); const supplementRecordDialogVisible = ref(false); const supplementRecordLoading = ref(false); const supplementRecordTableData = ref([]); const supplementRules = { supplementQty: [{ required: true, message: "请è¾å ¥è¡¥ææ°é", trigger: "blur" }], supplementReason: [{ required: true, message: "请è¾å ¥è¡¥æåå ", trigger: "blur" }], }; const returnRules = { returnQty: [{ required: true, message: "请è¾å ¥éææ°é", trigger: "blur" }], }; const loadMaterialTable = async row => { if (!row?.id) return; currentMaterialOrderRow.value = row; materialTableLoading.value = true; materialTableData.value = []; try { const res = await listWorkOrderMaterialLedger({ workOrderId: row.id, processId: row.processId, productProcessRouteItemId: row.productProcessRouteItemId, }); materialTableData.value = res.data || []; } catch (e) { console.error("è·åç©æå°è´¦å¤±è´¥", e); ElMessage.error("è·åç©æå°è´¦å¤±è´¥"); } finally { materialTableLoading.value = false; } }; watch( () => props.modelValue, visible => { if (visible && props.rowData) { loadMaterialTable(props.rowData); } } ); const handleCloseMaterialDialog = () => { materialTableData.value = []; currentMaterialRow.value = null; currentMaterialOrderRow.value = null; }; const openSupplementDialog = row => { currentMaterialRow.value = row; supplementForm.supplementQty = null; supplementForm.supplementReason = ""; supplementDialogVisible.value = true; nextTick(() => { supplementFormRef.value?.clearValidate(); }); }; 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 openReturnDialog = row => { currentMaterialRow.value = row; returnForm.returnQty = null; returnDialogVisible.value = true; nextTick(() => { returnFormRef.value?.clearValidate(); }); }; const handleSubmitReturn = () => { returnFormRef.value?.validate(async valid => { if (!valid || !currentMaterialRow.value?.id) { ElMessage.warning("缺å°ç©ææç»ID"); return; } const returnQty = Number(returnForm.returnQty); const minQty = Number(currentMaterialRow.value.pickQty || 0) + Number(currentMaterialRow.value.supplementQty || 0); if (returnQty < minQty) { ElMessage.warning(`éææ°éä¸è½ä½äºé¢ç¨æ°é+è¡¥ææ°éï¼${minQty}ï¼`); return; } returnSubmitting.value = true; try { await addWorkOrderMaterialReturn({ materialLedgerId: currentMaterialRow.value.id, returnQty, workOrderId: currentMaterialOrderRow.value?.id, }); returnDialogVisible.value = false; await loadMaterialTable(currentMaterialOrderRow.value); ElMessage.success("éææå"); emit("refresh"); } catch (e) { console.error("éæå¤±è´¥", e); ElMessage.error("éæå¤±è´¥"); } finally { returnSubmitting.value = false; } }); }; 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; } }; </script> src/views/productionManagement/workOrderManagement/index.vue
@@ -164,146 +164,11 @@ </template> </el-dialog> <el-dialog v-model="materialDialogVisible" title="ç©æ" width="1200px" @close="handleCloseMaterialDialog"> <el-table v-loading="materialTableLoading" :data="materialTableData" border row-key="id"> <el-table-column label="å·¥åºåç§°" prop="processName" min-width="140" /> <el-table-column label="åæåç§°" prop="materialName" min-width="140" /> <el-table-column label="åæåå·" prop="materialModel" 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="éææ°é" prop="returnQty" min-width="100" /> <el-table-column label="å®é æ°é" prop="actualQty" min-width="100" /> <el-table-column label="æä½" align="center" fixed="right" width="220"> <template #default="{ row }"> <el-button type="primary" link @click="openSupplementDialog(row)">è¡¥æ</el-button> <el-button type="warning" link @click="openReturnDialog(row)">éæ</el-button> <el-button type="info" link @click="openSupplementRecordDialog(row)">è¡¥æè®°å½</el-button> </template> </el-table-column> </el-table> </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> <FormDialog v-model="returnDialogVisible" title="éæ" width="500px" @confirm="handleSubmitReturn"> <el-form ref="returnFormRef" :model="returnForm" :rules="returnRules" label-width="120px"> <el-form-item label="éææ°é" prop="returnQty"> <el-input-number v-model="returnForm.returnQty" :min="0.001" :precision="3" :step="1" style="width: 100%;" /> </el-form-item> </el-form> <template #footer> <span class="dialog-footer"> <el-button type="primary" :loading="returnSubmitting" @click="handleSubmitReturn">ç¡®å®</el-button> <el-button @click="returnDialogVisible = 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> <MaterialDialog v-model="materialDialogVisible" :row-data="currentMaterialOrderRow" @refresh="getList" /> <FilesDia ref="workOrderFilesRef" /> </div> @@ -317,16 +182,12 @@ productWorkOrderPage, addProductMain, downProductWorkOrder, listWorkOrderMaterialLedger, addWorkOrderMaterialSupplement, addWorkOrderMaterialReturn, listWorkOrderMaterialSupplementRecord, } from "@/api/productionManagement/workOrder.js"; import { getUserProfile, userListNoPageByTenantId } from "@/api/system/user.js"; import QRCode from "qrcode"; import { getCurrentInstance, reactive, toRefs } from "vue"; import FilesDia from "./components/filesDia.vue"; import FormDialog from "@/components/Dialog/FormDialog.vue"; import MaterialDialog from "./components/MaterialDialog.vue"; const { proxy } = getCurrentInstance(); const tableColumn = ref([ @@ -492,13 +353,6 @@ quantity: [{ required: true, validator: validateQuantity, trigger: "blur" }], scrapQty: [{ validator: validateScrapQty, trigger: "blur" }], }; const supplementRules = { supplementQty: [{ required: true, message: "请è¾å ¥è¡¥ææ°é", trigger: "blur" }], supplementReason: [{ required: true, message: "请è¾å ¥è¡¥æåå ", trigger: "blur" }], }; const returnRules = { returnQty: [{ required: true, message: "请è¾å ¥éææ°é", trigger: "blur" }], }; // å¤çæ¬æ¬¡ç产æ°éè¾å ¥ï¼éå¶å¿ 须大äºçäº1 const handleQuantityInput = value => { @@ -556,26 +410,7 @@ const currentReportRowData = ref(null); const materialDialogVisible = ref(false); const materialTableLoading = ref(false); const materialTableData = ref([]); const currentMaterialRow = ref(null); const currentMaterialOrderRow = ref(null); const supplementDialogVisible = ref(false); const supplementSubmitting = ref(false); const supplementFormRef = ref(null); const supplementForm = reactive({ supplementQty: null, supplementReason: "", }); const returnDialogVisible = ref(false); const returnSubmitting = ref(false); const returnFormRef = ref(null); const returnForm = reactive({ returnQty: null, }); const supplementRecordDialogVisible = ref(false); const supplementRecordLoading = ref(false); const supplementRecordTableData = ref([]); const page = reactive({ current: 1, size: 100, @@ -727,127 +562,9 @@ reportDialogVisible.value = true; }; const openMaterialDialog = async row => { const openMaterialDialog = row => { currentMaterialOrderRow.value = row; materialDialogVisible.value = true; materialTableLoading.value = true; materialTableData.value = []; try { const res = await listWorkOrderMaterialLedger({ workOrderId: row.id, processId: row.processId, productProcessRouteItemId: row.productProcessRouteItemId, }); materialTableData.value = res.data || []; } catch (e) { console.error("è·åç©æå°è´¦å¤±è´¥", e); proxy.$modal.msgError("è·åç©æå°è´¦å¤±è´¥"); } finally { materialTableLoading.value = false; } }; const handleCloseMaterialDialog = () => { materialTableData.value = []; currentMaterialRow.value = null; currentMaterialOrderRow.value = null; }; const openSupplementDialog = row => { currentMaterialRow.value = row; supplementForm.supplementQty = null; supplementForm.supplementReason = ""; supplementDialogVisible.value = true; nextTick(() => { supplementFormRef.value?.clearValidate(); }); }; const handleSubmitSupplement = () => { supplementFormRef.value?.validate(async valid => { if (!valid) return; if (!currentMaterialRow.value?.id) { proxy.$modal.msgWarning("缺å°ç©ææç»ID"); return; } supplementSubmitting.value = true; try { await addWorkOrderMaterialSupplement({ materialLedgerId: currentMaterialRow.value.id, supplementQty: Number(supplementForm.supplementQty), supplementReason: supplementForm.supplementReason, workOrderId: currentMaterialOrderRow.value?.id, }); proxy.$modal.msgSuccess("è¡¥ææå"); supplementDialogVisible.value = false; await openMaterialDialog(currentMaterialOrderRow.value); } catch (e) { console.error("è¡¥æå¤±è´¥", e); proxy.$modal.msgError("è¡¥æå¤±è´¥"); } finally { supplementSubmitting.value = false; } }); }; const openReturnDialog = row => { currentMaterialRow.value = row; returnForm.returnQty = null; returnDialogVisible.value = true; nextTick(() => { returnFormRef.value?.clearValidate(); }); }; const handleSubmitReturn = () => { returnFormRef.value?.validate(async valid => { if (!valid) return; if (!currentMaterialRow.value?.id) { proxy.$modal.msgWarning("缺å°ç©ææç»ID"); return; } const returnQty = Number(returnForm.returnQty); const minQty = Number(currentMaterialRow.value.pickQty || 0) + Number(currentMaterialRow.value.supplementQty || 0); if (returnQty < minQty) { proxy.$modal.msgWarning(`éææ°éä¸è½ä½äºé¢ç¨æ°é+è¡¥ææ°éï¼${minQty}ï¼`); return; } returnSubmitting.value = true; try { await addWorkOrderMaterialReturn({ materialLedgerId: currentMaterialRow.value.id, returnQty, workOrderId: currentMaterialOrderRow.value?.id, }); proxy.$modal.msgSuccess("éææå"); returnDialogVisible.value = false; await openMaterialDialog(currentMaterialOrderRow.value); } catch (e) { console.error("éæå¤±è´¥", e); proxy.$modal.msgError("éæå¤±è´¥"); } finally { returnSubmitting.value = false; } }); }; 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); proxy.$modal.msgError("è·åè¡¥æè®°å½å¤±è´¥"); } finally { supplementRecordLoading.value = false; } }; const handleReport = () => {