From 19cfcf0d713d5dafbbed77700a70609d8274d343 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A0=82=E7=B3=96?= <2178503051@qq.com> Date: Mon, 29 Jun 2026 10:50:01 +0800 Subject: [PATCH 1/3] =?UTF-8?q?style(attendance):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E8=80=83=E5=8B=A4=E9=A1=B5=E9=9D=A2=E6=97=B6=E9=97=B4=E5=B1=95?= =?UTF-8?q?=E7=A4=BA=E5=92=8C=E9=BB=98=E8=AE=A4=E6=97=A5=E6=9C=9F=E8=8C=83?= =?UTF-8?q?=E5=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 调整考勤同步页面的创建时间展示格式,补充时分秒信息 2. 修改考勤异常页面的默认日期范围,改为昨日至当前时间 --- .../src/views/wms/hrm/attendance/attendanceAbnormal.vue | 8 ++++---- klp-ui/src/views/wms/hrm/attendance/sync.vue | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/klp-ui/src/views/wms/hrm/attendance/attendanceAbnormal.vue b/klp-ui/src/views/wms/hrm/attendance/attendanceAbnormal.vue index 861a3758b..cdfee5d3c 100644 --- a/klp-ui/src/views/wms/hrm/attendance/attendanceAbnormal.vue +++ b/klp-ui/src/views/wms/hrm/attendance/attendanceAbnormal.vue @@ -418,11 +418,11 @@ export default { methods: { initDateRange() { const now = new Date() - const firstDay = new Date(now.getFullYear(), now.getMonth(), 1) - const lastDay = new Date(now.getFullYear(), now.getMonth() + 1, 0) + const yesterday = new Date(now) + yesterday.setDate(yesterday.getDate() - 1) - this.defaultStartTime = this.formatDate(firstDay) - this.defaultEndTime = this.formatDate(lastDay) + this.defaultStartTime = this.formatDate(yesterday) + this.defaultEndTime = this.formatDate(now) this.dateRangeParams = { startDate: this.defaultStartTime, diff --git a/klp-ui/src/views/wms/hrm/attendance/sync.vue b/klp-ui/src/views/wms/hrm/attendance/sync.vue index ec31b126a..bd66c16d9 100644 --- a/klp-ui/src/views/wms/hrm/attendance/sync.vue +++ b/klp-ui/src/views/wms/hrm/attendance/sync.vue @@ -93,7 +93,7 @@ From da01bfaa4822481a70f18aed48333add4e306366 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A0=82=E7=B3=96?= <2178503051@qq.com> Date: Mon, 29 Jun 2026 14:58:24 +0800 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90=E5=94=AE?= =?UTF-8?q?=E5=90=8E=E6=8A=95=E8=AF=89=E6=B5=81=E7=A8=8B=E5=85=A8=E9=93=BE?= =?UTF-8?q?=E8=B7=AF=E4=BC=98=E5=8C=96=E4=B8=8E=E5=8A=9F=E8=83=BD=E5=AE=8C?= =?UTF-8?q?=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 本次提交完成了多项核心优化与功能新增: 1. 重构投诉受理流程状态,移除冗余的执行反馈阶段,简化为4个流程节点 2. 新增售后单关联客户、下游用户、工程信息等扩展字段,完善基础信息采集 3. 新增部门结构化意见表单与预览组件,优化部门意见填写与展示流程 4. 新增售后意见汇总页面,支持流程阶段筛选与详情查看 5. 优化合同列表页面,新增重置筛选按钮与默认备注逻辑 6. 新增PDF导出功能,完善钢卷信息展示列 7. 修复逻辑删除级联问题,新增任务过滤逻辑保障数据一致性 --- .../klp/flow/domain/TsComplaintAccept.java | 26 +- .../flow/domain/bo/TsComplaintAcceptBo.java | 32 +- .../flow/domain/vo/TsComplaintAcceptVo.java | 38 +- .../impl/TsComplaintAcceptServiceImpl.java | 16 +- .../impl/TsComplaintTaskServiceImpl.java | 8 + .../mapper/flow/TsComplaintAcceptMapper.xml | 6 + .../crm/contract/components/ContractList.vue | 2 + .../contract/components/ProductContent.vue | 2 +- klp-ui/src/views/crm/contract/index.vue | 3 +- .../objection/components/BasicInfoSection.vue | 112 +++- .../objection/components/CoilInfoSection.vue | 9 +- .../components/DepartmentOpinionSection.vue | 21 +- .../objection/components/DeptOpinionForm.vue | 146 ++++ .../components/DeptOpinionPreview.vue | 137 ++++ .../objection/components/ExportPdfDialog.vue | 621 ++++++++++++++++++ .../components/FlowOverviewSection.vue | 18 +- .../components/HeaderControlSection.vue | 10 +- klp-ui/src/views/wms/post/objection/index.vue | 68 +- .../src/views/wms/post/objection/opinion.vue | 43 +- .../src/views/wms/post/objection/summary.vue | 464 +++++++++++++ klp-ui/src/views/wms/post/objection/todo.vue | 6 +- 21 files changed, 1723 insertions(+), 65 deletions(-) create mode 100644 klp-ui/src/views/wms/post/objection/components/DeptOpinionForm.vue create mode 100644 klp-ui/src/views/wms/post/objection/components/DeptOpinionPreview.vue create mode 100644 klp-ui/src/views/wms/post/objection/components/ExportPdfDialog.vue create mode 100644 klp-ui/src/views/wms/post/objection/summary.vue diff --git a/klp-flow/src/main/java/com/klp/flow/domain/TsComplaintAccept.java b/klp-flow/src/main/java/com/klp/flow/domain/TsComplaintAccept.java index b7fa8a958..52d083f92 100644 --- a/klp-flow/src/main/java/com/klp/flow/domain/TsComplaintAccept.java +++ b/klp-flow/src/main/java/com/klp/flow/domain/TsComplaintAccept.java @@ -42,6 +42,30 @@ public class TsComplaintAccept extends BaseEntity { * 客户诉求 */ private String customerDemand; + /** + * 关联客户ID + */ + private Long customerId; + /** + * 下游使用用户名称 + */ + private String downstreamUserName; + /** + * 电话 + */ + private String phone; + /** + * 使用的工程名称 + */ + private String projectName; + /** + * 工程使用地点 + */ + private String projectLocation; + /** + * 产品用途 + */ + private String productUsage; /** * 客户照片凭证等 */ @@ -64,7 +88,7 @@ public class TsComplaintAccept extends BaseEntity { private Date auditTime; /** * 流程阶段: -1=待审核 2=部门意见填写中 3=待总负责人汇总方案 4=方案下发执行反馈中 5=全部办结 +1=待审核 2=部门意见填写中 3=待总负责人汇总方案 4=全部办结 */ private Long flowStatus; /** diff --git a/klp-flow/src/main/java/com/klp/flow/domain/bo/TsComplaintAcceptBo.java b/klp-flow/src/main/java/com/klp/flow/domain/bo/TsComplaintAcceptBo.java index 65f1772b4..1723c13cc 100644 --- a/klp-flow/src/main/java/com/klp/flow/domain/bo/TsComplaintAcceptBo.java +++ b/klp-flow/src/main/java/com/klp/flow/domain/bo/TsComplaintAcceptBo.java @@ -48,6 +48,36 @@ public class TsComplaintAcceptBo extends BaseEntity { */ private String customerDemand; + /** + * 关联客户ID + */ + private Long customerId; + + /** + * 下游使用用户名称 + */ + private String downstreamUserName; + + /** + * 电话 + */ + private String phone; + + /** + * 使用的工程名称 + */ + private String projectName; + + /** + * 工程使用地点 + */ + private String projectLocation; + + /** + * 产品用途 + */ + private String productUsage; + /** * 客户照片凭证等 */ @@ -75,7 +105,7 @@ public class TsComplaintAcceptBo extends BaseEntity { /** * 流程阶段: -1=待审核 2=部门意见填写中 3=待总负责人汇总方案 4=方案下发执行反馈中 5=全部办结 +1=待审核 2=部门意见填写中 3=待总负责人汇总方案 4=全部办结 */ private Long flowStatus; diff --git a/klp-flow/src/main/java/com/klp/flow/domain/vo/TsComplaintAcceptVo.java b/klp-flow/src/main/java/com/klp/flow/domain/vo/TsComplaintAcceptVo.java index 4519b5253..7957c68b7 100644 --- a/klp-flow/src/main/java/com/klp/flow/domain/vo/TsComplaintAcceptVo.java +++ b/klp-flow/src/main/java/com/klp/flow/domain/vo/TsComplaintAcceptVo.java @@ -52,6 +52,42 @@ public class TsComplaintAcceptVo extends BaseEntity { @ExcelProperty(value = "客户诉求") private String customerDemand; + /** + * 关联客户ID + */ + @ExcelProperty(value = "关联客户ID") + private Long customerId; + + /** + * 下游使用用户名称 + */ + @ExcelProperty(value = "下游使用用户名称") + private String downstreamUserName; + + /** + * 电话 + */ + @ExcelProperty(value = "电话") + private String phone; + + /** + * 使用的工程名称 + */ + @ExcelProperty(value = "使用的工程名称") + private String projectName; + + /** + * 工程使用地点 + */ + @ExcelProperty(value = "工程使用地点") + private String projectLocation; + + /** + * 产品用途 + */ + @ExcelProperty(value = "产品用途") + private String productUsage; + /** * 客户照片凭证等 */ @@ -84,7 +120,7 @@ public class TsComplaintAcceptVo extends BaseEntity { /** * 流程阶段: -1=待审核 2=部门意见填写中 3=待总负责人汇总方案 4=方案下发执行反馈中 5=全部办结 +1=待审核 2=部门意见填写中 3=待总负责人汇总方案 4=全部办结 */ @ExcelProperty(value = "流程阶段") private Long flowStatus; diff --git a/klp-flow/src/main/java/com/klp/flow/service/impl/TsComplaintAcceptServiceImpl.java b/klp-flow/src/main/java/com/klp/flow/service/impl/TsComplaintAcceptServiceImpl.java index f40bb00f0..fed46981b 100644 --- a/klp-flow/src/main/java/com/klp/flow/service/impl/TsComplaintAcceptServiceImpl.java +++ b/klp-flow/src/main/java/com/klp/flow/service/impl/TsComplaintAcceptServiceImpl.java @@ -268,13 +268,27 @@ public class TsComplaintAcceptServiceImpl implements ITsComplaintAcceptService { } /** - * 批量删除投诉受理单主 + * 批量删除投诉受理单主(级联删除关联的部门任务和执行反馈) */ @Override public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { if(isValid){ //TODO 做一些业务上的校验,判断是否需要校验 } + if (ids == null || ids.isEmpty()) { + return false; + } + // 级联逻辑删除关联的部门意见任务 + tsComplaintTaskMapper.delete( + Wrappers.lambdaQuery() + .in(TsComplaintTask::getAcceptId, ids) + ); + // 级联逻辑删除关联的执行反馈记录 + tsPlanExecuteRelMapper.delete( + Wrappers.lambdaQuery() + .in(TsPlanExecuteRel::getAcceptId, ids) + ); + // 删除受理单本身 return baseMapper.deleteBatchIds(ids) > 0; } } diff --git a/klp-flow/src/main/java/com/klp/flow/service/impl/TsComplaintTaskServiceImpl.java b/klp-flow/src/main/java/com/klp/flow/service/impl/TsComplaintTaskServiceImpl.java index a7983e0e1..0df328de4 100644 --- a/klp-flow/src/main/java/com/klp/flow/service/impl/TsComplaintTaskServiceImpl.java +++ b/klp-flow/src/main/java/com/klp/flow/service/impl/TsComplaintTaskServiceImpl.java @@ -47,6 +47,13 @@ public class TsComplaintTaskServiceImpl implements ITsComplaintTaskService { public TsComplaintTaskVo queryById(Long taskId){ TsComplaintTaskVo vo = baseMapper.selectVoById(taskId); if (vo != null) { + // 过滤已逻辑删除的售后单关联任务 + if (vo.getAcceptId() != null) { + TsComplaintAcceptVo acceptVo = tsComplaintAcceptMapper.selectVoById(vo.getAcceptId()); + if (acceptVo == null) { + return null; + } + } enrichWithAcceptInfo(Collections.singletonList(vo)); } return vo; @@ -115,6 +122,7 @@ public class TsComplaintTaskServiceImpl implements ITsComplaintTaskService { lqw.eq(bo.getFillTime() != null, TsComplaintTask::getFillTime, bo.getFillTime()); lqw.eq(StringUtils.isNotBlank(bo.getFillFile()), TsComplaintTask::getFillFile, bo.getFillFile()); lqw.eq(bo.getRejectMark() != null, TsComplaintTask::getRejectMark, bo.getRejectMark()); + lqw.apply("accept_id IN (SELECT accept_id FROM ts_complaint_accept WHERE del_flag = 0)"); return lqw; } diff --git a/klp-flow/src/main/resources/mapper/flow/TsComplaintAcceptMapper.xml b/klp-flow/src/main/resources/mapper/flow/TsComplaintAcceptMapper.xml index 5334e58e0..5735dd487 100644 --- a/klp-flow/src/main/resources/mapper/flow/TsComplaintAcceptMapper.xml +++ b/klp-flow/src/main/resources/mapper/flow/TsComplaintAcceptMapper.xml @@ -10,6 +10,12 @@ + + + + + + diff --git a/klp-ui/src/views/crm/contract/components/ContractList.vue b/klp-ui/src/views/crm/contract/components/ContractList.vue index 67e0643e4..209c6ff33 100644 --- a/klp-ui/src/views/crm/contract/components/ContractList.vue +++ b/klp-ui/src/views/crm/contract/components/ContractList.vue @@ -7,6 +7,7 @@ 筛选 + 重置 @@ -213,6 +214,7 @@ export default { }, /** 重置按钮操作 */ resetQuery() { + this.queryParams.keyword = undefined; this.$refs["queryForm"].resetFields(); this.queryParams.signDateStart = undefined; this.queryParams.signDateEnd = undefined; diff --git a/klp-ui/src/views/crm/contract/components/ProductContent.vue b/klp-ui/src/views/crm/contract/components/ProductContent.vue index 1768a3b32..0525b001d 100644 --- a/klp-ui/src/views/crm/contract/components/ProductContent.vue +++ b/klp-ui/src/views/crm/contract/components/ProductContent.vue @@ -298,7 +298,7 @@ export default { Object.assign(item, calculateProductFields(item, 'quantity')); }); this.products = products; - this.remark = data.remark || '净边料/毛边料、简包/裸包、卷重结算'; + this.remark = data.remark !== undefined ? data.remark : '净边料/毛边料、简包/裸包、卷重结算'; this.productName = data.productName || ''; } catch (error) { console.error('解析content失败:', error); diff --git a/klp-ui/src/views/crm/contract/index.vue b/klp-ui/src/views/crm/contract/index.vue index d3b8e116e..cab3c1a2d 100644 --- a/klp-ui/src/views/crm/contract/index.vue +++ b/klp-ui/src/views/crm/contract/index.vue @@ -227,11 +227,10 @@ - + @@ -59,4 +131,38 @@ export default { padding-bottom: 12px; border-bottom: 1px solid #eeeae4; } + +/* ===== 客户信息卡片 ===== */ +.customer-info-card { + padding: 10px 14px; + background: #faf8f5; + border: 1px solid #e8e4de; + border-radius: 2px; + margin-bottom: 6px; +} + +.customer-row { + display: flex; + gap: 14px; + padding: 3px 0; +} + +.customer-row + .customer-row { + border-top: 1px solid #f0ece6; +} + +.customer-row-label { + flex-shrink: 0; + width: 70px; + font-family: 'Georgia', 'Times New Roman', serif; + font-size: 11px; + color: #8c8c8c; + letter-spacing: 0.3px; +} + +.customer-row-value { + flex: 1; + font-size: 13px; + color: #1a1a1a; +} diff --git a/klp-ui/src/views/wms/post/objection/components/CoilInfoSection.vue b/klp-ui/src/views/wms/post/objection/components/CoilInfoSection.vue index 36f8168d1..19348a41d 100644 --- a/klp-ui/src/views/wms/post/objection/components/CoilInfoSection.vue +++ b/klp-ui/src/views/wms/post/objection/components/CoilInfoSection.vue @@ -11,15 +11,18 @@ + + + - + - + - + + + @@ -92,8 +94,6 @@ - - @@ -114,8 +114,11 @@
+
+
@@ -131,10 +134,25 @@ placeholder="请选择投诉日期" style="width:100%" /> - + - - + + + + + + + + + + + + + + + + + @@ -170,6 +188,8 @@ 确 定 + + @@ -183,6 +203,7 @@ import jsPDF from 'jspdf'; import CoilSelector from "@/components/CoilSelector/index.vue"; import CurrentCoilNo from "@/components/KLPService/Renderer/CurrentCoilNo.vue"; import DragResizePanel from "@/components/DragResizePanel/index.vue"; +import CustomerSelect from "@/components/KLPService/CustomerSelect/index.vue"; import HeaderControlSection from "./components/HeaderControlSection.vue"; import BasicInfoSection from "./components/BasicInfoSection.vue"; import ContractInfoSection from "./components/ContractInfoSection.vue"; @@ -191,13 +212,15 @@ import DepartmentOpinionSection from "./components/DepartmentOpinionSection.vue" import HandlingSchemeSection from "./components/HandlingSchemeSection.vue"; import ExecutionFeedbackSection from "./components/ExecutionFeedbackSection.vue"; import FlowOverviewSection from "./components/FlowOverviewSection.vue"; +import ExportPdfDialog from "./components/ExportPdfDialog.vue"; export default { name: "AftermarketObjection", components: { - CoilSelector, CurrentCoilNo, DragResizePanel, + CoilSelector, CurrentCoilNo, DragResizePanel, CustomerSelect, HeaderControlSection, BasicInfoSection, ContractInfoSection, - CoilInfoSection, DepartmentOpinionSection, HandlingSchemeSection, ExecutionFeedbackSection, FlowOverviewSection + CoilInfoSection, DepartmentOpinionSection, HandlingSchemeSection, ExecutionFeedbackSection, FlowOverviewSection, + ExportPdfDialog }, dicts: ['coil_quality_status'], data() { @@ -235,8 +258,7 @@ export default { rules: { complaintNo: [{ required: true, message: "请输入售后编号", trigger: "blur" }], complaintDate: [{ required: true, message: "请选择投诉日期", trigger: "blur" }], - complaintContent: [{ required: true, message: "请输入投诉情况", trigger: "blur" }], - customerDemand: [{ required: true, message: "请输入客户诉求", trigger: "blur" }] + complaintContent: [{ required: true, message: "请输入投诉情况", trigger: "blur" }] } }; }, @@ -329,7 +351,12 @@ export default { complaintNo: undefined, complaintDate: undefined, complaintContent: undefined, - customerDemand: undefined, + customerId: undefined, + downstreamUserName: undefined, + phone: undefined, + projectName: undefined, + projectLocation: undefined, + productUsage: undefined, file: undefined, auditStatus: undefined, auditOpinion: undefined, @@ -523,6 +550,9 @@ export default { this.$modal.msgSuccess("移除成功"); this.loadCoilList(this.currentRow.acceptId); }).catch(() => { }); + }, + handleExportPdf(row) { + this.$refs.exportPdfDialog.open(row.acceptId); } } }; diff --git a/klp-ui/src/views/wms/post/objection/opinion.vue b/klp-ui/src/views/wms/post/objection/opinion.vue index f1b5afdb4..868b06b57 100644 --- a/klp-ui/src/views/wms/post/objection/opinion.vue +++ b/klp-ui/src/views/wms/post/objection/opinion.vue @@ -73,6 +73,10 @@ + + @@ -80,20 +84,27 @@ - - +
+
+
+ 已提交意见 · Submitted Opinion +
+ +
+ +
+
+
- 填写处理意见 · Fill in Opinion + {{ currentTask.taskStatus === 1 ? '修改处理意见' : '填写处理意见' }} · {{ currentTask.taskStatus === 1 ? 'Edit Opinion' : 'Fill in Opinion' }}
- - - + @@ -121,10 +132,13 @@ import BasicInfoSection from "./components/BasicInfoSection.vue"; import CoilInfoSection from "./components/CoilInfoSection.vue"; import ContractInfoSection from "./components/ContractInfoSection.vue"; import FlowOverviewSection from "./components/FlowOverviewSection.vue"; +import DeptOpinionForm from "./components/DeptOpinionForm.vue"; +import DeptOpinionPreview from "./components/DeptOpinionPreview.vue"; +import FileList from "@/components/FileList/index.vue"; export default { name: "AftermarketOpinion", - components: { DragResizePanel, HeaderControlSection, BasicInfoSection, CoilInfoSection, ContractInfoSection, FlowOverviewSection }, + components: { DragResizePanel, HeaderControlSection, BasicInfoSection, CoilInfoSection, ContractInfoSection, FlowOverviewSection, DeptOpinionForm, DeptOpinionPreview, FileList }, data() { return { loading: false, @@ -212,7 +226,7 @@ export default { }, canEdit(row) { const status = (row.acceptInfo || {}).flowStatus; - return (status === 2 || status === 3) && row.taskStatus === 0; + return status === 2; }, confirmCancel() { this.$modal.confirm('确认取消?已填写的内容将不会保存。').then(() => { @@ -235,7 +249,14 @@ export default { }).catch(() => { }).finally(() => { this.rejectLoading = false; }); }, submitOpinion() { - if (!this.opinionForm.deptOpinion) { + try { + const data = JSON.parse(this.opinionForm.deptOpinion || '{}'); + const hasContent = Object.values(data).some(v => v); + if (!hasContent) { + this.$modal.msgWarning("请至少填写一项处理意见"); + return; + } + } catch (e) { this.$modal.msgWarning("请填写处理意见"); return; } @@ -457,6 +478,10 @@ export default { padding-top: 4px; } +.opinion-preview-wrapper { + padding-top: 4px; +} + .opinion-form-section { margin-bottom: 0; } diff --git a/klp-ui/src/views/wms/post/objection/summary.vue b/klp-ui/src/views/wms/post/objection/summary.vue new file mode 100644 index 000000000..89e60a42b --- /dev/null +++ b/klp-ui/src/views/wms/post/objection/summary.vue @@ -0,0 +1,464 @@ + + + + + diff --git a/klp-ui/src/views/wms/post/objection/todo.vue b/klp-ui/src/views/wms/post/objection/todo.vue index 92a8bfbd3..f22fca9d8 100644 --- a/klp-ui/src/views/wms/post/objection/todo.vue +++ b/klp-ui/src/views/wms/post/objection/todo.vue @@ -72,12 +72,14 @@ + + - - From ee376f922faa0bf656ac9c7a65d15b23bf294609 Mon Sep 17 00:00:00 2001 From: Joshi <3040996759@qq.com> Date: Mon, 29 Jun 2026 16:07:29 +0800 Subject: [PATCH 3/3] =?UTF-8?q?feat(aps):=20=E6=96=B0=E5=A2=9E=E6=8E=92?= =?UTF-8?q?=E4=BA=A7=E5=8D=95=E6=98=8E=E7=BB=86=E5=90=88=E5=B9=B6=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E5=8F=8A=E4=BC=98=E5=8C=96=E7=95=8C=E9=9D=A2=E5=B1=95?= =?UTF-8?q?=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在排产单明细表格中添加多选合并功能 - 实现排产单明细合并对话框及合并逻辑 - 优化排产单明细表格列配置和表单布局 - 添加合并校验和接收产需单API接口 - 重构订单绑定解绑逻辑提升用户体验 - 添加ScheduleDetailCoilBind组件引入 --- .../SchProdScheduleItemController.java | 21 + .../klp/flow/domain/SchProdScheduleItem.java | 8 +- .../flow/domain/bo/SchProdScheduleItemBo.java | 9 +- .../SchProdScheduleItemMergeValidateBo.java | 22 + .../bo/SchProdScheduleItemReceiveBo.java | 21 + .../SchProdScheduleItemMergeValidateVo.java | 115 +++ .../flow/domain/vo/SchProdScheduleItemVo.java | 16 +- .../service/ISchProdScheduleItemService.java | 13 + .../impl/SchProdScheduleItemServiceImpl.java | 299 +++++++- .../impl/SchProdScheduleServiceImpl.java | 2 + klp-ui/src/api/aps/detailCoilRel.js | 27 + klp-ui/src/api/aps/schedule.js | 29 + .../components/ScheduleDetailCoilBind.vue | 334 +++++++++ klp-ui/src/views/wms/coil/merge.vue | 3 + klp-ui/src/views/wms/coil/panels/do.vue | 5 +- klp-ui/src/views/wms/coil/split.vue | 3 + klp-ui/src/views/wms/coil/typing.vue | 5 +- klp-ui/src/views/wms/post/aps/requirement.vue | 114 +-- klp-ui/src/views/wms/post/aps/schedule.vue | 655 +++++++++++++++--- 19 files changed, 1547 insertions(+), 154 deletions(-) create mode 100644 klp-flow/src/main/java/com/klp/flow/domain/bo/SchProdScheduleItemMergeValidateBo.java create mode 100644 klp-flow/src/main/java/com/klp/flow/domain/bo/SchProdScheduleItemReceiveBo.java create mode 100644 klp-flow/src/main/java/com/klp/flow/domain/vo/SchProdScheduleItemMergeValidateVo.java create mode 100644 klp-ui/src/api/aps/detailCoilRel.js create mode 100644 klp-ui/src/views/wms/coil/components/ScheduleDetailCoilBind.vue diff --git a/klp-flow/src/main/java/com/klp/flow/controller/SchProdScheduleItemController.java b/klp-flow/src/main/java/com/klp/flow/controller/SchProdScheduleItemController.java index 4ea667bf7..6112cfe64 100644 --- a/klp-flow/src/main/java/com/klp/flow/controller/SchProdScheduleItemController.java +++ b/klp-flow/src/main/java/com/klp/flow/controller/SchProdScheduleItemController.java @@ -18,8 +18,11 @@ import com.klp.common.core.validate.EditGroup; import com.klp.common.enums.BusinessType; import com.klp.common.utils.poi.ExcelUtil; import com.klp.flow.domain.vo.SchProdScheduleItemVo; +import com.klp.flow.domain.vo.SchProdScheduleItemMergeValidateVo; import com.klp.flow.domain.bo.SchProdScheduleItemBo; import com.klp.flow.domain.bo.SchProdScheduleItemMergeBo; +import com.klp.flow.domain.bo.SchProdScheduleItemReceiveBo; +import com.klp.flow.domain.bo.SchProdScheduleItemMergeValidateBo; import com.klp.flow.service.ISchProdScheduleItemService; import com.klp.common.core.page.TableDataInfo; @@ -117,4 +120,22 @@ public class SchProdScheduleItemController extends BaseController { public R merge(@Validated @RequestBody SchProdScheduleItemMergeBo mergeBo) { return toAjax(iSchProdScheduleItemService.mergeByBo(mergeBo)); } + + /** + * 接收产需单:从 sch_prod_schedule + sch_prod_schedule_detail 全字段复制到 sch_prod_schedule_item + */ + @Log(title = "排产单主加明细可合并", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping("/receive") + public R receive(@Validated @RequestBody SchProdScheduleItemReceiveBo receiveBo) { + return toAjax(iSchProdScheduleItemService.receiveByBo(receiveBo)); + } + + /** + * 合并校验:检查待合并记录来源产需单的 header 字段是否一致 + */ + @PostMapping("/mergeValidate") + public R mergeValidate(@Validated @RequestBody SchProdScheduleItemMergeValidateBo validateBo) { + return R.ok(iSchProdScheduleItemService.validateMerge(validateBo)); + } } diff --git a/klp-flow/src/main/java/com/klp/flow/domain/SchProdScheduleItem.java b/klp-flow/src/main/java/com/klp/flow/domain/SchProdScheduleItem.java index 6a58fc1e0..f888ece07 100644 --- a/klp-flow/src/main/java/com/klp/flow/domain/SchProdScheduleItem.java +++ b/klp-flow/src/main/java/com/klp/flow/domain/SchProdScheduleItem.java @@ -128,13 +128,9 @@ public class SchProdScheduleItem extends BaseEntity { */ private String returnReason; /** - * 排产单主表ID + * 排产明细关联主表ID(逗号分隔,未合并存单值,合并后存多个) */ - private String scheduleIds; - /** - * 来源销售订单明细ID(溯源原始订单规格) - */ - private String orderDetailIds; + private String scheduleDetailIds; /** * 规格 例:1.0X1250 */ diff --git a/klp-flow/src/main/java/com/klp/flow/domain/bo/SchProdScheduleItemBo.java b/klp-flow/src/main/java/com/klp/flow/domain/bo/SchProdScheduleItemBo.java index 9ef0f8be2..c2fbd5f3f 100644 --- a/klp-flow/src/main/java/com/klp/flow/domain/bo/SchProdScheduleItemBo.java +++ b/klp-flow/src/main/java/com/klp/flow/domain/bo/SchProdScheduleItemBo.java @@ -154,14 +154,9 @@ public class SchProdScheduleItemBo extends BaseEntity { private String returnReason; /** - * 排产单主表ID + * 排产明细关联主表ID(逗号分隔,未合并存单值,合并后存多个) */ - private String scheduleIds; - - /** - * 来源销售订单明细ID(溯源原始订单规格) - */ - private String orderDetailIds; + private String scheduleDetailIds; /** * 规格 例:1.0X1250 diff --git a/klp-flow/src/main/java/com/klp/flow/domain/bo/SchProdScheduleItemMergeValidateBo.java b/klp-flow/src/main/java/com/klp/flow/domain/bo/SchProdScheduleItemMergeValidateBo.java new file mode 100644 index 000000000..7a2f3369d --- /dev/null +++ b/klp-flow/src/main/java/com/klp/flow/domain/bo/SchProdScheduleItemMergeValidateBo.java @@ -0,0 +1,22 @@ +package com.klp.flow.domain.bo; + +import lombok.Data; +import javax.validation.constraints.NotEmpty; +import java.util.List; + +/** + * 排产单合并校验请求对象 + * + * @author klp + * @date 2026-06-29 + */ +@Data +public class SchProdScheduleItemMergeValidateBo { + + /** + * 待合并记录ID列表 + */ + @NotEmpty(message = "待合并记录ID不能为空") + private List ids; + +} diff --git a/klp-flow/src/main/java/com/klp/flow/domain/bo/SchProdScheduleItemReceiveBo.java b/klp-flow/src/main/java/com/klp/flow/domain/bo/SchProdScheduleItemReceiveBo.java new file mode 100644 index 000000000..a200e24bd --- /dev/null +++ b/klp-flow/src/main/java/com/klp/flow/domain/bo/SchProdScheduleItemReceiveBo.java @@ -0,0 +1,21 @@ +package com.klp.flow.domain.bo; + +import lombok.Data; +import javax.validation.constraints.NotNull; + +/** + * 产需单接收请求对象 + * + * @author klp + * @date 2026-06-29 + */ +@Data +public class SchProdScheduleItemReceiveBo { + + /** + * 排产单主表ID(产需单ID) + */ + @NotNull(message = "排产单主表ID不能为空") + private Long scheduleId; + +} diff --git a/klp-flow/src/main/java/com/klp/flow/domain/vo/SchProdScheduleItemMergeValidateVo.java b/klp-flow/src/main/java/com/klp/flow/domain/vo/SchProdScheduleItemMergeValidateVo.java new file mode 100644 index 000000000..4540e36ee --- /dev/null +++ b/klp-flow/src/main/java/com/klp/flow/domain/vo/SchProdScheduleItemMergeValidateVo.java @@ -0,0 +1,115 @@ +package com.klp.flow.domain.vo; + +import lombok.Data; +import java.math.BigDecimal; +import java.util.List; +import java.util.Map; + +/** + * 排产单合并校验视图对象 + * + * @author klp + * @date 2026-06-29 + */ +@Data +public class SchProdScheduleItemMergeValidateVo { + + /** 是否所有 header 字段一致 */ + private Boolean consistent; + + /** 有差异的字段列表 */ + private List diffs; + + /** 被合并记录快照 */ + private List items; + + /** 汇总信息 */ + private MergedSummary summary; + + /** + * 字段差异 + */ + @Data + public static class FieldDiff { + /** 字段名(Java属性名) */ + private String fieldName; + /** 字段中文标签 */ + private String fieldLabel; + /** scheduleId → 值 */ + private Map values; + } + + /** + * 被合并记录快照 + */ + @Data + public static class ItemInfo { + /** sch_prod_schedule_item 主键 */ + private Long itemId; + /** 第一个源 schedule_id(从 scheduleIds 解析) */ + private Long sourceScheduleId; + /** 排产单号 */ + private String scheduleNo; + /** 订货单位 */ + private String customerName; + /** 品名 */ + private String productType; + /** 规格 */ + private String spec; + /** 材质 */ + private String material; + /** 排产吨数 */ + private BigDecimal scheduleWeight; + /** 来源排产明细ID串 */ + private String scheduleDetailIds; + /** 业务员 */ + private String businessUser; + /** 联系电话 */ + private String businessPhone; + /** 交货期(天) */ + private Long deliveryCycle; + /** 产品用途 */ + private String usePurpose; + /** 厚度公差 */ + private String thicknessTolerance; + /** 宽度公差 */ + private String widthTolerance; + /** 表面质量 */ + private String surfaceQuality; + /** 表面处理 */ + private String surfaceTreatment; + /** 内径尺寸 */ + private String innerDiameter; + /** 外径要求 */ + private String outerDiameter; + /** 包装要求 */ + private String packReq; + /** 切边要求 */ + private String cutEdgeReq; + /** 单件重量 */ + private String singleCoilWeight; + /** 交货重量偏差 */ + private String weightDeviation; + /** 其他技术要求 */ + private String otherTechReq; + /** 付款情况说明 */ + private String paymentDesc; + /** 关联销售合同号 */ + private String relContractNo; + /** 排产总计划吨数 */ + private BigDecimal totalPlanWeight; + /** 订单日期 */ + private java.util.Date orderDate; + } + + /** + * 合并汇总 + */ + @Data + public static class MergedSummary { + /** 明细条数 */ + private int itemCount; + /** 合计排产吨数 */ + private BigDecimal totalScheduleWeight; + } +} diff --git a/klp-flow/src/main/java/com/klp/flow/domain/vo/SchProdScheduleItemVo.java b/klp-flow/src/main/java/com/klp/flow/domain/vo/SchProdScheduleItemVo.java index 6fec3214c..6adfeb3a5 100644 --- a/klp-flow/src/main/java/com/klp/flow/domain/vo/SchProdScheduleItemVo.java +++ b/klp-flow/src/main/java/com/klp/flow/domain/vo/SchProdScheduleItemVo.java @@ -8,6 +8,7 @@ import com.alibaba.excel.annotation.ExcelProperty; import com.klp.common.annotation.ExcelDictFormat; import com.klp.common.convert.ExcelDictConvert; import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; /** @@ -40,6 +41,8 @@ public class SchProdScheduleItemVo { */ @ExcelProperty(value = "生产日期", converter = ExcelDictConvert.class) @ExcelDictFormat(readConverterExp = "和=合同号组成业务关联键") + @JsonFormat(pattern = "yyyy-MM-dd") + @DateTimeFormat(pattern = "yyyy-MM-dd") private Date prodDate; /** @@ -181,17 +184,10 @@ public class SchProdScheduleItemVo { private String returnReason; /** - * 排产单主表ID + * 排产明细关联主表ID(逗号分隔) */ - @ExcelProperty(value = "排产单主表ID") - private String scheduleIds; - - /** - * 来源销售订单明细ID(溯源原始订单规格) - */ - @ExcelProperty(value = "来源销售订单明细ID", converter = ExcelDictConvert.class) - @ExcelDictFormat(readConverterExp = "溯=源原始订单规格") - private String orderDetailIds; + @ExcelProperty(value = "排产明细关联主表ID") + private String scheduleDetailIds; /** * 规格 例:1.0X1250 diff --git a/klp-flow/src/main/java/com/klp/flow/service/ISchProdScheduleItemService.java b/klp-flow/src/main/java/com/klp/flow/service/ISchProdScheduleItemService.java index cef5243bf..1eea0f013 100644 --- a/klp-flow/src/main/java/com/klp/flow/service/ISchProdScheduleItemService.java +++ b/klp-flow/src/main/java/com/klp/flow/service/ISchProdScheduleItemService.java @@ -2,8 +2,11 @@ package com.klp.flow.service; import com.klp.flow.domain.SchProdScheduleItem; import com.klp.flow.domain.vo.SchProdScheduleItemVo; +import com.klp.flow.domain.vo.SchProdScheduleItemMergeValidateVo; import com.klp.flow.domain.bo.SchProdScheduleItemBo; import com.klp.flow.domain.bo.SchProdScheduleItemMergeBo; +import com.klp.flow.domain.bo.SchProdScheduleItemReceiveBo; +import com.klp.flow.domain.bo.SchProdScheduleItemMergeValidateBo; import com.klp.common.core.page.TableDataInfo; import com.klp.common.core.domain.PageQuery; @@ -57,4 +60,14 @@ public interface ISchProdScheduleItemService { * 合并多条排产单主加明细可合并记录:先删除被合并的记录,再插入合并后的新记录 */ Boolean mergeByBo(SchProdScheduleItemMergeBo mergeBo); + + /** + * 接收产需单:从 sch_prod_schedule + sch_prod_schedule_detail 全字段复制到 sch_prod_schedule_item + */ + Boolean receiveByBo(SchProdScheduleItemReceiveBo receiveBo); + + /** + * 合并校验:检查待合并记录来源产需单的 header 字段是否一致 + */ + SchProdScheduleItemMergeValidateVo validateMerge(SchProdScheduleItemMergeValidateBo validateBo); } diff --git a/klp-flow/src/main/java/com/klp/flow/service/impl/SchProdScheduleItemServiceImpl.java b/klp-flow/src/main/java/com/klp/flow/service/impl/SchProdScheduleItemServiceImpl.java index 1359cf513..dee39cfb3 100644 --- a/klp-flow/src/main/java/com/klp/flow/service/impl/SchProdScheduleItemServiceImpl.java +++ b/klp-flow/src/main/java/com/klp/flow/service/impl/SchProdScheduleItemServiceImpl.java @@ -11,16 +11,27 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import com.klp.flow.domain.bo.SchProdScheduleItemBo; import com.klp.flow.domain.bo.SchProdScheduleItemMergeBo; +import com.klp.flow.domain.bo.SchProdScheduleItemReceiveBo; +import com.klp.flow.domain.bo.SchProdScheduleItemMergeValidateBo; import com.klp.flow.domain.vo.SchProdScheduleItemVo; +import com.klp.flow.domain.vo.SchProdScheduleItemMergeValidateVo; import com.klp.flow.domain.SchProdScheduleItem; +import com.klp.flow.domain.SchProdSchedule; +import com.klp.flow.domain.SchProdScheduleDetail; import com.klp.flow.mapper.SchProdScheduleItemMapper; +import com.klp.flow.mapper.SchProdScheduleMapper; +import com.klp.flow.mapper.SchProdScheduleDetailMapper; import com.klp.flow.service.ISchProdScheduleItemService; import org.springframework.transaction.annotation.Transactional; +import java.math.BigDecimal; import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Collection; +import java.util.stream.Collectors; /** * 排产单主加明细可合并Service业务层处理 @@ -33,6 +44,8 @@ import java.util.Collection; public class SchProdScheduleItemServiceImpl implements ISchProdScheduleItemService { private final SchProdScheduleItemMapper baseMapper; + private final SchProdScheduleMapper schProdScheduleMapper; + private final SchProdScheduleDetailMapper schProdScheduleDetailMapper; /** * 查询排产单主加明细可合并 @@ -89,8 +102,7 @@ public class SchProdScheduleItemServiceImpl implements ISchProdScheduleItemServi lqw.eq(StringUtils.isNotBlank(bo.getOtherTechReq()), SchProdScheduleItem::getOtherTechReq, bo.getOtherTechReq()); lqw.eq(StringUtils.isNotBlank(bo.getPaymentDesc()), SchProdScheduleItem::getPaymentDesc, bo.getPaymentDesc()); lqw.eq(StringUtils.isNotBlank(bo.getReturnReason()), SchProdScheduleItem::getReturnReason, bo.getReturnReason()); - lqw.eq(StringUtils.isNotBlank(bo.getScheduleIds()), SchProdScheduleItem::getScheduleIds, bo.getScheduleIds()); - lqw.eq(StringUtils.isNotBlank(bo.getOrderDetailIds()), SchProdScheduleItem::getOrderDetailIds, bo.getOrderDetailIds()); + lqw.eq(StringUtils.isNotBlank(bo.getScheduleDetailIds()), SchProdScheduleItem::getScheduleDetailIds, bo.getScheduleDetailIds()); lqw.eq(StringUtils.isNotBlank(bo.getSpec()), SchProdScheduleItem::getSpec, bo.getSpec()); lqw.eq(StringUtils.isNotBlank(bo.getMaterial()), SchProdScheduleItem::getMaterial, bo.getMaterial()); lqw.eq(bo.getScheduleWeight() != null, SchProdScheduleItem::getScheduleWeight, bo.getScheduleWeight()); @@ -179,4 +191,287 @@ public class SchProdScheduleItemServiceImpl implements ISchProdScheduleItemServi } return flag; } + + /** + * 接收产需单:从 sch_prod_schedule + sch_prod_schedule_detail 全字段复制到 sch_prod_schedule_item + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean receiveByBo(SchProdScheduleItemReceiveBo receiveBo) { + Long scheduleId = receiveBo.getScheduleId(); + + // 1. 查询产需单主表 + SchProdSchedule header = schProdScheduleMapper.selectById(scheduleId); + if (header == null) { + throw new RuntimeException("产需单不存在,scheduleId=" + scheduleId); + } + + // 2. 查询产需单明细列表 + List details = schProdScheduleDetailMapper.selectList( + Wrappers.lambdaQuery() + .eq(SchProdScheduleDetail::getScheduleId, scheduleId) + ); + if (details == null || details.isEmpty()) { + throw new RuntimeException("产需单无明细数据,scheduleId=" + scheduleId); + } + + // 3. 遍历每条 detail,构建 SchProdScheduleItem 列表 + List addList = new ArrayList<>(details.size()); + for (SchProdScheduleDetail detail : details) { + SchProdScheduleItem item = new SchProdScheduleItem(); + + // 从 header 复制所有字段 + item.setScheduleNo(header.getScheduleNo()); + item.setProdDate(header.getProdDate()); + item.setScheduleStatus(2L); // 已下达 + item.setTotalPlanWeight(header.getTotalPlanWeight()); + item.setRelContractNo(header.getRelContractNo()); + item.setBusinessUser(header.getBusinessUser()); + item.setBusinessPhone(header.getBusinessPhone()); + item.setOrderDate(header.getOrderDate()); + item.setCustomerName(header.getCustomerName()); + item.setDeliveryCycle(header.getDeliveryCycle()); + item.setUsePurpose(header.getUsePurpose()); + item.setProductType(header.getProductType()); + item.setThicknessTolerance(header.getThicknessTolerance()); + item.setWidthTolerance(header.getWidthTolerance()); + item.setSurfaceQuality(header.getSurfaceQuality()); + item.setSurfaceTreatment(header.getSurfaceTreatment()); + item.setInnerDiameter(header.getInnerDiameter()); + item.setOuterDiameter(header.getOuterDiameter()); + item.setPackReq(header.getPackReq()); + item.setCutEdgeReq(header.getCutEdgeReq()); + item.setSingleCoilWeight(header.getSingleCoilWeight()); + item.setWeightDeviation(header.getWeightDeviation()); + item.setOtherTechReq(header.getOtherTechReq()); + item.setPaymentDesc(header.getPaymentDesc()); + item.setRemark(header.getRemark()); + // 不复制 returnReason + + // 从 detail 复制 + item.setSpec(detail.getSpec()); + item.setMaterial(detail.getMaterial()); + item.setScheduleWeight(detail.getScheduleWeight()); + item.setProductItem(detail.getProductType()); + item.setRowRemark(detail.getRemark()); + + // 来源追溯(未合并,各存明细ID) + item.setScheduleDetailIds(String.valueOf(detail.getScheduleDetailId())); + + validEntityBeforeSave(item); + addList.add(item); + } + + // 4. 批量插入 + baseMapper.insertBatch(addList); + + // 5. 更新产需单状态为 2(已下达) + header.setScheduleStatus(2L); + schProdScheduleMapper.updateById(header); + + return true; + } + + /** + * 合并校验:检查待合并记录来源产需单的 header 字段是否一致 + */ + @Override + public SchProdScheduleItemMergeValidateVo validateMerge(SchProdScheduleItemMergeValidateBo validateBo) { + List ids = validateBo.getIds(); + + // 1. 查询所有待合并的 sch_prod_schedule_item 记录 + List items = baseMapper.selectBatchIds(ids); + if (items == null || items.size() < 2) { + throw new RuntimeException("待合并记录不足,至少需要2条"); + } + + // 2. 解析每条记录的 scheduleIds(逗号分隔),取第一个作为源 schedule_id + // 查询所有源 sch_prod_schedule 记录 + List sourceScheduleIds = new ArrayList<>(); + for (SchProdScheduleItem item : items) { + String sids = item.getScheduleDetailIds(); + if (sids != null && !sids.isEmpty()) { + String[] parts = sids.split(","); + if (parts.length > 0) { + String firstId = parts[0].trim(); + if (!firstId.isEmpty()) { + sourceScheduleIds.add(Long.valueOf(firstId)); + } + } + } + } + List distinctSourceIds = sourceScheduleIds.stream().distinct().collect(Collectors.toList()); + List sourceHeaders = schProdScheduleMapper.selectBatchIds(distinctSourceIds); + Map headerMap = new LinkedHashMap<>(); + for (SchProdSchedule h : sourceHeaders) { + headerMap.put(h.getScheduleId(), h); + } + + // 3. 定义要比较的字段列表(字段名 → 中文标签) + Map compareFields = new LinkedHashMap<>(); + compareFields.put("scheduleNo", "排产单号"); + compareFields.put("customerName", "订货单位"); + compareFields.put("businessUser", "业务员"); + compareFields.put("businessPhone", "联系电话"); + compareFields.put("deliveryCycle", "交货期(天)"); + compareFields.put("usePurpose", "产品用途"); + compareFields.put("productType", "品名"); + compareFields.put("thicknessTolerance", "厚度公差"); + compareFields.put("widthTolerance", "宽度公差"); + compareFields.put("surfaceQuality", "表面质量"); + compareFields.put("surfaceTreatment", "表面处理"); + compareFields.put("innerDiameter", "内径尺寸"); + compareFields.put("outerDiameter", "外径要求"); + compareFields.put("packReq", "包装要求"); + compareFields.put("cutEdgeReq", "切边要求"); + compareFields.put("singleCoilWeight", "单件重量"); + compareFields.put("weightDeviation", "交货重量偏差"); + compareFields.put("otherTechReq", "其他技术要求"); + compareFields.put("paymentDesc", "付款情况说明"); + compareFields.put("relContractNo", "关联销售合同号"); + compareFields.put("totalPlanWeight", "排产总计划吨数"); + compareFields.put("orderDate", "订单日期"); + + // 4. 逐一比较字段 + List diffs = new ArrayList<>(); + boolean allConsistent = true; + + for (Map.Entry entry : compareFields.entrySet()) { + String fieldName = entry.getKey(); + String fieldLabel = entry.getValue(); + Map values = new LinkedHashMap<>(); + + for (SchProdSchedule header : sourceHeaders) { + Long headerId = header.getScheduleId(); + Object val = getFieldValue(header, fieldName); + values.put(String.valueOf(headerId), val); + } + + // 判断所有值是否一致 + boolean same = values.values().stream().distinct().count() <= 1; + if (!same) { + allConsistent = false; + SchProdScheduleItemMergeValidateVo.FieldDiff diff = new SchProdScheduleItemMergeValidateVo.FieldDiff(); + diff.setFieldName(fieldName); + diff.setFieldLabel(fieldLabel); + diff.setValues(values); + diffs.add(diff); + } + } + + // 4.1 比对明细级字段(spec, material, productItem)—— 这些字段来自 item 自身,不来自 header + Map itemCompareFields = new LinkedHashMap<>(); + itemCompareFields.put("spec", "规格"); + itemCompareFields.put("material", "材质"); + itemCompareFields.put("productItem", "品名项"); + + for (Map.Entry entry : itemCompareFields.entrySet()) { + String fieldName = entry.getKey(); + String fieldLabel = entry.getValue(); + Map values = new LinkedHashMap<>(); + + for (SchProdScheduleItem item : items) { + Object val = getItemFieldValue(item, fieldName); + values.put(String.valueOf(item.getScheduleId()), val); + } + + boolean same = values.values().stream().distinct().count() <= 1; + if (!same) { + allConsistent = false; + SchProdScheduleItemMergeValidateVo.FieldDiff diff = new SchProdScheduleItemMergeValidateVo.FieldDiff(); + diff.setFieldName(fieldName); + diff.setFieldLabel(fieldLabel); + diff.setValues(values); + diffs.add(diff); + } + } + + // 5. 构建 ItemInfo 快照列表 + List itemInfos = new ArrayList<>(); + BigDecimal totalWeight = BigDecimal.ZERO; + for (SchProdScheduleItem item : items) { + SchProdScheduleItemMergeValidateVo.ItemInfo info = new SchProdScheduleItemMergeValidateVo.ItemInfo(); + info.setItemId(item.getScheduleId()); + // 解析第一个源 schedule_id + String sids = item.getScheduleDetailIds(); + Long firstSourceId = null; + if (sids != null && !sids.isEmpty()) { + String[] parts = sids.split(","); + if (parts.length > 0) { + try { + firstSourceId = Long.valueOf(parts[0].trim()); + } catch (NumberFormatException ignored) {} + } + } + info.setSourceScheduleId(firstSourceId); + info.setScheduleNo(item.getScheduleNo()); + info.setCustomerName(item.getCustomerName()); + info.setProductType(item.getProductType()); + info.setSpec(item.getSpec()); + info.setMaterial(item.getMaterial()); + info.setScheduleWeight(item.getScheduleWeight()); + info.setScheduleDetailIds(item.getScheduleDetailIds()); + // header 字段 + info.setBusinessUser(item.getBusinessUser()); + info.setBusinessPhone(item.getBusinessPhone()); + info.setDeliveryCycle(item.getDeliveryCycle()); + info.setUsePurpose(item.getUsePurpose()); + info.setThicknessTolerance(item.getThicknessTolerance()); + info.setWidthTolerance(item.getWidthTolerance()); + info.setSurfaceQuality(item.getSurfaceQuality()); + info.setSurfaceTreatment(item.getSurfaceTreatment()); + info.setInnerDiameter(item.getInnerDiameter()); + info.setOuterDiameter(item.getOuterDiameter()); + info.setPackReq(item.getPackReq()); + info.setCutEdgeReq(item.getCutEdgeReq()); + info.setSingleCoilWeight(item.getSingleCoilWeight()); + info.setWeightDeviation(item.getWeightDeviation()); + info.setOtherTechReq(item.getOtherTechReq()); + info.setPaymentDesc(item.getPaymentDesc()); + info.setRelContractNo(item.getRelContractNo()); + info.setTotalPlanWeight(item.getTotalPlanWeight()); + info.setOrderDate(item.getOrderDate()); + itemInfos.add(info); + totalWeight = totalWeight.add(item.getScheduleWeight() != null ? item.getScheduleWeight() : BigDecimal.ZERO); + } + + // 6. 构建汇总 + SchProdScheduleItemMergeValidateVo.MergedSummary summary = new SchProdScheduleItemMergeValidateVo.MergedSummary(); + summary.setItemCount(items.size()); + summary.setTotalScheduleWeight(totalWeight); + + // 7. 组装返回 + SchProdScheduleItemMergeValidateVo vo = new SchProdScheduleItemMergeValidateVo(); + vo.setConsistent(allConsistent); + vo.setDiffs(diffs); + vo.setItems(itemInfos); + vo.setSummary(summary); + return vo; + } + + /** + * 反射获取字段值(用于动态字段比较 — SchProdSchedule header) + */ + private Object getFieldValue(SchProdSchedule header, String fieldName) { + try { + java.lang.reflect.Field field = SchProdSchedule.class.getDeclaredField(fieldName); + field.setAccessible(true); + return field.get(header); + } catch (Exception e) { + return null; + } + } + + /** + * 反射获取字段值(用于动态字段比较 — SchProdScheduleItem 明细) + */ + private Object getItemFieldValue(SchProdScheduleItem item, String fieldName) { + try { + java.lang.reflect.Field field = SchProdScheduleItem.class.getDeclaredField(fieldName); + field.setAccessible(true); + return field.get(item); + } catch (Exception e) { + return null; + } + } } diff --git a/klp-flow/src/main/java/com/klp/flow/service/impl/SchProdScheduleServiceImpl.java b/klp-flow/src/main/java/com/klp/flow/service/impl/SchProdScheduleServiceImpl.java index e6a105b6f..f391a6783 100644 --- a/klp-flow/src/main/java/com/klp/flow/service/impl/SchProdScheduleServiceImpl.java +++ b/klp-flow/src/main/java/com/klp/flow/service/impl/SchProdScheduleServiceImpl.java @@ -196,6 +196,8 @@ public class SchProdScheduleServiceImpl implements ISchProdScheduleService { lqw.eq(StringUtils.isNotBlank(bo.getOtherTechReq()), SchProdSchedule::getOtherTechReq, bo.getOtherTechReq()); lqw.eq(StringUtils.isNotBlank(bo.getPaymentDesc()), SchProdSchedule::getPaymentDesc, bo.getPaymentDesc()); lqw.eq(StringUtils.isNotBlank(bo.getReturnReason()), SchProdSchedule::getReturnReason, bo.getReturnReason()); + // 根据创建时间倒叙排序 + lqw.orderByDesc(SchProdSchedule::getCreateTime); return lqw; } diff --git a/klp-ui/src/api/aps/detailCoilRel.js b/klp-ui/src/api/aps/detailCoilRel.js new file mode 100644 index 000000000..42019ce36 --- /dev/null +++ b/klp-ui/src/api/aps/detailCoilRel.js @@ -0,0 +1,27 @@ +import request from '@/utils/request' + +// 查询排产明细-钢卷绑定关系列表 +export function listDetailCoilRel(query) { + return request({ + url: '/flow/detailCoilRel/list', + method: 'get', + params: query + }) +} + +// 批量新增绑定关系 +export function batchAddDetailCoilRel(data) { + return request({ + url: '/flow/detailCoilRel/batch', + method: 'post', + data + }) +} + +// 删除绑定关系 +export function delDetailCoilRel(relIds) { + return request({ + url: '/flow/detailCoilRel/' + relIds, + method: 'delete' + }) +} diff --git a/klp-ui/src/api/aps/schedule.js b/klp-ui/src/api/aps/schedule.js index 03138303b..8e1615358 100644 --- a/klp-ui/src/api/aps/schedule.js +++ b/klp-ui/src/api/aps/schedule.js @@ -70,3 +70,32 @@ export function delScheduleItem(ids) { method: 'delete' }) } + +// ====== 排产单明细项 接收/合并 ====== + +// 接收产需单(后端全字段复制) +export function receiveScheduleItem(scheduleId) { + return request({ + url: '/flow/prodScheduleItem/receive', + method: 'post', + data: { scheduleId } + }) +} + +// 合并验证(检查源排产单字段一致性) +export function validateMerge(data) { + return request({ + url: '/flow/prodScheduleItem/mergeValidate', + method: 'post', + data + }) +} + +// 合并排产单明细项 +export function mergeScheduleItem(data) { + return request({ + url: '/flow/prodScheduleItem/merge', + method: 'post', + data + }) +} diff --git a/klp-ui/src/views/wms/coil/components/ScheduleDetailCoilBind.vue b/klp-ui/src/views/wms/coil/components/ScheduleDetailCoilBind.vue new file mode 100644 index 000000000..8410e1e65 --- /dev/null +++ b/klp-ui/src/views/wms/coil/components/ScheduleDetailCoilBind.vue @@ -0,0 +1,334 @@ + + + + + diff --git a/klp-ui/src/views/wms/coil/merge.vue b/klp-ui/src/views/wms/coil/merge.vue index 5f0b8524b..31f8652ce 100644 --- a/klp-ui/src/views/wms/coil/merge.vue +++ b/klp-ui/src/views/wms/coil/merge.vue @@ -26,6 +26,7 @@
源卷列表 + 添加源卷
@@ -374,6 +375,7 @@ import WarehouseSelect from "@/components/KLPService/WarehouseSelect"; import TimeInput from "@/components/TimeInput"; import AbnormalForm from './components/AbnormalForm'; import PlanSheetViewer from './components/PlanSheetViewer.vue'; +import ScheduleDetailCoilBind from './components/ScheduleDetailCoilBind' import { generateCoilNoPrefix } from "@/utils/coil/coilNo"; import ContractSelect from "@/components/KLPService/ContractSelect"; @@ -389,6 +391,7 @@ export default { AbnormalForm, ContractSelect, PlanSheetViewer, + ScheduleDetailCoilBind, }, dicts: ['coil_quality_status', 'coil_abnormal_position', 'coil_abnormal_code', 'coil_abnormal_degree', 'coil_business_purpose'], data() { diff --git a/klp-ui/src/views/wms/coil/panels/do.vue b/klp-ui/src/views/wms/coil/panels/do.vue index 0ddc5e4a0..dc0652cde 100644 --- a/klp-ui/src/views/wms/coil/panels/do.vue +++ b/klp-ui/src/views/wms/coil/panels/do.vue @@ -11,6 +11,7 @@ :name="tab.value">

待领物料列表

+ 刷新
@@ -309,6 +310,7 @@ import CoilCard from '@/components/KLPService/Renderer/CoilCard.vue' import LabelRender from './LabelRender/index.vue' import StepSplit from './stepSplit.vue' import ExceptionManager from '../components/ExceptionManager' +import ScheduleDetailCoilBind from '../components/ScheduleDetailCoilBind' import { getCoilTagPrintType } from '@/views/wms/coil/js/coilPrint' export default { @@ -334,7 +336,8 @@ export default { CoilCard, LabelRender, StepSplit, - ExceptionManager + ExceptionManager, + ScheduleDetailCoilBind }, data() { return { diff --git a/klp-ui/src/views/wms/coil/split.vue b/klp-ui/src/views/wms/coil/split.vue index 695738002..8ae7ccd0a 100644 --- a/klp-ui/src/views/wms/coil/split.vue +++ b/klp-ui/src/views/wms/coil/split.vue @@ -25,6 +25,7 @@
母卷信息 +
@@ -377,6 +378,7 @@ import WarehouseSelect from "@/components/KLPService/WarehouseSelect"; import TimeInput from "@/components/TimeInput"; import AbnormalForm from './components/AbnormalForm'; import PlanSheetViewer from './components/PlanSheetViewer.vue'; +import ScheduleDetailCoilBind from './components/ScheduleDetailCoilBind' import { generateCoilNoPrefix } from "@/utils/coil/coilNo"; import ContractSelect from "@/components/KLPService/ContractSelect"; @@ -391,6 +393,7 @@ export default { AbnormalForm, ContractSelect, PlanSheetViewer, + ScheduleDetailCoilBind, }, dicts: ['coil_quality_status', 'coil_abnormal_position', 'coil_abnormal_code', 'coil_abnormal_degree', 'coil_business_purpose'], data() { diff --git a/klp-ui/src/views/wms/coil/typing.vue b/klp-ui/src/views/wms/coil/typing.vue index fc3c10087..99e8da2eb 100644 --- a/klp-ui/src/views/wms/coil/typing.vue +++ b/klp-ui/src/views/wms/coil/typing.vue @@ -30,9 +30,10 @@
-
+
{{ '更新信息' }}
+ 暂存内容 保存更新
@@ -405,6 +406,7 @@ import ContractSelect from "@/components/KLPService/ContractSelect"; import L2MatchPanel from './panels/L2MatchPanel.vue' import DrMatchPanel from './panels/DrMatchPanel.vue'; import PlanSheetViewer from './components/PlanSheetViewer.vue'; +import ScheduleDetailCoilBind from './components/ScheduleDetailCoilBind' // actionType -> 产线名称映射 @@ -431,6 +433,7 @@ export default { L2MatchPanel, DrMatchPanel, PlanSheetViewer, + ScheduleDetailCoilBind, }, dicts: ['coil_quality_status', 'coil_abnormal_position', 'coil_abnormal_code', 'coil_abnormal_degree', 'coil_business_purpose'], data() { diff --git a/klp-ui/src/views/wms/post/aps/requirement.vue b/klp-ui/src/views/wms/post/aps/requirement.vue index 09fc0eac1..6226376cf 100644 --- a/klp-ui/src/views/wms/post/aps/requirement.vue +++ b/klp-ui/src/views/wms/post/aps/requirement.vue @@ -158,7 +158,7 @@
{{ order.orderCode || ('订单 #' + order.orderId) }} - +
@@ -399,6 +399,7 @@ import { delRequirement, addRel, delRel, + listRel, addRequirementDetail, updateRequirementDetail, delRequirementDetail @@ -628,7 +629,8 @@ export default { searchBindOrders() { this.bindSearchLoading = true listCrmOrder(this.bindQuery).then(res => { - this.candidateOrderList = res.rows || [] + const boundOrderIds = (this.currentReq?.orderList || []).map(o => o.orderId) + this.candidateOrderList = (res.rows || []).filter(o => !boundOrderIds.includes(o.orderId)) }).catch(() => { this.candidateOrderList = [] }).finally(() => { @@ -672,65 +674,87 @@ export default { } this.bindBtnLoading = true const scheduleId = this.currentReq.scheduleId - const allPromises = [] - this.selectedBindOrders.forEach(order => { - // 1. 创建订单关联 - allPromises.push( - addRel({ - orderId: order.orderId, - scheduleId: scheduleId, - relWeight: null, - remark: '' - }) - ) - - // 2. 解析 productContent,每行产品创建一条排产明细 - if (order.productContent) { - const parsed = parseProductContent(order.productContent) - const products = parsed.products || [] - const productType = parsed.productName || '' - products.forEach(prod => { - allPromises.push( - addRequirementDetail({ - scheduleId: scheduleId, - orderDetailId: order.orderId, - spec: prod.spec || '', - material: prod.material || '', - scheduleWeight: prod.quantity || 0, - productType: productType, - remark: prod.remark || '' - }) - ) - }) - } + const orderTasks = this.selectedBindOrders.map(order => { + return addRel({ + orderId: order.orderId, + scheduleId: scheduleId, + relWeight: null, + remark: '' + }).then(() => { + if (!order.productContent) return + let products = [] + let productType = '' + try { + const parsed = parseProductContent(order.productContent) + products = (parsed.products || []).filter(p => p.spec && p.material) + productType = parsed.productName || '' + } catch (e) { + return + } + if (products.length === 0) return + const detailPromises = products.map(prod => + addRequirementDetail({ + scheduleId: scheduleId, + orderDetailId: order.orderId, + spec: prod.spec, + material: prod.material, + scheduleWeight: prod.quantity || 0, + productType: productType, + remark: prod.remark || '' + }) + ) + return Promise.all(detailPromises) + }) }) - Promise.all(allPromises).then(() => { - this.$modal.msgSuccess('绑定成功,排产明细已生成') + Promise.allSettled(orderTasks).then(results => { + const successCount = results.filter(r => r.status === 'fulfilled').length + const failCount = results.filter(r => r.status === 'rejected').length this.bindDialogVisible = false this.handleReqClick(this.currentReq) - }).catch(() => { - this.$modal.msgError('绑定或创建明细失败') + if (failCount === 0) { + this.$modal.msgSuccess(`成功绑定 ${successCount} 个订单,排产明细已生成`) + } else if (successCount === 0) { + this.$modal.msgError('绑定失败,请重试') + } else { + this.$modal.msgWarning(`成功绑定 ${successCount} 个,${failCount} 个失败`) + } }).finally(() => { this.bindBtnLoading = false }) }, - handleUnbindByOrderId(orderId) { - const order = (this.currentReq.orderList || []).find(o => o.orderId === orderId) - if (!order) { - this.$message.warning('未找到关联记录') - return - } - this.$confirm('确认解绑该订单吗?', '提示', { + handleUnbindByOrderId(order) { + this.$confirm('确认解绑该订单吗?解绑将同时删除对应的排产明细。', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { - delRel(order.relId || orderId).then(() => { + const tasks = [] + // 1. 删除该订单关联的排产明细 + const detailIds = this.requirementDetailList + .filter(d => d.orderDetailId === order.orderId) + .map(d => d.scheduleDetailId) + detailIds.forEach(id => { + tasks.push(delRequirementDetail(id)) + }) + // 2. 查询并删除关联表记录 + tasks.push( + listRel({ orderId: order.orderId, scheduleId: this.currentReq.scheduleId }).then(res => { + const rows = res.rows || [] + if (rows.length === 0) { + throw new Error('未找到关联记录') + } + const relId = rows[0].id || rows[0].relId + return delRel(relId) + }) + ) + Promise.all(tasks).then(() => { this.$modal.msgSuccess('解绑成功') this.handleReqClick(this.currentReq) + }).catch(() => { + this.$modal.msgError('解绑失败') }) }).catch(() => { }) }, diff --git a/klp-ui/src/views/wms/post/aps/schedule.vue b/klp-ui/src/views/wms/post/aps/schedule.vue index 06d984e7e..726abd298 100644 --- a/klp-ui/src/views/wms/post/aps/schedule.vue +++ b/klp-ui/src/views/wms/post/aps/schedule.vue @@ -17,22 +17,22 @@ -
+
{{ summaryText }}
-
+
待审核产需单明细 共 {{ pendingScheduleList.length }} 个产需单
-
+
-
+
@@ -131,7 +131,7 @@ -
+
规格 材质 @@ -140,9 +140,9 @@ 备注
{{ d.spec }} @@ -162,34 +162,84 @@
-
+
已排产明细 - - 共 {{ scheduledItemList.length }} 条 - +
+ + 共 {{ scheduledItemList.length }} 条 + + + + 合并选中项 ({{ selectedItems.length }}) + + + + + 合并选中项 + + +
-
+
- - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -237,33 +287,329 @@ - - - - {{ editForm.scheduleNo }} - - - - - - - - - - - - - - - - + + + + + + {{ editForm.scheduleNo }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ 选中 {{ mergeForm.itemCount }} 条明细,请选择一个作为合并模板,下方字段将自动填充,您可修改后确认合并。 +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ 合并后 schedule_detail_ids:{{ mergeForm.scheduleDetailIds }} +
+ +
@@ -275,9 +621,10 @@ import { import { getCrmOrderInfo, listScheduleItem, - addScheduleItem, updateScheduleItem, - delScheduleItem + delScheduleItem, + receiveScheduleItem, + mergeScheduleItem } from '@/api/aps/schedule' export default { @@ -301,6 +648,24 @@ export default { // 已排产 scheduledItemList: [], + selectedItems: [], + sourceColorMap: {}, + + // 合并对话框 + mergeDialogVisible: false, + mergeBtnLoading: false, + mergeTemplateIndex: 0, + mergeSourceRows: [], + mergeForm: { + itemCount: 0, scheduleNo: '', customerName: '', spec: '', material: '', + scheduleWeight: 0, productType: '', productItem: '', businessUser: '', + businessPhone: '', deliveryCycle: undefined, usePurpose: '', + thicknessTolerance: '', widthTolerance: '', surfaceQuality: '', + surfaceTreatment: '', innerDiameter: '', outerDiameter: '', + packReq: '', cutEdgeReq: '', singleCoilWeight: '', + weightDeviation: '', otherTechReq: '', paymentDesc: '', + remark: '', scheduleDetailIds: '' + }, // 驳回 rejectDialogVisible: false, @@ -322,20 +687,47 @@ export default { drillOrder: null } }, + watch: { + activeTab() { + this.selectedItems = [] + } + }, created() { this.handleQuery() }, methods: { getEmptyEditForm() { return { - itemId: undefined, scheduleId: undefined, scheduleNo: '', + prodDate: '', + scheduleStatus: undefined, + totalPlanWeight: undefined, + relContractNo: '', + businessUser: '', + businessPhone: '', + orderDate: '', + customerName: '', + deliveryCycle: undefined, + usePurpose: '', + productType: '', + thicknessTolerance: '', + widthTolerance: '', + surfaceQuality: '', + surfaceTreatment: '', + innerDiameter: '', + outerDiameter: '', + packReq: '', + cutEdgeReq: '', + singleCoilWeight: '', + weightDeviation: '', + otherTechReq: '', + paymentDesc: '', spec: '', material: '', scheduleWeight: 0, - productType: '', - customerName: '', + productItem: '', + rowRemark: '', remark: '' } }, @@ -386,40 +778,18 @@ export default { }, handleAccept(sch) { + const details = sch.detailList || [] + if (details.length === 0) { + this.$message.warning('该产需单无可排产的明细') + return + } this.$confirm( - `确认接收产需单「${sch.scheduleNo}」的全部排产明细吗?`, + `确认接收产需单「${sch.scheduleNo}」的全部排产明细吗?共 ${details.length} 条明细`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' } ).then(() => { - const details = sch.detailList || [] - if (details.length === 0) { - this.$message.warning('该产需单无可排产的明细') - return - } - - const promises = details.map(detail => - addScheduleItem({ - scheduleId: sch.scheduleId, - scheduleNo: sch.scheduleNo, - spec: detail.spec || '', - material: detail.material || '', - scheduleWeight: detail.scheduleWeight || 0, - productType: sch.productType || detail.productType || '', - customerName: sch.customerName || '', - remark: detail.remark || '' - }) - ) - - // 更新产需单状态为 2(已接收) - promises.push( - updateRequirement({ - scheduleId: sch.scheduleId, - scheduleStatus: 2 - }) - ) - this.$message.info('正在处理接收...') - Promise.all(promises).then(() => { + receiveScheduleItem(sch.scheduleId).then(() => { this.$modal.msgSuccess('接收成功,排产明细已写入') this.queryPending() }).catch(() => { @@ -463,12 +833,24 @@ export default { queryScheduled() { this.schLoading = true this.scheduledItemList = [] + this.sourceColorMap = {} + this.selectedItems = [] listScheduleItem({ prodDate: this.queryDate, pageNum: 1, pageSize: 999 }).then(res => { - this.scheduledItemList = res.rows || [] - const totalWeight = this.scheduledItemList.reduce((sum, d) => sum + (parseFloat(d.scheduleWeight) || 0), 0) + this.scheduledItemList = (res.rows || []).sort((a, b) => (a.scheduleNo || '').localeCompare(b.scheduleNo || '')) + try { + this.sourceColorMap = this.buildGroupColorMap(this.scheduledItemList) + } catch (e) { + console.error('buildGroupColorMap error:', e) + this.sourceColorMap = {} + } + const totalWeight = this.scheduledItemList.reduce((sum, d) => { + const w = parseFloat(d.scheduleWeight) + return sum + (isNaN(w) ? 0 : w) + }, 0) this.summaryText = `共 ${this.scheduledItemList.length} 条明细,排产总吨数 ${totalWeight.toFixed(3)} 吨` - }).catch(() => { + }).catch((e) => { + console.error('queryScheduled error:', e) this.scheduledItemList = [] this.summaryText = '' }).finally(() => { @@ -504,7 +886,7 @@ export default { cancelButtonText: '取消', type: 'warning' }).then(() => { - delScheduleItem(row.itemId || row.scheduleItemId).then(() => { + delScheduleItem(row.scheduleId).then(() => { this.$modal.msgSuccess('删除成功') this.queryScheduled() }).catch(() => { @@ -513,6 +895,97 @@ export default { }).catch(() => {}) }, + // ====== 合并 ====== + handleSelectionChange(rows) { + this.selectedItems = rows + }, + + getItemRowClassName({ row }) { + const key = row.scheduleNo + if (!key) return '' + const colorIndex = this.sourceColorMap[key] + return colorIndex !== undefined ? `merge-source-${colorIndex}` : '' + }, + + buildGroupColorMap(list) { + const map = {} + let colorIdx = 0 + list.forEach(item => { + const key = item.scheduleNo + if (!key) return + if (!(key in map)) { + map[key] = colorIdx % 5 + colorIdx++ + } + }) + return map + }, + + handleMergePrepare() { + if (this.selectedItems.length < 2) { + this.$message.warning('请至少选择2条排产明细进行合并') + return + } + this.mergeSourceRows = [...this.selectedItems] + this.mergeTemplateIndex = 0 + this.pickMergeTemplate(this.mergeSourceRows[0]) + this.mergeDialogVisible = true + }, + + pickMergeTemplate(row) { + const idx = this.mergeSourceRows.indexOf(row) + if (idx >= 0) this.mergeTemplateIndex = idx + this.mergeForm = { + itemCount: this.mergeSourceRows.length, + scheduleNo: row.scheduleNo || '', + customerName: row.customerName || '', + spec: row.spec || '', + material: row.material || '', + scheduleWeight: this.mergeSourceRows.reduce((sum, r) => sum + (parseFloat(r.scheduleWeight) || 0), 0), + productType: row.productType || '', + productItem: row.productItem || '', + businessUser: row.businessUser || '', + businessPhone: row.businessPhone || '', + deliveryCycle: row.deliveryCycle, + usePurpose: row.usePurpose || '', + thicknessTolerance: row.thicknessTolerance || '', + widthTolerance: row.widthTolerance || '', + surfaceQuality: row.surfaceQuality || '', + surfaceTreatment: row.surfaceTreatment || '', + innerDiameter: row.innerDiameter || '', + outerDiameter: row.outerDiameter || '', + packReq: row.packReq || '', + cutEdgeReq: row.cutEdgeReq || '', + singleCoilWeight: row.singleCoilWeight || '', + weightDeviation: row.weightDeviation || '', + otherTechReq: row.otherTechReq || '', + paymentDesc: row.paymentDesc || '', + remark: row.remark || '', + scheduleDetailIds: this.mergeSourceRows.map(r => r.scheduleDetailIds || '').filter(Boolean).join(',') + } + }, + + confirmMerge() { + this.mergeBtnLoading = true + const ids = this.mergeSourceRows.map(r => r.scheduleId) + const mergedBo = { + ...this.mergeForm, + prodDate: this.queryDate, + scheduleStatus: 2 + } + delete mergedBo.itemCount + mergeScheduleItem({ ids, mergedBo }).then(() => { + this.$modal.msgSuccess(`合并成功:${ids.length} 条明细合并为 1 条`) + this.mergeDialogVisible = false + this.selectedItems = [] + this.queryScheduled() + }).catch(() => { + this.$modal.msgError('合并失败') + }).finally(() => { + this.mergeBtnLoading = false + }) + }, + // ====== 下钻 & 辅助方法 ====== scheduleTotalWeight(sch) { const details = sch.detailList || [] @@ -594,6 +1067,7 @@ export default { flex: 1; overflow: auto; min-height: 0; + overflow-x: auto; } } @@ -830,4 +1304,21 @@ export default { color: $aps-text-muted; font-size: 12px; } + +// ====== 合并候选行颜色高亮(同一源排产单 → 同色) ====== +::v-deep .el-table__body tr.merge-source-0 > td { + background-color: #f0f5ff !important; +} +::v-deep .el-table__body tr.merge-source-1 > td { + background-color: #f6ffed !important; +} +::v-deep .el-table__body tr.merge-source-2 > td { + background-color: #fff7e6 !important; +} +::v-deep .el-table__body tr.merge-source-3 > td { + background-color: #f9f0ff !important; +} +::v-deep .el-table__body tr.merge-source-4 > td { + background-color: #fff0f6 !important; +}