diff --git a/klp-flow/src/main/java/com/klp/flow/domain/bo/SchProdScheduleDetailBo.java b/klp-flow/src/main/java/com/klp/flow/domain/bo/SchProdScheduleDetailBo.java index fa17f70d2..2bc4f281c 100644 --- a/klp-flow/src/main/java/com/klp/flow/domain/bo/SchProdScheduleDetailBo.java +++ b/klp-flow/src/main/java/com/klp/flow/domain/bo/SchProdScheduleDetailBo.java @@ -1,8 +1,11 @@ package com.klp.flow.domain.bo; +import com.fasterxml.jackson.annotation.JsonFormat; import com.klp.common.core.domain.BaseEntity; import lombok.Data; import lombok.EqualsAndHashCode; +import org.springframework.format.annotation.DateTimeFormat; + import javax.validation.constraints.*; import java.math.BigDecimal; @@ -42,6 +45,8 @@ public class SchProdScheduleDetailBo extends BaseEntity { /** * 排产日期 */ + @JsonFormat(pattern = "yyyy-MM-dd") + @DateTimeFormat(pattern = "yyyy-MM-dd") private Date scheduleDate; /** diff --git a/klp-ui/src/api/aps/requirement.js b/klp-ui/src/api/aps/requirement.js index 183aeb8e1..d6fc3a476 100644 --- a/klp-ui/src/api/aps/requirement.js +++ b/klp-ui/src/api/aps/requirement.js @@ -82,6 +82,15 @@ export function delRequirementDetail(detailIds) { }) } +// 产需明细逐条下发到排产明细(sch_prod_schedule_item) +export function convertRequirementDetail(data) { + return request({ + url: '/flow/prodScheduleItem/receiveFromDetail', + method: 'post', + data + }) +} + // ====== 销售订单-产需单关联(SchSaleScheduleRel) ====== // 查询关联列表 diff --git a/klp-ui/src/views/wms/post/aps/order.vue b/klp-ui/src/views/wms/post/aps/order.vue index d4837993c..393bcaefd 100644 --- a/klp-ui/src/views/wms/post/aps/order.vue +++ b/klp-ui/src/views/wms/post/aps/order.vue @@ -53,11 +53,6 @@ :value="item.value" /> - - - - - 重置 @@ -96,7 +91,6 @@
签订地点: {{ item.signLocation || '-' }}
- {{ statusLabel(item.orderStatus) }}
- - - - - 重置 @@ -96,7 +91,6 @@
签订地点: {{ item.signLocation || '-' }}
- {{ statusLabel(item.orderStatus) }}
- + + + @@ -464,13 +555,17 @@ import { listRel, addRequirementDetail, updateRequirementDetail, - delRequirementDetail + delRequirementDetail, + convertRequirementDetail } from '@/api/aps/requirement' import { listCrmOrder } from '@/api/aps/order' +import { listProcess } from '@/api/aps/process' import { parseProductContent } from '@/utils/productContent' +import DragResizePanel from '@/components/DragResizePanel' export default { name: 'ApsRequirement', + components: { DragResizePanel }, data() { return { // 左侧列表 @@ -520,6 +615,16 @@ export default { scheduleWeight: [{ required: true, message: '排产吨数不能为空', trigger: 'change' }] }, + // 产需转化对话框 + convertDialogVisible: false, + convertDetailList: [], + convertBtnLoading: false, + convertReqNo: '', + convertProgress: 0, + convertProgressShow: false, + convertSelection: [], + processList: [], + // 新增/编辑对话框 dialogVisible: false, dialogTitle: '新增产需单', @@ -541,6 +646,16 @@ export default { const n = Number(item.scheduleWeight) return isNaN(n) ? sum : sum + n }, 0) + }, + convertAllValid() { + return this.convertDetailList.length > 0 && + this.convertDetailList.every(d => d.processId && d.scheduleDate) + }, + detailDispatchProgress() { + const total = this.requirementDetailList.length + if (total === 0) return { issued: 0, total: 0, percent: 0 } + const issued = this.requirementDetailList.filter(d => d.isIssued === 1 || d.isIssued === '1').length + return { issued, total, percent: Math.round((issued / total) * 100) } } }, created() { @@ -582,6 +697,9 @@ export default { spec: '', material: '', scheduleWeight: 0, + processId: '', + scheduleDate: '', + isIssued: 0, productType: '', remark: '' } @@ -592,18 +710,6 @@ export default { this.getList() }, - // 跳转到排产单页面(路由跳转) - handleJumpToSchedule(item) { - this.$router.push({ - path: '/aps/schedule', - query: { - prodDate: item.prodDate || '', - scheduleNo: item.scheduleNo || '', - scheduleStatus: item.scheduleStatus - } - }) - }, - statusBadgeType(status) { return this.statusBadgeMap[status] || 'gray' }, @@ -624,11 +730,22 @@ export default { handleReqClick(item) { this.currentReq = item this.detailLoading = true - getRequirement(item.scheduleId).then(res => { + Promise.all([ + getRequirement(item.scheduleId), + listProcess({ pageNum: 1, pageSize: 1000 }) + ]).then(([res, processRes]) => { if (res.data) { this.currentReq = res.data this.requirementDetailList = res.data.detailList || [] } + // 构建工序查找表 + this.processList = (processRes.rows || []).map(p => ({ + ...p, + _steps: (p.stepList && p.stepList.length > 0) + ? p.stepList.slice().sort((a, b) => (a.stepOrder || 0) - (b.stepOrder || 0)) + .map(s => s.stepName).join(' → ') + : '' + })) }).catch(() => { this.requirementDetailList = [] }).finally(() => { @@ -963,6 +1080,286 @@ export default { }).catch(() => { }) }, + handleRecall() { + this.$confirm('确认撤回该产需单吗?撤回后将恢复为草稿状态。', '提示', { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning' + }).then(() => { + updateRequirement({ + scheduleId: this.currentReq.scheduleId, + scheduleStatus: 0 + }).then(() => { + this.$modal.msgSuccess('撤回成功') + this.getList() + this.handleReqClick(this.currentReq) + }).catch(() => { + this.$modal.msgError('撤回失败') + }) + }).catch(() => { }) + }, + + // ====== 产需转化 ====== + openConvertDialogForReq(item) { + // 确保加载了该产需单的详情 + this.convertReqNo = item.scheduleNo || '' + this.convertProgressShow = false + this.convertProgress = 0 + const loadDetail = item.scheduleId === (this.currentReq && this.currentReq.scheduleId) + ? Promise.resolve(this.currentReq) + : getRequirement(item.scheduleId) + + // 同时加载工序列表和明细 + Promise.all([ + loadDetail, + listProcess({ pageNum: 1, pageSize: 1000 }) + ]).then(([res, processRes]) => { + const data = res.data || res + const detailList = (data.detailList || []).map(d => ({ ...d })) + // 构建工序查找表 + const processRows = processRes.rows || [] + const processMap = new Map() + processRows.forEach(p => { + processMap.set(p.processId, p) + }) + this.processList = processRows.map(p => ({ + ...p, + _steps: (p.stepList && p.stepList.length > 0) + ? p.stepList.slice().sort((a, b) => (a.stepOrder || 0) - (b.stepOrder || 0)) + .map(s => s.stepName).join(' → ') + : '' + })) + // 为已有 processId 的明细填充 _stepNames,默认 scheduleDate 为今天 + const today = this.getTodayStr() + detailList.forEach(d => { + if (!d.scheduleDate) { + d.scheduleDate = today + } + if (d.processId) { + this.fillProcessStepInfo(d) + } + }) + this.convertDetailList = detailList + this.convertDialogVisible = true + }).catch(() => { + this.convertDetailList = [] + this.convertDialogVisible = true + }) + }, + + fillProcessStepInfo(row) { + const pid = row.processId ? String(row.processId) : '' + if (!pid) return + const process = this.processList.find(p => String(p.processId) === pid) + if (process) { + const steps = (process.stepList || []).slice().sort((a, b) => (a.stepOrder || 0) - (b.stepOrder || 0)) + row._stepNames = steps.map(s => s.stepName).join(' → ') + row._stepList = steps + } else { + row._stepNames = '' + row._stepList = [] + } + }, + + onConvertProcessChange(row, val) { + if (!val) { + row._stepNames = '' + row._stepList = [] + return + } + this.fillProcessStepInfo(row) + }, + + getStepNamesByProcessId(processId) { + if (!processId) return '' + const pid = String(processId) + const process = this.processList.find(p => String(p.processId) === pid) + if (!process || !process.stepList || process.stepList.length === 0) return '' + return process.stepList.slice().sort((a, b) => (a.stepOrder || 0) - (b.stepOrder || 0)) + .map(s => s.stepName).join(' → ') + }, + + handleConvertSingle(row) { + if (!row.processId) { + this.$message.warning('请先选择工序') + return + } + if (!row.scheduleDate) { + this.$message.warning('请先填写排产日期') + return + } + this.$confirm(`确认下发明细「${row.spec} / ${row.material}」吗?`, '提示', { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning' + }).then(() => { + this.convertBtnLoading = true + // 先保存 processId、scheduleDate 并标记已下发 + updateRequirementDetail({ + scheduleDetailId: row.scheduleDetailId, + processId: row.processId, + scheduleDate: row.scheduleDate, + isIssued: 1 + }).then(() => { + // 构建下发 BO,逐条下发 + return convertRequirementDetail({ + detailList: [{ + scheduleDetailId: row.scheduleDetailId, + processId: row.processId || null, + stepList: (row._stepList || []).map(s => ({ + stepId: s.stepId, + stepOrder: s.stepOrder, + stepName: s.stepName + })) + }] + }) + }).then(() => { + this.$modal.msgSuccess('下发成功') + row.isIssued = 1 + this.refreshDetailList() + }).catch(() => { + this.$modal.msgError('下发失败') + }).finally(() => { + this.convertBtnLoading = false + }) + }).catch(() => { }) + }, + + confirmConvertAll() { + if (this.convertDetailList.length === 0) { + this.$message.warning('没有可下发的明细') + return + } + const invalidItems = this.convertDetailList.filter(d => !d.processId || !d.scheduleDate) + if (invalidItems.length > 0) { + this.$message.warning(`还有 ${invalidItems.length} 条明细未填写工序或排产日期,请完善后再下发`) + return + } + this.$confirm(`确认下发全部 ${this.convertDetailList.length} 条明细吗?`, '提示', { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning' + }).then(() => { + this.convertBtnLoading = true + this.convertProgressShow = true + this.convertProgress = 0 + const total = this.convertDetailList.length + let completed = 0 + let failed = 0 + + const processNext = (index) => { + if (index >= total) { + this.convertBtnLoading = false + this.refreshDetailList() + if (failed === 0) { + this.$modal.msgSuccess(`成功下发 ${completed} 条明细`) + } else if (completed === 0) { + this.$modal.msgError('下发失败,请重试') + } else { + this.$modal.msgWarning(`成功下发 ${completed} 条,${failed} 条失败`) + } + return + } + const row = this.convertDetailList[index] + updateRequirementDetail({ + scheduleDetailId: row.scheduleDetailId, + processId: row.processId, + scheduleDate: row.scheduleDate, + isIssued: 1 + }).then(() => { + return convertRequirementDetail({ + detailList: [{ + scheduleDetailId: row.scheduleDetailId, + processId: row.processId, + stepList: (row._stepList || []).map(s => ({ + stepId: s.stepId, + stepOrder: s.stepOrder, + stepName: s.stepName + })) + }] + }) + }).then(() => { + completed++ + row.isIssued = 1 + }).catch(() => { + failed++ + }).finally(() => { + this.convertProgress = Math.round(((completed + failed) / total) * 100) + processNext(index + 1) + }) + } + processNext(0) + }).catch(() => { }) + }, + + confirmConvertSelected() { + if (this.convertSelection.length === 0) { + this.$message.warning('请勾选要下发的明细') + return + } + const invalidItems = this.convertSelection.filter(d => !d.processId || !d.scheduleDate) + if (invalidItems.length > 0) { + this.$message.warning(`所选明细中有 ${invalidItems.length} 条未填写工序或排产日期,请完善后再下发`) + return + } + this.$confirm(`确认下发所选 ${this.convertSelection.length} 条明细吗?`, '提示', { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning' + }).then(() => { + this.convertBtnLoading = true + this.convertProgressShow = true + this.convertProgress = 0 + const list = [...this.convertSelection] + const total = list.length + let completed = 0 + let failed = 0 + + const processNext = (index) => { + if (index >= total) { + this.convertBtnLoading = false + this.refreshDetailList() + if (failed === 0) { + this.$modal.msgSuccess(`成功下发 ${completed} 条明细`) + } else if (completed === 0) { + this.$modal.msgError('下发失败,请重试') + } else { + this.$modal.msgWarning(`成功下发 ${completed} 条,${failed} 条失败`) + } + return + } + const row = list[index] + updateRequirementDetail({ + scheduleDetailId: row.scheduleDetailId, + processId: row.processId, + scheduleDate: row.scheduleDate, + isIssued: 1 + }).then(() => { + return convertRequirementDetail({ + detailList: [{ + scheduleDetailId: row.scheduleDetailId, + processId: row.processId, + stepList: (row._stepList || []).map(s => ({ + stepId: s.stepId, + stepOrder: s.stepOrder, + stepName: s.stepName + })) + }] + }) + }).then(() => { + completed++ + row.isIssued = 1 + }).catch(() => { + failed++ + }).finally(() => { + this.convertProgress = Math.round(((completed + failed) / total) * 100) + processNext(index + 1) + }) + } + processNext(0) + }).catch(() => { }) + }, + // ====== 排产明细新增/编辑/删除 ====== resetDetailForm() { this.detailForm = this.getEmptyDetailForm() @@ -1058,8 +1455,7 @@ export default { @import './scss/aps-theme.scss'; .aps-req-page { - height: 100%; - padding: 8px; + height: calc(100vh - 84px); box-sizing: border-box; background: $aps-silver-1; } @@ -1070,17 +1466,19 @@ export default { // ====== 左侧(参照 HTML 产需单列表) ====== .aps-req-left { - display: flex; - flex-direction: column; height: 100%; background: #fff; border: 1px solid $aps-silver-3; border-radius: 6px; box-sizing: border-box; - overflow: hidden; min-height: 0; } +.aps-req-scroll { + height: 100%; + overflow-y: auto; +} + .panel-header { background: $aps-silver-mid; padding: 8px 12px; @@ -1088,15 +1486,12 @@ export default { font-weight: 600; color: $aps-text; border-bottom: 1px solid $aps-border; - flex-shrink: 0; display: flex; align-items: center; justify-content: space-between; } .list-container { - flex: 1; - overflow-y: auto; min-height: 0; } @@ -1198,21 +1593,15 @@ export default { border: 1px solid $aps-silver-3; border-radius: 6px; box-sizing: border-box; - display: flex; - flex-direction: column; - overflow: hidden; min-height: 0; } .detail-panel { - flex: 1; - overflow-y: auto; padding: 16px; display: flex; flex-direction: column; gap: 16px; background: $aps-bg; - min-height: 0; } // ====== 右侧详情卡片 ====== diff --git a/klp-ui/src/views/wms/post/aps/schedule.vue b/klp-ui/src/views/wms/post/aps/schedule.vue index 1d3a432cd..5d26a1ae1 100644 --- a/klp-ui/src/views/wms/post/aps/schedule.vue +++ b/klp-ui/src/views/wms/post/aps/schedule.vue @@ -13,18 +13,17 @@ @change="handleDateChange" /> 查询 - +
- -
+
{{ sch.scheduleNo }} @@ -49,7 +47,6 @@
- @@ -131,7 +128,6 @@
-
规格 @@ -162,7 +158,6 @@
-
已接收产需单 @@ -283,10 +278,10 @@ {{ hasQueried ? '该日期暂无已接收产需单' : '请选择日期查询' }}
-
+
--> -
+
已排产明细
@@ -696,7 +691,7 @@ export default { const d = String(today.getDate()).padStart(2, '0') return { queryDate: `${y}-${m}-${d}`, - activeTab: 'pending', + activeTab: 'scheduled', loading: false, schLoading: false, hasQueried: false,