# 连续制造业流程优化技术方案 **文档版本**: V2.0 **创建日期**: 2025-11-17 **更新日期**: 2025-11-17 **负责人**: 周启威 **审核状态**: 待审核 --- ## 📋 需求概述 ### 背景 当前系统中连续制造业和离散制造业在工序路线管理上存在以下问题: 1. 工序时间计算方式单一,不能满足连续生产的同步开工需求 2. 离散制造的转运时间没有明确字段,导致时间计算不够精确 3. 连续生产未绑定工序路线,缺乏标准化流程管理 4. 销售订单可混合不同制造类型的物料,导致生产混乱 5. 工单显示信息不完整,连续制造工序信息缺失 ### 优化目标 1. **工序路线制造类型标识**:在工序路线上明确标识制造类型(离散/连续) 2. **时间字段差异化**:离散制造使用转运时间,连续制造使用等待开始时间 3. **连续生产标准化**:连续生产必须绑定工序路线,统一流程管理 4. **物料与工序路线匹配**:物料制造类型与工序路线制造类型必须一致 5. **销售订单校验**:禁止在同一销售订单中混合不同制造类型的物料 6. **工单信息完善**:连续制造工单显示完整的工序信息 ### 核心原则 ⚠️ **重要约束**: - ✅ 不修改主流程逻辑 - ✅ 不影响现有离散制造功能 - ✅ 向下兼容,支持数据迁移 - ✅ 最小化代码侵入性 --- ## 🗂️ 数据库设计 ### 1. 工序路线主表扩展 (pro_route) **新增字段**: | 字段名 | 类型 | 长度 | 默认值 | 说明 | |--------|------|------|--------|------| | `manufacture_type` | VARCHAR | 20 | 'DISCRETE' | 制造类型 | **字段值说明**: #### manufacture_type(制造类型) - **DISCRETE**(离散制造,默认值) - 工序按**顺序执行**(顺序推进模式) - 工序1结束时间 = 开始时间 + 工序1持续时间 - 工序2开始时间 = 工序1结束时间 + **转运时间** - 工序2结束时间 = 工序2开始时间 + 工序2持续时间 - 使用 `transfer_time` 字段 - **CONTINUOUS**(连续制造) - 所有工序**同时开始**(同步开工模式) - 工序开始时间 = 生产起始时间 + **等待开始时间** - 工序结束时间 = 工序开始时间 + 工序持续时间 - 使用 `wait_start_time` 字段 **设计理念**: - ✅ **制造类型直接决定时间计算模式**,无需额外字段 - ✅ 离散制造 = 顺序推进,连续制造 = 同步开工 - ✅ 简化配置,减少理解成本 **SQL DDL**: ```sql ALTER TABLE `pro_route` ADD COLUMN `manufacture_type` VARCHAR(20) DEFAULT 'DISCRETE' COMMENT '制造类型: DISCRETE=离散制造(顺序推进), CONTINUOUS=连续制造(同步开工)' AFTER `status`; ``` --- ### 2. 工序路线明细表扩展 (pro_route_process) **新增字段**: | 字段名 | 类型 | 长度 | 默认值 | 说明 | |--------|------|------|--------|------| | `transfer_time` | BIGINT | - | 0 | 转运时间(秒,离散制造) | | `wait_start_time` | BIGINT | - | 0 | 等待开始时间(秒,连续制造) | **字段说明**: #### transfer_time(转运时间 - 离散制造专用) - **适用场景**:离散制造(DISCRETE) - **含义**:前一道工序完成后,转运到当前工序所需的时间 - **计算公式**:下一工序开始时间 = 当前工序结束时间 + 转运时间 - **连续制造**:此字段忽略不计 #### wait_start_time(等待开始时间 - 连续制造专用) - **适用场景**:连续制造(CONTINUOUS) - **含义**:从生产起始时间开始,等待多久后该工序才开始 - **计算公式**:工序开始时间 = 生产起始时间 + 等待开始时间 - **离散制造**:此字段忽略不计 - **第一道工序**:通常为 0(立即开始) **SQL DDL**: ```sql ALTER TABLE `pro_route_process` ADD COLUMN `transfer_time` BIGINT DEFAULT 0 COMMENT '转运时间(秒),离散制造时从上一工序到本工序的转运耗时' AFTER `duration`, ADD COLUMN `wait_start_time` BIGINT DEFAULT 0 COMMENT '等待开始时间(秒),连续制造时从生产起始时间的延迟' AFTER `transfer_time`; ``` --- ### 3. 数据迁移脚本 **目的**:确保现有数据兼容新字段 ```sql -- 为现有工序路线设置默认值(离散制造) UPDATE `pro_route` SET `manufacture_type` = 'DISCRETE' WHERE `manufacture_type` IS NULL; -- 为现有工序明细设置默认转运时间和等待开始时间为0 UPDATE `pro_route_process` SET `transfer_time` = 0, `wait_start_time` = 0 WHERE `transfer_time` IS NULL OR `wait_start_time` IS NULL; ``` --- ## 💻 Java 代码设计 ### 1. 实体类扩展 #### Route.java 扩展 ```java /** * 制造类型 * DISCRETE: 离散制造(顺序推进,默认) * CONTINUOUS: 连续制造(同步开工) */ @Excel(name = "制造类型") private String manufactureType; /** * 判断是否为离散制造 */ @TableField(exist = false) public boolean isDiscrete() { return "DISCRETE".equals(this.manufactureType) || this.manufactureType == null; } /** * 判断是否为连续制造 */ @TableField(exist = false) public boolean isContinuous() { return "CONTINUOUS".equals(this.manufactureType); } ``` #### RouteProcess.java 扩展 ```java /** * 转运时间(单位:秒) * 仅在离散制造下生效 */ @Excel(name = "转运时间(秒)") private Long transferTime; /** * 等待开始时间(单位:秒) * 仅在连续制造下生效 */ @Excel(name = "等待开始时间(秒)") private Long waitStartTime; /** * 获取转运时间(如果为null则返回0) */ @TableField(exist = false) public Long getTransferTimeOrZero() { return transferTime != null ? transferTime : 0L; } /** * 获取等待开始时间(如果为null则返回0) */ @TableField(exist = false) public Long getWaitStartTimeOrZero() { return waitStartTime != null ? waitStartTime : 0L; } ``` --- ### 2. 时间计算工具类 **新增工具类**: `RouteTimeCalculator.java` **位置**: `cn.sourceplan.production.util.RouteTimeCalculator` **职责**: 封装工序路线的时间计算逻辑 **核心方法**: ```java package cn.sourceplan.production.util; import cn.sourceplan.production.domain.Route; import cn.sourceplan.production.domain.RouteProcess; import java.util.Date; import java.util.List; /** * 工序路线时间计算工具类 * * @author 周启威 * @date 2025-11-17 */ public class RouteTimeCalculator { /** * 计算工序的开始和结束时间 * * @param route 工序路线 * @param processes 工序列表(已按sort排序) * @param productionStartTime 生产起始时间 * @return 返回每个工序的时间信息列表 */ public static List calculateProcessTimes( Route route, List processes, Date productionStartTime) { if (route.isContinuous()) { // 连续制造:同步开工模式 return calculateContinuousTimes(processes, productionStartTime); } else { // 离散制造:顺序推进模式 return calculateDiscreteTimes(processes, productionStartTime); } } /** * 连续制造:同步开工模式(使用等待开始时间) */ private static List calculateContinuousTimes( List processes, Date startTime) { List result = new ArrayList<>(); for (RouteProcess process : processes) { ProcessTimeInfo info = new ProcessTimeInfo(); info.setProcess(process); // 工序开始时间 = 生产起始时间 + 等待开始时间 long waitMs = process.getWaitStartTimeOrZero() * 1000; Date processStartTime = new Date(startTime.getTime() + waitMs); info.setStartTime(processStartTime); // 结束时间 = 开始时间 + 持续时间 long durationMs = process.getDuration() != null ? process.getDuration() * 1000 : 0; info.setEndTime(new Date(processStartTime.getTime() + durationMs)); result.add(info); } return result; } /** * 离散制造:顺序推进模式(使用转运时间) */ private static List calculateDiscreteTimes( List processes, Date startTime) { List result = new ArrayList<>(); Date currentTime = startTime; for (RouteProcess process : processes) { ProcessTimeInfo info = new ProcessTimeInfo(); info.setProcess(process); info.setStartTime(currentTime); // 结束时间 = 开始时间 + 持续时间 long durationMs = process.getDuration() != null ? process.getDuration() * 1000 : 0; Date endTime = new Date(currentTime.getTime() + durationMs); info.setEndTime(endTime); // 下一工序开始时间 = 当前工序结束时间 + 转运时间 long transferMs = process.getTransferTimeOrZero() * 1000; currentTime = new Date(endTime.getTime() + transferMs); result.add(info); } return result; } /** * 工序时间信息内部类 */ public static class ProcessTimeInfo { private RouteProcess process; private Date startTime; private Date endTime; // getter/setter省略 } } ``` --- ### 3. AutoCompleteServiceImpl 改造方案 **改造位置**: `AutoCompleteServiceImpl.generateWorkOrders()` 方法 **改造原则**: - ✅ 使用工具类封装时间计算逻辑 - ✅ 不修改现有离散制造逻辑 - ✅ 通过工序路线配置自动识别制造类型 **核心改造点**: ```java // 原有代码(第667-703行) // ========== 第一步:收集所有工序的持续时间,正向计算时间 ========== List processTimeInfos = new ArrayList<>(); // ... 现有逻辑 // ====== 改造后代码 ====== // ========== 第一步:使用工具类计算工序时间 ========== // 1. 查询工序路线信息(获取时间计算模式) Route route = routeMapper.selectById(dto.getRouteId()); if (route == null) { throw new RuntimeException("工序路线不存在"); } // 2. 查询工序列表并排序 List routeProcesses = routeProcessMapper.selectList( new QueryWrapper() .eq("route_id", dto.getRouteId()) .orderByAsc("sort") ); // 3. 解析生产开始时间 Date productionStartTime; if (StringUtils.isNotBlank(dto.getProductionStartTime())) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); productionStartTime = sdf.parse(dto.getProductionStartTime()); } else { productionStartTime = new Date(); } // 4. 使用工具类计算时间(自动适配离散/连续模式) List calculatedTimes = RouteTimeCalculator.calculateProcessTimes(route, routeProcesses, productionStartTime); // 5. 构建 ProcessTimeInfo 列表(保持与现有代码兼容) List processTimeInfos = new ArrayList<>(); for (int i = 0; i < dto.getProcessConfigs().size(); i++) { ProcessConfigDTO config = dto.getProcessConfigs().get(i); RouteTimeCalculator.ProcessTimeInfo calcInfo = calculatedTimes.get(i); ProcessTimeInfo timeInfo = new ProcessTimeInfo(); timeInfo.config = config; timeInfo.startTime = calcInfo.getStartTime(); timeInfo.duration = calcInfo.getProcess().getDuration(); processTimeInfos.add(timeInfo); } // 后续逻辑保持不变... ``` **改造说明**: 1. **兼容性**: 保持现有 `ProcessTimeInfo` 结构不变,只改造时间计算部分 2. **自动识别**: 通过工序路线的 `timeCalculationMode` 自动选择计算方式 3. **向下兼容**: 现有工序路线默认为 `SEQUENTIAL` 模式,行为不变 --- ### 4. 连续生产改造方案 **改造位置**: `AutoCompleteServiceImpl.autoCompleteContinuous()` 方法 **改造内容**: #### 4.1 允许连续生产绑定工序路线 ```java // 原代码(第278行) workOrder.setRouteId(null); // 连续制造不需要工序路线 // 改造后 workOrder.setRouteId(dto.getRouteId()); // 连续制造也可绑定工序路线 ``` #### 4.2 连续制造报工逻辑说明 **特点**: - ✅ 连续制造只有**一个工单**(不分工序分录) - ✅ 可以**多次报工**(针对同一个工单) - ✅ 每次报工时间都是**最后一道工序结束时间** **业务场景示例**: ``` 销售订单: 5吨产品 工单: 1个(连续制造工单,绑定工序路线) 报工记录: - 早班报工: 3吨,报工时间 = 生产开始时间 + 最后工序等待时间 + 最后工序持续时间 - 中班报工: 2吨,报工时间 = 生产开始时间 + 最后工序等待时间 + 最后工序持续时间 ``` **代码改造**: ```java // 连续生产必须绑定工序路线 if (dto.getRouteId() == null) { throw new RuntimeException("连续制造必须选择工序路线"); } // 连续制造不创建工序分录,只创建工单 // 工单的 currentProcess 字段显示所有工序名称 Route route = routeMapper.selectById(dto.getRouteId()); if (route != null && route.isContinuous()) { List processes = routeProcessMapper.selectList( new QueryWrapper() .eq("route_id", route.getId()) .orderByAsc("sort") ); String processNames = processes.stream() .map(RouteProcess::getProcessName) .collect(Collectors.joining("-")); workOrder.setCurrentProcess(processNames); } ``` #### 4.3 报工时间计算(连续制造) **关键修改**: 连续制造的报工时间需要改为最后一道工序的完成时间 ```java // 获取最后一道工序的时间信息 RouteProcess lastProcess = routeProcesses.get(routeProcesses.size() - 1); // 报工时间 = 生产起始时间 + 最后工序的等待开始时间 + 最后工序的持续时间 Date productionStartTime = DateUtils.parseDate(dto.getProductionStartTime()); long waitMs = lastProcess.getWaitStartTimeOrZero() * 1000; long durationMs = (lastProcess.getDuration() != null ? lastProcess.getDuration() : 0) * 1000; Date reportTime = new Date(productionStartTime.getTime() + waitMs + durationMs); report.setReportTime(reportTime); ``` --- ### 5. 业务校验逻辑 #### 5.1 物料与工序路线制造类型匹配校验 **位置**: 一键完成功能、手动创建工单等涉及工序路线选择的地方 **校验逻辑**: ```java /** * 校验物料制造类型与工序路线是否匹配 */ private void validateMaterialAndRoute(Material material, Route route) { if (material == null || route == null) { return; } String materialType = material.getManufactureType(); String routeType = route.getManufactureType(); // 如果物料是离散制造,只能选择离散制造的工序路线 if ("DISCRETE".equals(materialType) && !"DISCRETE".equals(routeType)) { throw new RuntimeException("离散制造的物料只能选择离散制造的工序路线"); } // 如果物料是连续制造,只能选择连续制造的工序路线 if ("CONTINUOUS".equals(materialType) && !"CONTINUOUS".equals(routeType)) { throw new RuntimeException("连续制造的物料只能选择连续制造的工序路线"); } } ``` **应用场景**: - 一键完成销售订单时 - 手动创建生产工单时 - 修改工单的工序路线时 #### 5.2 销售订单混合制造类型校验 **位置**: 销售订单保存时 **校验逻辑**: ```java /** * 校验销售订单明细是否混合了不同制造类型的物料 */ @Override public void validateSaleOrderEntries(List entries) { if (entries == null || entries.isEmpty()) { return; } // 收集所有物料ID List materialIds = entries.stream() .map(SalOrderEntry::getMaterialId) .filter(Objects::nonNull) .distinct() .collect(Collectors.toList()); if (materialIds.isEmpty()) { return; } // 批量查询物料 List materials = materialMapper.selectBatchIds(materialIds); Map materialTypeMap = materials.stream() .collect(Collectors.toMap(Material::getId, m -> m.getManufactureType() != null ? m.getManufactureType() : "DISCRETE")); // 检查是否存在多种制造类型 Set types = entries.stream() .map(entry -> materialTypeMap.get(entry.getMaterialId())) .filter(Objects::nonNull) .collect(Collectors.toSet()); if (types.size() > 1) { throw new RuntimeException("一个销售订单不能同时包含离散制造和连续制造的物料,请分开下单"); } } ``` **触发时机**: - 新建销售订单保存时 - 修改销售订单明细时 - 批量导入销售订单时 #### 5.3 连续制造必须绑定工序路线 **位置**: `AutoCompleteServiceImpl.autoCompleteContinuous()` 和 `WorkOrderServiceImpl` **校验逻辑**: ```java // 连续制造必须有工序路线 if ("CONTINUOUS".equals(material.getManufactureType()) && dto.getRouteId() == null) { throw new RuntimeException("连续制造的物料必须选择工序路线"); } ``` --- ### 6. 工单显示信息完善 #### 6.1 当前工序字段补充 **问题**: 连续制造工单的 `currentProcess` 字段为空 **解决方案**: ```java // 在创建连续制造工单时设置当前工序 workOrder.setCurrentProcess("连续制造"); // 或者显示所有工序名称 if (route != null && route.isContinuous()) { List processes = routeProcessMapper.selectList( new QueryWrapper() .eq("route_id", route.getId()) .orderByAsc("sort") ); String processNames = processes.stream() .map(RouteProcess::getProcessName) .collect(Collectors.joining("-")); workOrder.setCurrentProcess(processNames); } ``` #### 6.2 工单详情页工序展示 **文件**: `mes-ui/src/views/mes/production/workOrder/detail.vue` **改造内容**: 1. **字段名称修改**: "查看工序" → "工序" 2. **连续制造工序展示**: ```vue ``` --- ## 🔄 前端界面改造 ### 1. 工序路线管理界面 **文件**: `mes-ui/src/views/mes/production/route/index.vue` **新增表单字段**: ```vue
工序按顺序执行,使用转运时间 所有工序同时开始,使用等待开始时间
``` --- ### 2. 工序明细管理界面 **新增字段**: 转运时间 和 等待开始时间 ```vue ``` **动态显示逻辑**: ```javascript computed: { // 根据制造类型动态显示不同的时间字段 showTransferTime() { return this.routeForm.manufactureType === 'DISCRETE'; }, showWaitStartTime() { return this.routeForm.manufactureType === 'CONTINUOUS'; } } ``` --- ## 📊 业务流程图 ### 离散制造(顺序推进)时间计算 ``` 生产开始时间: 2025-11-17 08:00:00 工序1: 下料 ├─ 开始: 08:00:00 ├─ 持续: 3600秒(1小时) ├─ 结束: 09:00:00 └─ 转运: 600秒(10分钟) 工序2: 焊接 ├─ 开始: 09:10:00 (工序1结束 + 转运时间) ├─ 持续: 7200秒(2小时) ├─ 结束: 11:10:00 └─ 转运: 300秒(5分钟) 工序3: 喷漆 ├─ 开始: 11:15:00 (工序2结束 + 转运时间) ├─ 持续: 10800秒(3小时) └─ 结束: 14:15:00 ``` ### 连续制造(同步开工)时间计算 ``` 生产开始时间: 2025-11-17 08:00:00 工序1: 灌装 ├─ 等待开始时间: 0秒 ├─ 开始: 08:00:00 + 0秒 = 08:00:00 ├─ 持续: 7200秒(2小时) └─ 结束: 10:00:00 工序2: 封盖 ├─ 等待开始时间: 0秒 ├─ 开始: 08:00:00 + 0秒 = 08:00:00 ├─ 持续: 3600秒(1小时) └─ 结束: 09:00:00 工序3: 贴标 ├─ 等待开始时间: 3600秒(1小时,等待封盖完成) ├─ 开始: 08:00:00 + 1小时 = 09:00:00 ├─ 持续: 5400秒(1.5小时) └─ 结束: 10:30:00 一键完成报工时间: 10:30:00 (最后一道工序的结束时间) 说明: 1. 工序1和工序2同时开始(等待时间为0) 2. 工序3等待1小时后开始(等待封盖完成) 3. 连续制造不使用转运时间字段 ``` --- ## ✅ 测试验证方案 ### 1. 单元测试 **测试类**: `RouteTimeCalculatorTest.java` ```java @Test public void testSequentialMode() { // 测试顺序推进模式 // 验证: 工序2开始时间 = 工序1结束时间 + 转运时间 } @Test public void testSynchronizedMode() { // 测试同步开工模式 // 验证: 所有工序开始时间相同 } ``` ### 2. 集成测试 | 测试场景 | 测试点 | 预期结果 | |---------|--------|---------| | 离散制造创建工单 | 顺序推进 + 转运时间 | 工序时间正确计算 | | 连续制造创建工单 | 同步开工 | 所有工序同时开始 | | 数据迁移 | 现有工序路线 | 自动设置为SEQUENTIAL | | 向下兼容 | 不设置时间模式 | 默认使用SEQUENTIAL | ### 3. 回归测试 - ✅ 现有离散制造流程不受影响 - ✅ 现有工单创建功能正常 - ✅ 现有报工流程正常 --- ## 📝 实施计划 ### 阶段一:数据库改造(1天) - [ ] 执行DDL脚本,添加新字段 - [ ] 执行数据迁移脚本 - [ ] 验证现有数据完整性 ### 阶段二:后端改造(2天) - [ ] 扩展实体类 Route 和 RouteProcess - [ ] 创建时间计算工具类 RouteTimeCalculator - [ ] 改造 AutoCompleteServiceImpl - [ ] 单元测试 ### 阶段三:前端改造(1天) - [ ] 工序路线管理界面添加新字段 - [ ] 工序明细界面添加转运时间 - [ ] 联动逻辑实现 ### 阶段四:测试验证(1天) - [ ] 功能测试 - [ ] 集成测试 - [ ] 回归测试 ### 阶段五:上线部署(0.5天) - [ ] 生产环境数据库升级 - [ ] 后端服务部署 - [ ] 前端部署 - [ ] 验证 **总计**: 5.5 工作日 --- ## ⚠️ 风险评估 | 风险项 | 影响程度 | 应对措施 | |--------|---------|---------| | 数据迁移失败 | 高 | 提前备份,准备回滚脚本 | | 现有功能受影响 | 中 | 充分回归测试,灰度发布 | | 性能影响 | 低 | 新增字段已建索引,性能影响可控 | | 用户培训 | 低 | 提供操作文档和视频 | --- ## 📚 附录 ### A. SQL 完整脚本 见配套文件: `2025-11-17_01_周启威_连续制造业流程优化_V2.sql` ### B. 代码文件清单 | 文件路径 | 改动类型 | 说明 | |---------|---------|------| | Route.java | 修改 | 新增manufacture_type字段和判断方法 | | RouteProcess.java | 修改 | 新增transfer_time和wait_start_time字段 | | RouteTimeCalculator.java | 新增 | 时间计算工具类(支持离散/连续模式) | | AutoCompleteServiceImpl.java | 修改 | 使用工具类计算时间、连续制造绑定工序路线 | | SalOrderServiceImpl.java | 修改 | 新增销售订单混合制造类型校验 | | WorkOrderServiceImpl.java | 修改 | 物料与工序路线匹配校验、工单currentProcess补充 | ### C. 数据字典 #### pro_route 表 | 字段名 | 类型 | 说明 | 新增 | |--------|------|------|------| | manufacture_type | VARCHAR(20) | 制造类型: DISCRETE=离散制造(顺序推进), CONTINUOUS=连续制造(同步开工) | ✅ | #### pro_route_process 表 | 字段名 | 类型 | 说明 | 新增 | |--------|------|------|------| | transfer_time | BIGINT | 转运时间(秒),离散制造专用 | ✅ | | wait_start_time | BIGINT | 等待开始时间(秒),连续制造专用 | ✅ | --- ## 📞 联系方式 - **技术负责人**: 周启威 - **文档维护**: AI Assistant - **最后更新**: 2025-11-17 --- **文档状态**: ✅ 待审核 **下一步**: 等待技术评审通过后进入开发阶段