From 099e01e518b28fb5d55b414cf370e53117fdd699 Mon Sep 17 00:00:00 2001 From: panchengyong Date: Fri, 13 Mar 2026 11:11:37 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=94=9F=E4=BA=A7=E8=AE=A1?= =?UTF-8?q?=E5=88=92=E3=80=81=E9=94=80=E5=94=AE=E8=AE=A2=E5=8D=95=E3=80=81?= =?UTF-8?q?=E5=B7=A5=E4=BD=9C=E8=AE=A2=E5=8D=95=E5=92=8C=E4=BB=93=E5=BA=93?= =?UTF-8?q?=E5=8F=91=E6=96=99=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .DS_Store | Bin 14340 -> 14340 bytes erp-frontend-vue/src/api/mbom.ts | 17 ++ erp-frontend-vue/src/api/productionPlan.ts | 15 +- erp-frontend-vue/src/api/salesOrder.ts | 28 +-- .../src/views/Production/PlanOrder/form.vue | 167 ++++++++++++++---- .../src/views/Production/PlanOrder/index.vue | 31 ++-- .../src/views/Production/WorkOrder/form.vue | 166 +++++++++++------ .../src/views/Production/WorkOrder/index.vue | 16 +- .../src/views/Sales/Order/form.vue | 10 +- .../src/views/Sales/Order/index.vue | 8 +- .../src/views/Warehouse/Issue/form.vue | 40 ++++- .../src/views/Warehouse/Issue/index.vue | 12 +- .../mes/mp/controller/MpMbomController.java | 75 ++++++++ .../java/com/ktg/mes/mp/domain/MpPlan.java | 47 ++++- .../com/ktg/mes/mp/domain/MpPlanLine.java | 16 ++ .../mp/service/impl/MpPlanServiceImpl.java | 8 +- .../service/impl/ProWorkorderServiceImpl.java | 21 +++ .../mes/sl/controller/SlOrderController.java | 28 ++- .../ktg/mes/sl/service/ISlOrderService.java | 5 +- .../sl/service/impl/SlOrderServiceImpl.java | 10 +- .../resources/mapper/mp/MpPlanLineMapper.xml | 7 +- .../main/resources/mapper/mp/MpPlanMapper.xml | 58 +++--- .../mapper/pro/ProWorkorderMapper.xml | 2 +- .../resources/mapper/sl/SlOrderLineMapper.xml | 2 +- prd/.DS_Store | Bin 6148 -> 6148 bytes prd/issues-0311-3.md | 10 ++ prd/issues-0311-4.md | 17 ++ prd/issues-0311-5.md | 11 ++ prd/issues-0311-6.md | 12 ++ prd/issues-0311-7.md | 27 +++ 30 files changed, 681 insertions(+), 185 deletions(-) create mode 100644 prd/issues-0311-3.md create mode 100644 prd/issues-0311-4.md create mode 100644 prd/issues-0311-5.md create mode 100644 prd/issues-0311-6.md create mode 100644 prd/issues-0311-7.md diff --git a/.DS_Store b/.DS_Store index ec7d9ead3bdbd1f0fbae093e12d6238e08e40d56..1c3d3aa26eaa3bc035fb2a1e8aaa5e61186e5902 100644 GIT binary patch delta 38 ucmZoEXepTB&nUMsU^hP_&tx8f8=NV{$vH{+`8kZ6ZwbipZ{}9`DGmS#ehpdx delta 31 ncmZoEXepTB&nUYwU^hP__hcS{8=Joii11H*AiSAd;iotNxwH$D diff --git a/erp-frontend-vue/src/api/mbom.ts b/erp-frontend-vue/src/api/mbom.ts index 166db31..9ba9091 100644 --- a/erp-frontend-vue/src/api/mbom.ts +++ b/erp-frontend-vue/src/api/mbom.ts @@ -140,3 +140,20 @@ export function revokeIssue(mbomId: number): Promise { export function calcBom(planId: number): Promise { return request.put(`/erp/mp/plan/bom-calculate/${planId}`) } + +// 齐套检查:查询物料清单的子件库存情况 +export interface StockCheckItem { + itemCode: string + itemName: string + unitName?: string + requiredQty: number + availableQty: number + shortage: number + isShortage: boolean +} + +export function checkMbomStock(mbomId: number): Promise { + return request.get(`/erp/mp/mbom/stock-check/${mbomId}`).then((res: any) => { + return res.data ?? res.rows ?? res ?? [] + }) +} diff --git a/erp-frontend-vue/src/api/productionPlan.ts b/erp-frontend-vue/src/api/productionPlan.ts index 0dc4dac..d72d18a 100644 --- a/erp-frontend-vue/src/api/productionPlan.ts +++ b/erp-frontend-vue/src/api/productionPlan.ts @@ -57,6 +57,12 @@ export interface ProductionPlan { slaveList?: PlanLine[] /** 部门名称(单据视图) */ deptName?: string + /** 客户ID(从关联销售订单带入) */ + clientId?: number + /** 客户编码(从关联销售订单带入) */ + clientCode?: string + /** 客户名称(从关联销售订单带入) */ + clientName?: string /** 物料清单(BOM运算结果,详情页只读) */ mbomList?: MbomLine[] /** 补料清单(详情页只读) */ @@ -187,6 +193,7 @@ export const PLAN_STATUS_MAP: Record = export const BUSINESS_STATUS_OPTIONS = [ { value: 'NORMAL', label: '正常' }, + { value: 'BOM_CALCULATED', label: '已运算' }, { value: 'PAUSE', label: '暂停' }, { value: 'CANCEL', label: '取消' } ] @@ -295,7 +302,7 @@ export function getImportOrderList(params?: { return request.get('/erp/sl/order/list', { params: { ...params, - orderStatus: '审核', + status: 'APPROVED', pageNum: 1, pageSize: 100 } @@ -306,9 +313,9 @@ export function getImportOrderList(params?: { orderId: row.orderId, orderCode: row.orderCode, orderDate: row.orderDate, - userId: row.salesmanId, - userName: row.salesmanName, - salesmanName: row.salesmanName, + userId: row.salesUserId ?? row.salesmanId, + userName: row.salesUserName ?? row.salesmanName, + salesmanName: row.salesUserName ?? row.salesmanName, clientName: row.clientName, deliveryDate: row.deliveryDate, deliveryStatus: row.deliveryDate && new Date(row.deliveryDate) < new Date() ? '超期' : '预计', diff --git a/erp-frontend-vue/src/api/salesOrder.ts b/erp-frontend-vue/src/api/salesOrder.ts index 11e3489..809ec0e 100644 --- a/erp-frontend-vue/src/api/salesOrder.ts +++ b/erp-frontend-vue/src/api/salesOrder.ts @@ -122,11 +122,12 @@ export interface SalesOrderDetailListResponse { // ============ 状态映射 ============ export const ORDER_STATUS_MAP: Record = { - 'DRAFT': { label: '草稿', type: 'info' }, - '开立': { label: '开立', type: '' }, - '审核': { label: '审核', type: 'success' }, - '退回': { label: '退回', type: 'warning' }, - '关闭': { label: '关闭', type: 'danger' } + 'DRAFT': { label: '草稿', type: 'info' }, + 'PREPARE': { label: '开立', type: '' }, + 'APPROVED': { label: '已审核', type: 'success' }, + 'REJECTED': { label: '退回', type: 'warning' }, + 'FINISHED': { label: '已完成', type: 'info' }, + 'CANCELED': { label: '关闭', type: 'danger' }, } export const SALES_TYPE_OPTIONS = [ @@ -162,9 +163,10 @@ const BASE = '/erp/sl/order' function normalizeOrderRow(row: any): any { return { ...row, + orderStatus: row.orderStatus ?? row.status ?? '', salesmanId: row.salesmanId ?? row.salesUserId, salesmanName: row.salesmanName ?? row.salesUserName ?? '', - salesType: row.salesType ?? row.saleType ?? '', + salesType: row.salesType ?? row.orderType ?? row.saleType ?? '', } } @@ -197,11 +199,13 @@ export function getSalesOrderDetail(orderId: number): Promise { // 后端返回 salesUserId/salesUserName,前端表单使用 salesmanId/salesmanName,便于下拉回显 if (data.salesUserId !== undefined) data.salesmanId = data.salesUserId if (data.salesUserName !== undefined) data.salesmanName = data.salesUserName - // 审核信息字段别名映射(后端可能使用 checkBy/checkDate 等命名) + // 审核信息字段别名映射 // 使用 == null 而非 ! 判断,避免 auditorId=0 或 auditorName='' 被误判为缺失 - if (data.auditorId == null) data.auditorId = data.checkUserId ?? data.checkById ?? data.auditById - if (data.auditorName == null) data.auditorName = data.checkUserName ?? data.checkByName ?? data.auditByName ?? data.auditUser - if (data.auditDate == null) data.auditDate = data.checkDate ?? data.auditTime ?? data.checkTime + if (data.auditorId == null) data.auditorId = data.approverId ?? data.checkUserId ?? data.checkById ?? data.auditById + if (data.auditorName == null) data.auditorName = data.approverName ?? data.checkUserName ?? data.checkByName ?? data.auditByName ?? data.auditUser + if (data.auditDate == null) data.auditDate = data.approveDate ?? data.checkDate ?? data.auditTime ?? data.checkTime + // 统一状态字段 + if (data.orderStatus == null) data.orderStatus = data.status ?? '' return data }) } @@ -216,6 +220,10 @@ function buildOrderPayload(data: Partial): Record { // 后端实体为 salesUserId / salesUserName,前端表单为 salesmanId / salesmanName,此处统一发给后端 if (payload.salesmanId !== undefined) payload.salesUserId = payload.salesmanId if (payload.salesmanName !== undefined) payload.salesUserName = payload.salesmanName + // 后端实体用 orderType 存储销售类型,前端表单用 salesType + if (payload.salesType !== undefined) payload.orderType = payload.salesType + // 前端用 orderStatus,后端实体字段为 status + if (payload.orderStatus !== undefined) payload.status = payload.orderStatus return payload } diff --git a/erp-frontend-vue/src/views/Production/PlanOrder/form.vue b/erp-frontend-vue/src/views/Production/PlanOrder/form.vue index d403962..258ea8b 100644 --- a/erp-frontend-vue/src/views/Production/PlanOrder/form.vue +++ b/erp-frontend-vue/src/views/Production/PlanOrder/form.vue @@ -208,6 +208,7 @@ {{ formatNumber(row.quantity) }} @@ -236,7 +236,6 @@ {{ row.qualityReq || '-' }} @@ -247,7 +246,6 @@ {{ row.remark || '-' }} @@ -291,17 +289,14 @@ {{ row.mbomCode || '—' }} - - - + + + + @@ -334,9 +329,9 @@ {{ formatNumber(row.productionQty) }} - + - - - @@ -400,7 +400,7 @@ > - + @@ -645,6 +645,46 @@ 关闭 + + + +
+ + + + + + + + + + + + + + + +
+ 暂无数据 +
+
+ +
@@ -669,7 +709,7 @@ import { type MbomLine, type ImportOrder } from '@/api/productionPlan' -import { calcBom, updateMbom, issueToWorkshop, revokeIssue, getMbomLines, type MbomLine as ApiMbomLine } from '@/api/mbom' +import { calcBom, updateMbom, issueToWorkshop, revokeIssue, getMbomLines, checkMbomStock, type MbomLine as ApiMbomLine, type StockCheckItem } from '@/api/mbom' import { listEbom, getEbom, type EbomHeader, type EbomLine } from '@/api/rd/ebom' import { listWorkshop, type Workshop } from '@/api/masterdata/workshop' @@ -767,6 +807,11 @@ const ebomViewData = ref(null) const ebomViewLines = ref([]) const ebomViewLoading = ref(false) +/** 齐套检查弹窗 */ +const showStockCheckDialog = ref(false) +const stockCheckData = ref([]) +const stockCheckLoading = ref(false) + /** 供应方式选项(生产/加工/装配/采购/委外) */ const SUPPLY_TYPE_OPTIONS = [ { value: 'SELF_MADE', label: '生产' }, @@ -877,8 +922,24 @@ async function handleBomCalculate() { } /** 物料清单 - 齐套检查(占位,后续对接接口) */ -function handleMbomStockCheck(_row: any) { - ElMessage.info('齐套检查功能待对接后端接口') +async function handleMbomStockCheck(row: any) { + if (!row.mbomId) { + ElMessage.warning('物料清单ID不存在') + return + } + + stockCheckLoading.value = true + showStockCheckDialog.value = true + stockCheckData.value = [] + + try { + const data = await checkMbomStock(row.mbomId) + stockCheckData.value = data + } catch (error: any) { + ElMessage.error('齐套检查失败:' + (error.message || '未知错误')) + } finally { + stockCheckLoading.value = false + } } /** 物料清单 - BOM补料(占位,后续对接接口) */ @@ -900,6 +961,29 @@ function getSupplyTypeLabel(supplyType?: string): string { return labelMap[supplyType] ?? supplyType } +/** MBOM 单据状态映射 */ +function getMbomStatusLabel(status?: string): string { + if (!status) return '—' + const labelMap: Record = { + 'PREPARE': '开立', + 'DRAFT': '开立', + 'APPROVED': '审核', + 'CLOSED': '关闭' + } + return labelMap[status] ?? status +} + +/** MBOM 业务类型映射 */ +function getBusinessTypeLabel(businessType?: string): string { + if (!businessType) return '—' + const labelMap: Record = { + 'BOM_CALC': 'BOM运算', + 'BOM_SUPPLEMENT': 'BOM补料', + 'PRODUCTION_SUPPLEMENT': '生产补料' + } + return labelMap[businessType] ?? businessType +} + /** 打开物料清单汇总弹窗(PRD 6.3.10:标题「计划单号-产品编码-产品名称-数量」- 物料清单,表格为MBOM明细) */ function openMbomSummaryDialog() { const planCode = formData.planCode || '计划单' @@ -1554,4 +1638,13 @@ onMounted(async () => { color: #909399; font-size: 12px; } + +.order-lines-table :deep(.el-table__body .el-table__cell) { + padding: 8px 0; +} + +.order-lines-table :deep(.el-input__wrapper), +.order-lines-table :deep(.el-input-number .el-input__wrapper) { + min-height: 36px; +} diff --git a/erp-frontend-vue/src/views/Production/PlanOrder/index.vue b/erp-frontend-vue/src/views/Production/PlanOrder/index.vue index ccd9104..1e271b0 100644 --- a/erp-frontend-vue/src/views/Production/PlanOrder/index.vue +++ b/erp-frontend-vue/src/views/Production/PlanOrder/index.vue @@ -214,6 +214,13 @@
+ + +
@@ -241,9 +248,9 @@ - - - + + + @@ -253,8 +260,12 @@ {{ getBusinessStatusLabel(row.businessStatus) }} - - + + + + - + @@ -156,18 +156,18 @@ - - + + - + - + @@ -178,7 +178,7 @@ {{ row.productionDate ? String(row.productionDate).slice(0, 10) : '-' }} - + @@ -601,7 +601,7 @@ const formData = reactive({ orderId: undefined, orderCode: '', orderDate: new Date().toISOString().split('T')[0], - orderStatus: '开立', + orderStatus: 'PREPARE', bizType: 'sales_order', bizStatus: 'normal', salesType: 'factory', @@ -688,13 +688,13 @@ const totalAmount = computed(() => { /** 是否可审核 */ const canAudit = computed(() => { return (isEdit.value || isView.value) && - formData.orderStatus === '开立' && + formData.orderStatus === 'PREPARE' && (formData.lines?.length || 0) > 0 }) /** 是否可反审核 */ const canUnaudit = computed(() => { - return (isEdit.value || isView.value) && formData.orderStatus === '审核' + return (isEdit.value || isView.value) && formData.orderStatus === 'APPROVED' }) // ============ 方法 ============ @@ -1093,7 +1093,7 @@ async function handleWithdraw() { } try { await ElMessageBox.confirm('确认撤回此订单吗?', '提示', { type: 'warning' }) - formData.orderStatus = '草稿' + formData.orderStatus = 'PREPARE' await updateSalesOrder(formData) ElMessage.success('撤回成功') loadOrderData() diff --git a/erp-frontend-vue/src/views/Sales/Order/index.vue b/erp-frontend-vue/src/views/Sales/Order/index.vue index 67b93ca..9017ef4 100644 --- a/erp-frontend-vue/src/views/Sales/Order/index.vue +++ b/erp-frontend-vue/src/views/Sales/Order/index.vue @@ -235,7 +235,7 @@ type="primary" link @click="handleEdit(row)" - :disabled="row.orderStatus === '审核'" + :disabled="row.orderStatus === 'APPROVED'" > 编辑 @@ -244,7 +244,7 @@ 删除 @@ -341,13 +341,13 @@ const salesTypeOptions = SALES_TYPE_OPTIONS /** 是否可以审核(选中了开立状态的订单) */ const canAudit = computed(() => { return selectedRows.value.length > 0 && - selectedRows.value.every(row => row.orderStatus === '开立') + selectedRows.value.every(row => row.orderStatus === 'PREPARE') }) /** 是否可以反审核(选中了审核状态的订单) */ const canUnaudit = computed(() => { return selectedRows.value.length > 0 && - selectedRows.value.every(row => row.orderStatus === '审核') + selectedRows.value.every(row => row.orderStatus === 'APPROVED') }) // ============ 方法 ============ diff --git a/erp-frontend-vue/src/views/Warehouse/Issue/form.vue b/erp-frontend-vue/src/views/Warehouse/Issue/form.vue index ec5b321..996a182 100644 --- a/erp-frontend-vue/src/views/Warehouse/Issue/form.vue +++ b/erp-frontend-vue/src/views/Warehouse/Issue/form.vue @@ -433,7 +433,9 @@ @@ -586,6 +588,7 @@ import { STATUS_MAP, type IssueHeader } from '@/api/warehouse/issue' +import { STATUS_MAP as WORKORDER_STATUS_MAP, getWorkOrderBomList } from '@/api/workOrder' import PrintDialog from '@/components/print/PrintDialog.vue' import type { PrintConfig } from '@/components/print/types' import { @@ -656,7 +659,7 @@ const formRules = { trigger: 'blur' } ], - issueName: [{ required: true, message: '领料单名称不能为空', trigger: 'blur' }] + issueName: [{ required: false }] } // ============ 领料行状态 ============ @@ -751,11 +754,28 @@ const selectedItem = ref(null) async function loadItems() { itemLoading.value = true try { - const res = await getItemSelectList({ - itemCode: itemSearch.itemCode || undefined, - itemName: itemSearch.itemName || undefined - }) - itemList.value = res.rows + if (formData.workorderId) { + const bomItems = await getWorkOrderBomList(formData.workorderId) + const keyword = (itemSearch.itemCode || itemSearch.itemName || '').toLowerCase() + const mapped = bomItems.map(b => ({ + itemId: b.itemId, + itemCode: b.itemCode || '', + itemName: b.itemName || '', + specification: b.itemSpc || '', + unitOfMeasure: b.unitOfMeasure || '', + unitName: b.unitName || '', + itemTypeName: '' + })) + itemList.value = keyword + ? mapped.filter(m => m.itemCode.toLowerCase().includes(keyword) || m.itemName.toLowerCase().includes(keyword)) + : mapped + } else { + const res = await getItemSelectList({ + itemCode: itemSearch.itemCode || undefined, + itemName: itemSearch.itemName || undefined + }) + itemList.value = res.rows + } } catch (error) { console.error('加载物料失败:', error) } finally { @@ -1188,6 +1208,12 @@ async function handleSave() { try { await formRef.value?.validate() + if (!formData.issueName?.trim()) { + const now = new Date() + const pad = (n: number) => String(n).padStart(2, '0') + formData.issueName = `领料单-${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}` + } + saveLoading.value = true // 保存表头 diff --git a/erp-frontend-vue/src/views/Warehouse/Issue/index.vue b/erp-frontend-vue/src/views/Warehouse/Issue/index.vue index 892df01..658c41a 100644 --- a/erp-frontend-vue/src/views/Warehouse/Issue/index.vue +++ b/erp-frontend-vue/src/views/Warehouse/Issue/index.vue @@ -127,24 +127,24 @@ - + - + - + - +