# 连续制造业改进方案 ## 1. 核心思路(5分钟读懂) ### 1.1 改进目标 在现有MES系统基础上,支持连续制造业(化工、钢铁、水泥等)的生产报工流程,实现按时间段/班次报工,兼容现有离散制造业模式。 ### 1.2 核心设计:物料级别的类型标识 **关键点**:在 `md_material`(物料表)中新增2个字段作为"开关" ```sql manufacture_type -- 制造类型: DISCRETE(离散) / CONTINUOUS(连续) report_mode -- 报工模式: PROCESS(按工序) / TIME(按时间) / SHIFT(按班次) / QUANTITY(按产量) / BATCH(按批次) ``` **报工模式说明**: | 报工模式 | 适用场景 | 报工方式 | 示例 | |---------|---------|---------|------| | `PROCESS` | 离散制造业 | 按工序完成报工 | 工序1完成 → 报工1次 | | `TIME` | 连续制造业 | 按固定时间段报工 | 每小时报工一次 | | `SHIFT` | 连续制造业 | 按班次报工 | 每班次结束报工一次 | | `QUANTITY` | 连续制造业 | 按达到的产量报工 | 每生产100吨报工一次 | | `BATCH` | 连续制造业 | 按生产批次报工 | 每完成一个批次报工一次 | **数据流转逻辑**: ``` 物料表设置类型 ↓ 销售订单选择物料(继承物料的类型) ↓ 生成工单时判断物料类型 → 决定工单生成方式 ↓ 报工时判断工单类型 → 决定报工方式 ``` --- ## 2. 完整业务流程(核心) ### 2.1 流程对比 | 环节 | 离散制造业 | 连续-按时间 | 连续-按班次 | 连续-按产量 | 连续-按批次 | |------|-----------|-----------|-----------|-----------|-----------| | **1. 物料设置** | `DISCRETE`
`PROCESS` | `CONTINUOUS`
`TIME` | `CONTINUOUS`
`SHIFT` | `CONTINUOUS`
`QUANTITY` | `CONTINUOUS`
`BATCH` | | **2. 工单生成** | 按工序生成多个
`XS001-P01`, `P02` | 生成一个工单
`XS001` | 生成一个工单
`XS001` | 生成一个工单
`XS001` | 生成一个工单
`XS001` | | **3. 工单分录** | 创建(每个工序) | **不创建** | **不创建** | **不创建** | **不创建** | | **4. 报工方式** | 按工序完成 | 按时间段 | 按班次 | 按产量 | 按批次 | | **5. 报工示例** | 工序1完成 → 报工 | 08:00-09:00
09:00-10:00 | 早班
中班
晚班 | 累计100吨
累计200吨 | 批次1
批次2 | | **6. 报工频率** | 每个工序1次 | 每小时 | 每班次 | 达到产量 | 批次完成 | | **7. 时间段** | 无 | 固定(1小时) | 固定(8小时) | 不固定 | 不固定 | | **8. 班次字段** | 不使用 | 可选填写 | **必填** | 可选填写 | 可选填写 | | **9. 完成判断** | 所有工序完成 | 累计 ≥ 计划 | 累计 ≥ 计划 | 累计 ≥ 计划 | 累计 ≥ 计划 | ### 2.2 详细流程图 #### 流程A:离散制造业(现有逻辑,保持不变) ``` 1. 物料管理 └─ 设置物料:manufacture_type = 'DISCRETE', report_mode = 'PROCESS' └─ 关联工序路线(route_id) 2. 销售订单 └─ 创建订单明细,选择物料 └─ 查询物料信息(包含 manufacture_type, report_mode) 3. 生成工单(关键判断点) └─ 读取物料的 manufacture_type └─ if (manufacture_type == 'DISCRETE') { // 按工序路线生成多个工单 for (每个工序) { 创建工单:batch_number = "XS001-P01", "XS001-P02"... 创建工单分录:关联工序信息 } } 4. 报工 └─ 选择工单分录(work_order_entry_id) └─ 提交报工:报工数量、合格数、不合格数 └─ 更新工单分录状态 └─ 检查:所有工序完成 → 工单完成 ``` #### 流程B:连续制造业(新增逻辑) **共同点**: - 只生成一个工单(不按工序) - 不创建工单分录 - 可以多次报工 - 累计报工数量达到计划数量即完成 **差异点**:根据 `report_mode` 决定报工触发方式 --- ##### B1:按时间报工(TIME) **适用场景**:化工、钢铁等连续生产,按固定时间段统计产量 ``` 1. 物料管理 └─ 设置物料:manufacture_type = 'CONTINUOUS', report_mode = 'TIME' 2. 生成工单 └─ 创建工单:batch_number = "XS001", report_mode = 'TIME' 3. 报工(按时间段) └─ 08:00-09:00 → 报工:100吨 └─ 09:00-10:00 → 报工:120吨 └─ 10:00-11:00 → 报工:110吨 └─ 累计:330吨 4. 报工信息 - report_period_start: 2025-01-11 08:00:00 - report_period_end: 2025-01-11 09:00:00 - shift_name: 早班 - report_quantity: 100 - equipment_id: 10 - downtime_minutes: 10(停机10分钟) ``` --- ##### B2:按班次报工(SHIFT) **适用场景**:三班倒生产,按班次统计产量 ``` 1. 物料管理 └─ 设置物料:manufacture_type = 'CONTINUOUS', report_mode = 'SHIFT' 2. 生成工单 └─ 创建工单:batch_number = "XS001", report_mode = 'SHIFT' 3. 报工(按班次) └─ 早班(08:00-16:00)→ 报工:800吨 └─ 中班(16:00-00:00)→ 报工:750吨 └─ 晚班(00:00-08:00)→ 报工:700吨 └─ 累计:2250吨 4. 报工信息 - report_period_start: 2025-01-11 08:00:00(班次开始) - report_period_end: 2025-01-11 16:00:00(班次结束) - shift_name: 早班(必填) - report_quantity: 800(本班次产量) - equipment_id: 10 - downtime_minutes: 30(本班次停机30分钟) ``` --- ##### B3:按产量报工(QUANTITY) **适用场景**:水泥、食品等,按达到的产量节点报工 ``` 1. 物料管理 └─ 设置物料:manufacture_type = 'CONTINUOUS', report_mode = 'QUANTITY' 2. 生成工单 └─ 创建工单:batch_number = "XS001", report_mode = 'QUANTITY' 3. 报工(按产量节点) └─ 累计达到100吨 → 报工1:100吨(08:00-09:30) └─ 累计达到200吨 → 报工2:100吨(09:30-11:00) └─ 累计达到300吨 → 报工3:100吨(11:00-12:20) └─ 累计:300吨 4. 报工信息 - report_period_start: 2025-01-11 08:00:00(开始时间) - report_period_end: 2025-01-11 09:30:00(达到100吨的时间) - report_quantity: 100(本次报工数量) - shift_name: 早班(可选) - remark: 累计产量达到100吨 ``` --- ##### B4:按批次报工(BATCH) **适用场景**:制药、精细化工等,按生产批次报工 ``` 1. 物料管理 └─ 设置物料:manufacture_type = 'CONTINUOUS', report_mode = 'BATCH' 2. 生成工单 └─ 创建工单:batch_number = "XS001", report_mode = 'BATCH' 3. 报工(按批次) └─ 批次1完成 → 报工1:50吨(批次号:20250111-001) └─ 批次2完成 → 报工2:48吨(批次号:20250111-002) └─ 批次3完成 → 报工3:52吨(批次号:20250111-003) └─ 累计:150吨 4. 报工信息 - report_period_start: 2025-01-11 08:00:00(批次开始) - report_period_end: 2025-01-11 10:30:00(批次完成) - report_sequence: 1(第1个批次) - report_quantity: 50(本批次产量) - shift_name: 早班(可选) - remark: 批次号:20250111-001 ``` --- **四种模式对比**: | 对比项 | 按时间(TIME) | 按班次(SHIFT) | 按产量(QUANTITY) | 按批次(BATCH) | |-------|--------------|---------------|-----------------|---------------| | 报工触发 | 固定时间到达 | 班次结束 | 产量达到节点 | 批次完成 | | 时间段 | 固定(如1小时) | 固定(如8小时) | 不固定 | 不固定 | | 产量 | 不固定 | 不固定 | 固定(如100吨) | 不固定 | | 班次字段 | 可选 | **必填** | 可选 | 可选 | | 适用场景 | 连续稳定生产 | 三班倒生产 | 产量为主要指标 | 批次管理严格 | | 报工频率 | 高(每小时) | 中(每班次) | 中(达到产量) | 低(批次完成) | | 典型行业 | 化工、钢铁 | 制造业三班倒 | 水泥、食品 | 制药、精细化工 | ``` ### 2.3 关键判断点代码逻辑 #### 判断点1:生成工单时(ProWorkorderService.generateWorkOrder) ```java // 1. 查询销售订单明细 SalOrderEntry entry = salOrderEntryMapper.selectById(entryId); // 2. 查询物料信息(关键:获取 manufacture_type) Material material = materialMapper.selectById(entry.getMaterialId()); // 3. 根据制造类型分流 if ("CONTINUOUS".equals(material.getManufactureType())) { // 连续制造业:生成一个工单 return generateContinuousWorkOrder(entry, material); } else { // 离散制造业:按工序生成多个工单(现有逻辑) return generateDiscreteWorkOrders(entry, material); } ``` #### 判断点2:报工时(ProReportService.submitReport) ```java // 1. 根据报工单获取工单信息 Long workOrderId = report.getWorkOrderId() != null ? report.getWorkOrderId() : getWorkOrderIdByEntryId(report.getWorkOrderEntryId()); ProWorkorder workOrder = workorderMapper.selectById(workOrderId); // 2. 根据工单的制造类型分流 if ("CONTINUOUS".equals(workOrder.getManufactureType())) { // 连续制造业报工逻辑 return submitContinuousReport(report, workOrder); } else { // 离散制造业报工逻辑(现有逻辑) return submitDiscreteReport(report, workOrder); } ``` ### 2.4 数据关联关系 #### 离散制造业(现有) ``` md_material (物料) ↓ material_id sal_order_entry (订单明细) ↓ 生成工单 pro_workorder (工单1: P01) ↓ workorder_id pro_workorder_entry (工单分录: 工序1) ↓ work_order_entry_id pro_report (报工单) pro_workorder (工单2: P02) ↓ workorder_id pro_workorder_entry (工单分录: 工序2) ↓ work_order_entry_id pro_report (报工单) ``` #### 连续制造业(新增) ``` md_material (物料) ↓ material_id sal_order_entry (订单明细) ↓ 生成工单 pro_workorder (工单: XS001) ↓ work_order_id (直接关联,跳过工单分录) pro_report (报工单1: 08:00-09:00) pro_report (报工单2: 09:00-10:00) pro_report (报工单3: 10:00-11:00) ... ``` --- ## 3. 设计原则 1. **向后兼容**:不影响现有离散制造业流程 2. **最小改动**:复用现有表结构,新增字段标识 3. **简化逻辑**:连续制造业不使用工单分录,报工直接关联工单 4. **物料驱动**:所有判断逻辑都基于物料的 `manufacture_type` 字段 --- ## 3. 数据库表分析与评估 ### 3.1 现有相关表梳理 #### ✅ 已存在可复用的表 | 表名 | 用途 | 是否满足需求 | |------|------|------------| | `md_material` | 物料主数据 | ✅ 需新增字段 | | `md_workshop` | 车间管理 | ✅ 可直接使用 | | `md_station` | 工位管理 | ✅ 可直接使用 | | `dm_equipment` | 设备管理 | ✅ 可直接使用(已有完整设备表) | | `pro_workorder` | 生产工单 | ✅ 需新增字段 | | `pro_workorder_entry` | 工单分录 | ✅ 连续制造业不使用 | | `pro_report` | 报工单 | ✅ 需新增字段 | | `pro_process` | 工序定义 | ✅ 连续制造业不使用 | | `pro_route` | 工序路线 | ✅ 连续制造业不使用 | | `sal_order` | 销售订单 | ✅ 可直接使用 | | `sal_order_entry` | 订单明细 | ✅ 可直接使用 | #### 📊 设备表详情(dm_equipment) 系统已有完整的设备管理表,包含以下字段: - `id`, `number`, `name` - 基础信息 - `brand`, `specification`, `type` - 设备属性 - `station_id`, `station_name` - 关联工位 - `equipment_status` - 设备状态 - `iot_sn` - 物联网序列号 **结论**:✅ 设备表完全满足需求,无需新建,直接使用 `dm_equipment` ### 3.2 需要新建的表评估 #### ❌ 不需要新建的表 | 表名 | 原因 | |------|------| | ~~设备表~~ | 已有 `dm_equipment` | | ~~班次表~~ | 第一版使用字符串字段 `shift_name` 即可 | | ~~停机记录表~~ | 停机信息直接记录在 `pro_report` 中 | | ~~产线表~~ | 可使用 `md_workshop`(车间)代替 | #### ⚠️ 可选新建的表(后续版本) **1. 班次管理表(md_shift)** - 优先级:P2 ```sql CREATE TABLE `md_shift` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id', `shift_code` varchar(32) NOT NULL COMMENT '班次编码', `shift_name` varchar(64) NOT NULL COMMENT '班次名称', `start_time` time NOT NULL COMMENT '开始时间', `end_time` time NOT NULL COMMENT '结束时间', `is_cross_day` tinyint(1) DEFAULT 0 COMMENT '是否跨天', `sort` int DEFAULT 0 COMMENT '排序', `status` varchar(2) DEFAULT '0' COMMENT '状态', `remark` varchar(255) COMMENT '备注', PRIMARY KEY (`id`), UNIQUE KEY `uk_shift_code` (`shift_code`) ) COMMENT='班次管理表'; ``` **说明**:第一版可以不建,使用字符串字段即可。后续如需班次时间管理再建。 **2. 停机记录表(pro_downtime_record)** - 优先级:P2 ```sql CREATE TABLE `pro_downtime_record` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id', `work_order_id` bigint NOT NULL COMMENT '工单ID', `equipment_id` bigint COMMENT '设备ID', `downtime_start` datetime NOT NULL COMMENT '停机开始时间', `downtime_end` datetime COMMENT '停机结束时间', `downtime_minutes` int COMMENT '停机时长(分钟)', `downtime_type` varchar(32) COMMENT '停机类型', `downtime_reason` varchar(500) COMMENT '停机原因', `handler` varchar(64) COMMENT '处理人', `remark` text COMMENT '备注', `create_time` datetime NOT NULL COMMENT '创建时间', PRIMARY KEY (`id`), KEY `idx_work_order_id` (`work_order_id`), KEY `idx_equipment_id` (`equipment_id`), KEY `idx_downtime_start` (`downtime_start`) ) COMMENT='停机记录表'; ``` **说明**:第一版停机信息记录在 `pro_report` 中即可。后续如需详细停机分析再建独立表。 ### 3.3 结论 **第一版实施方案(最简化)**: - ✅ **只修改3张表**:`md_material`, `pro_workorder`, `pro_report` - ✅ **复用现有表**:`dm_equipment`(设备)、`md_workshop`(车间) - ✅ **不新建表**:班次、停机等信息用字段记录 - ✅ **向后兼容**:所有新增字段都有默认值 **后续优化方向(P2)**: - 如需班次时间管理 → 建 `md_shift` 表 - 如需停机详细分析 → 建 `pro_downtime_record` 表 - 如需产线管理 → 扩展 `md_workshop` 或新建产线表 --- ## 4. 数据库改动(按执行顺序) ### 步骤1:物料表新增字段 > 基于现有 `md_material` 表结构 ```sql -- 标识物料的制造类型和报工配置 ALTER TABLE md_material ADD COLUMN manufacture_type VARCHAR(20) DEFAULT 'DISCRETE' COMMENT '制造类型: DISCRETE-离散, CONTINUOUS-连续', ADD COLUMN report_mode VARCHAR(20) COMMENT '报工模式: PROCESS-按工序, TIME-按时间, SHIFT-按班次, QUANTITY-按产量, BATCH-按批次'; -- 创建索引 CREATE INDEX idx_manufacture_type ON md_material(manufacture_type); ``` **说明**: - `route_id` 字段已存在,用于关联工序路线(离散制造业使用) - 连续制造业的物料可以不设置 `route_id` ### 步骤2:工单表新增字段 > 基于现有 `pro_workorder` 表结构 ```sql -- 支持连续制造业的工单管理 ALTER TABLE pro_workorder ADD COLUMN manufacture_type VARCHAR(20) DEFAULT 'DISCRETE' COMMENT '制造类型: DISCRETE-离散, CONTINUOUS-连续', ADD COLUMN report_mode VARCHAR(20) DEFAULT 'PROCESS' COMMENT '报工模式: PROCESS-按工序, TIME-按时间', ADD COLUMN total_report_count INT DEFAULT 0 COMMENT '总报工次数', ADD COLUMN total_reported_quantity DECIMAL(10,3) DEFAULT 0.000 COMMENT '累计报工数量', ADD COLUMN equipment_id BIGINT COMMENT '关联设备ID', ADD COLUMN equipment_name VARCHAR(255) COMMENT '设备名称', ADD COLUMN actual_start_time DATETIME COMMENT '实际开始时间', ADD COLUMN actual_end_time DATETIME COMMENT '实际结束时间', ADD COLUMN downtime_minutes INT DEFAULT 0 COMMENT '累计停机时间(分钟)'; -- 创建索引 CREATE INDEX idx_manufacture_type ON pro_workorder(manufacture_type); CREATE INDEX idx_equipment_id ON pro_workorder(equipment_id); CREATE INDEX idx_report_mode ON pro_workorder(report_mode); ``` **说明**: - `last_report_time` 字段已存在,无需新增 - 数量字段使用 `DECIMAL(10,3)` 与现有字段保持一致 ### 步骤3:报工单表新增字段 > 基于现有 `pro_report` 表结构 ```sql -- 支持连续制造业的报工记录 ALTER TABLE pro_report ADD COLUMN work_order_id BIGINT COMMENT '工单ID(连续制造业直接关联工单)', ADD COLUMN report_period_start DATETIME COMMENT '报工时段开始', ADD COLUMN report_period_end DATETIME COMMENT '报工时段结束', ADD COLUMN report_sequence INT COMMENT '报工序号', ADD COLUMN shift_name VARCHAR(64) COMMENT '班次名称', ADD COLUMN equipment_id BIGINT COMMENT '设备ID', ADD COLUMN equipment_name VARCHAR(255) COMMENT '设备名称', ADD COLUMN downtime_minutes INT DEFAULT 0 COMMENT '该时段停机时间(分钟)', ADD COLUMN downtime_reason VARCHAR(500) COMMENT '停机原因'; -- 说明:报工人使用现有的 report_user_id 和 report_user_name 字段 -- 创建索引 CREATE INDEX idx_work_order_id ON pro_report(work_order_id); CREATE INDEX idx_report_period ON pro_report(report_period_start, report_period_end); CREATE INDEX idx_shift_name ON pro_report(shift_name); CREATE INDEX idx_equipment_id ON pro_report(equipment_id); ``` **说明**: - `remark` 字段已存在,无需新增 - `qualified_quantity` 和 `unqualified_quantity` 字段已存在 - `quality_status` 字段已存在 ### 步骤4:销售订单明细表新增字段(可选) > 用于标识订单明细的生产状态 ```sql -- 扩展销售订单明细的状态管理 -- status字段已存在,可以使用以下状态值: -- A: 待生产 -- B: 生产中 -- C: 部分完成 -- D: 已完成 -- 无需新增字段,使用现有的status和extend_field即可 ``` ### 步骤5:数据迁移 ```sql -- 为现有数据设置默认值(确保兼容性) UPDATE md_material SET manufacture_type = 'DISCRETE' WHERE manufacture_type IS NULL; UPDATE pro_workorder SET manufacture_type = 'DISCRETE' WHERE manufacture_type IS NULL; UPDATE pro_workorder SET report_mode = 'PROCESS' WHERE report_mode IS NULL; UPDATE pro_workorder SET total_report_count = 0 WHERE total_report_count IS NULL; UPDATE pro_workorder SET total_reported_quantity = 0.000 WHERE total_reported_quantity IS NULL; UPDATE pro_workorder SET downtime_minutes = 0 WHERE downtime_minutes IS NULL; ``` --- ## 4. 代码改动清单(重点:XML改动) ### 4.1 实体类改动 #### Material.java **文件路径**:`com.yavii.domain.Material` ```java // 新增字段 private String manufactureType; // 制造类型: DISCRETE/CONTINUOUS private String reportMode; // 报工模式: PROCESS/TIME private Integer reportInterval; // 报工间隔(分钟) // getter/setter 略 ``` #### ProWorkorder.java **文件路径**:`com.yavii.domain.ProWorkorder` ```java // 新增字段 private String manufactureType; // 制造类型 private String reportMode; // 报工模式 private Integer totalReportCount; // 总报工次数 private BigDecimal totalReportedQuantity; // 累计报工数量 private Long equipmentId; // 设备ID private String equipmentName; // 设备名称 private Date actualStartTime; // 实际开始时间 private Date actualEndTime; // 实际结束时间 private Integer downtimeMinutes; // 累计停机时间 // getter/setter 略 ``` #### ProReport.java **文件路径**:`com.yavii.domain.ProReport` ```java // 新增字段 private Long workOrderId; // 工单ID(连续制造业直接关联) private Date reportPeriodStart; // 报工时段开始 private Date reportPeriodEnd; // 报工时段结束 private Integer reportSequence; // 报工序号 private String shiftName; // 班次名称 private Long equipmentId; // 设备ID private String equipmentName; // 设备名称 private Integer downtimeMinutes; // 停机时间(分钟) private String downtimeReason; // 停机原因 // 说明:报工人使用现有的 reportUserId 和 reportUserName 字段 // getter/setter 略 ``` --- ### 4.2 Mapper XML 改动(核心重点) #### ✅ MaterialMapper.xml **文件路径**:`resources/mapper/MaterialMapper.xml` **改动1:resultMap 增加字段映射** ```xml ``` **改动2:查询SQL 增加字段** ```xml select id, number, name, specification, unit_id, class_id, type_id, status, route_id, manufacture_type, report_mode, remark, create_by, create_time, update_by, update_time from md_material ``` **改动3:插入SQL 增加字段** ```xml insert into md_material number, name, manufacture_type, report_mode, #{number}, #{name}, #{manufactureType}, #{reportMode}, ``` **改动4:更新SQL 增加字段** ```xml update md_material name = #{name}, manufacture_type = #{manufactureType}, report_mode = #{reportMode}, where id = #{id} ``` --- #### ✅ ProWorkorderMapper.xml **文件路径**:`resources/mapper/ProWorkorderMapper.xml` **改动1:resultMap 增加字段映射** ```xml ``` **改动2:查询SQL 增加字段(关键:关联物料表)** ```xml select w.id, w.number, w.batch_number, w.material_id, w.material_name, w.specification, w.material_unit_id, w.material_unit_name, w.quantity, w.begin_pro_date, w.plan_finish_date, w.real_finish_date, w.pro_status, w.current_process, w.last_report_time, w.route_id, w.source_info, w.manufacture_type, w.report_mode, w.total_report_count, w.total_reported_quantity, w.equipment_id, w.equipment_name, w.actual_start_time, w.actual_end_time, w.downtime_minutes, m.manufacture_type as material_manufacture_type, m.report_mode as material_report_mode, w.remark, w.create_by, w.create_time, w.update_by, w.update_time from pro_workorder w left join md_material m on w.material_id = m.id ``` **改动3:插入SQL 增加字段** ```xml insert into pro_workorder number, batch_number, manufacture_type, report_mode, total_report_count, total_reported_quantity, equipment_id, equipment_name, actual_start_time, actual_end_time, downtime_minutes, #{number}, #{batchNumber}, #{manufactureType}, #{reportMode}, #{totalReportCount}, #{totalReportedQuantity}, #{equipmentId}, #{equipmentName}, #{actualStartTime}, #{actualEndTime}, #{downtimeMinutes}, ``` **改动4:更新SQL 增加字段** ```xml update pro_workorder pro_status = #{proStatus}, manufacture_type = #{manufactureType}, report_mode = #{reportMode}, total_report_count = #{totalReportCount}, total_reported_quantity = #{totalReportedQuantity}, equipment_id = #{equipmentId}, equipment_name = #{equipmentName}, actual_start_time = #{actualStartTime}, actual_end_time = #{actualEndTime}, downtime_minutes = #{downtimeMinutes}, last_report_time = #{lastReportTime}, where id = #{id} ``` **改动5:新增查询方法(查询连续制造业工单)** ```xml ``` --- #### ✅ ProReportMapper.xml **文件路径**:`resources/mapper/ProReportMapper.xml` **改动1:resultMap 增加字段映射** ```xml ``` **改动2:查询SQL 增加字段(关键:关联工单表判断制造类型)** ```xml select r.id, r.number, r.work_order_entry_id, r.report_user_id, r.report_user_name, r.report_time, r.report_quantity, r.qualified_quantity, r.unqualified_quantity, r.quality_status, r.workshop_id, r.workshop_name, r.station_id, r.station_name, r.work_order_id, r.report_period_start, r.report_period_end, r.report_sequence, r.shift_name, r.equipment_id, r.equipment_name, r.downtime_minutes, r.downtime_reason, w.number as workorder_number, w.manufacture_type as workorder_manufacture_type, w.material_name as workorder_material_name, r.remark, r.create_by, r.create_time from pro_report r left join pro_workorder w on r.work_order_id = w.id left join pro_workorder_entry e on r.work_order_entry_id = e.id left join pro_workorder w2 on e.workorder_id = w2.id ``` **改动3:插入SQL 增加字段** ```xml insert into pro_report number, work_order_entry_id, work_order_id, report_period_start, report_period_end, report_sequence, shift_name, equipment_id, equipment_name, downtime_minutes, downtime_reason, #{number}, #{workOrderEntryId}, #{workOrderId}, #{reportPeriodStart}, #{reportPeriodEnd}, #{reportSequence}, #{shiftName}, #{equipmentId}, #{equipmentName}, #{downtimeMinutes}, #{downtimeReason}, ``` **改动4:新增查询方法(按工单ID查询报工记录)** ```xml ``` --- #### ✅ SalOrderMapper.xml(可选改动) **文件路径**:`resources/mapper/SalOrderMapper.xml` **改动:查询订单明细时关联物料的制造类型** ```xml select e.id, e.main_id, e.material_id, e.material_name, e.material_specification, e.quantity, e.status, e.delivery_date, m.manufacture_type as material_manufacture_type, m.report_mode as material_report_mode, from sal_order_entry e left join md_material m on e.material_id = m.id ``` --- ### 4.3 Mapper 接口改动 #### ProWorkorderMapper.java ```java /** * 查询连续制造业工单列表 */ List selectContinuousWorkOrders(@Param("proStatus") String proStatus); ``` #### ProReportMapper.java ```java /** * 根据工单ID查询报工记录 */ List selectReportsByWorkOrderId(@Param("workOrderId") Long workOrderId); /** * 检查时间段是否重叠 */ int checkTimeOverlap(@Param("workOrderId") Long workOrderId, @Param("periodStart") Date periodStart, @Param("periodEnd") Date periodEnd); /** * 获取下一个报工序号 */ int getNextSequence(@Param("workOrderId") Long workOrderId); ``` --- ## 5. 业务流程改动 ### 4.1 连续制造业流程 ``` 销售订单 → 生成一个工单(不按工序) → 按时间段报工(多次) → 累计达到计划数量 → 完成 ``` ### 4.2 工单生成逻辑 **离散制造业(保持不变)**: ```java // 按工序路线生成多个工单 for (RouteProcess process : routeProcessList) { WorkOrder workOrder = new WorkOrder(); workOrder.setManufactureType("DISCRETE"); workOrder.setBatchNumber(orderNumber + "-P" + processSort); // ... } ``` **连续制造业(新增)**: ```java // 只生成一个工单 WorkOrder workOrder = new WorkOrder(); workOrder.setManufactureType("CONTINUOUS"); workOrder.setReportMode(material.getReportMode()); // 从物料继承 workOrder.setBatchNumber(orderNumber); // 不使用-P序号 workOrder.setEquipmentId(dto.getEquipmentId()); // 不创建工单分录 ``` ### 4.3 报工逻辑 **离散制造业(保持不变)**: ```java // 按工序报工,关联工单分录 Report report = new Report(); report.setWorkOrderEntryId(entryId); report.setReportQuantity(quantity); ``` **连续制造业(新增)**: ```java // 按时间段报工,直接关联工单 Report report = new Report(); report.setWorkOrderId(workOrderId); // 直接关联工单 report.setReportPeriodStart(periodStart); report.setReportPeriodEnd(periodEnd); report.setReportSequence(sequence); report.setShiftName(shiftName); report.setReportQuantity(quantity); // 更新工单累计数量 workOrder.setTotalReportedQuantity( workOrder.getTotalReportedQuantity().add(quantity) ); workOrder.setTotalReportCount(workOrder.getTotalReportCount() + 1); // 检查是否完成 if (workOrder.getTotalReportedQuantity().compareTo(workOrder.getQuantity()) >= 0) { workOrder.setProStatus("D"); // 已完成 } ``` --- ## 5. 前端改动 ### 5.1 物料管理页面 **新增字段**: - 制造类型:单选(离散制造业/连续制造业) - 报工模式:下拉(按工序/按时间/按产量) - 报工间隔:数字输入(分钟) ### 5.2 新增页面:连续制造业报工 **路由**:`/production/continuous-report` **页面布局**: ``` ┌─────────────────────────────────────┐ │ 1. 工单选择区 │ │ - 显示工单信息、设备、进度 │ ├─────────────────────────────────────┤ │ 2. 快速报工表单 │ │ - 时间段、班次、操作员 │ │ - 产量、合格数、不合格数 │ │ - 停机时间、停机原因 │ │ - 备注 │ ├─────────────────────────────────────┤ │ 3. 报工历史列表 │ │ - 显示已报工记录 │ ├─────────────────────────────────────┤ │ 4. 产量趋势图表 │ │ - 按小时/班次显示产量趋势 │ └─────────────────────────────────────┘ ``` ### 5.3 工单管理页面 **新增列**: - 制造类型(标签显示) - 累计报工数量(连续制造业显示) - 报工次数(连续制造业显示) --- ## 6. 后端接口(新增) ### 6.1 获取连续制造业工单列表 ``` GET /api/production/continuous/workorders 参数: status, equipmentId, pageNum, pageSize ``` ### 6.2 提交连续制造业报工 ``` POST /api/production/continuous/report/submit 参数: { workOrderId, reportPeriodStart, reportPeriodEnd, reportQuantity, qualifiedQuantity, unqualifiedQuantity, shiftName, downtimeMinutes, downtimeReason, remark } ``` ### 6.3 获取报工历史 ``` GET /api/production/continuous/report/history 参数: workOrderId, startTime, endTime ``` ### 6.4 获取产量趋势 ``` GET /api/production/continuous/report/trend 参数: workOrderId, startTime, endTime, groupBy(HOUR/SHIFT/DAY) ``` --- ## 7. 实施步骤 ### 第一阶段:数据库改动(1天) 1. 执行SQL脚本,新增字段 2. 执行数据迁移脚本 3. 验证现有功能不受影响 ### 第二阶段:后端开发(5天) #### 2.1 实体类和Mapper改动(1天) 1. 修改 `Material.java`:新增 `manufactureType`, `reportMode` 2. 修改 `ProWorkorder.java`:新增 9个字段 3. 修改 `ProReport.java`:新增 9个字段 4. 修改 `MaterialMapper.xml`:新增字段映射 5. 修改 `ProWorkorderMapper.xml`:新增字段映射 6. 修改 `ProReportMapper.xml`:新增字段映射 #### 2.2 Service层改动(2天) 1. **AutoCompleteService**: - 修改 `autoCompleteSaleOrder` 方法,增加制造类型判断 - 新增 `autoCompleteContinuous` 方法(连续制造业一键完成) - 新增 `autoCompleteDiscrete` 方法(重构现有逻辑) 2. **TimedCompleteService**: - 修改 `batchExecute` 方法,增加制造类型判断 - 跳过或特殊处理连续制造业订单 3. **ReportService**: - 修改 `submitReport` 方法,增加制造类型判断 - 新增 `submitContinuousReport` 方法(连续制造业报工) - 新增 `submitDiscreteReport` 方法(重构现有逻辑) 4. **WorkOrderService**: - 修改工单生成逻辑,支持连续制造业 - 新增工单完成判断逻辑(累计数量 >= 计划数量) #### 2.3 Controller和API(1天) 1. 新增连续制造业报工接口 2. 新增报工历史查询接口 3. 新增产量趋势统计接口 4. 修改现有接口,支持新字段 #### 2.4 单元测试(1天) 1. 测试连续制造业工单生成 2. 测试连续制造业报工 3. 测试一键完成功能(离散+连续) 4. 测试定时完成功能(离散+连续) ### 第三阶段:前端开发(5天) #### 3.1 物料管理页面(1天) 1. 新增制造类型字段(下拉框) 2. 新增报工模式字段(下拉框) 3. 实现字段联动逻辑 4. 表单验证 #### 3.2 报工页面改造(2天) 1. **创建连续制造业报工页面**: - 按时间报工界面(时段选择) - 按班次报工界面(班次选择) - 按产量报工界面(累计产量) - 按批次报工界面(批次号) 2. **改造现有报工页面**: - 增加制造类型判断 - 根据报工模式动态显示控件 - 报工序号自动生成 #### 3.3 工序执行情况页面改造(1天) 1. **一键完成功能改造**: - 增加制造类型判断 - 连续制造业:只生成工单,不自动报工 - 离散制造业:保持现有逻辑 2. **定时完成功能改造**: - 增加制造类型判断 - 连续制造业:跳过或仅生成工单 3. **进度条显示改造**: - 离散制造业:显示工序进度条 - 连续制造业:显示报工次数和累计产量 #### 3.4 工单管理页面(0.5天) 1. 列表新增列:制造类型、报工模式、报工次数、累计报工数量 2. 详情页显示:设备信息、实际时间、停机时间 3. 连续制造业:显示报工历史列表 #### 3.5 集成测试(0.5天) 1. 端到端测试(物料 → 订单 → 工单 → 报工) 2. 兼容性测试(离散制造业功能不受影响) ### 第四阶段:测试上线(2天) 1. 功能测试(所有报工模式) 2. 兼容性测试(离散制造业) 3. 性能测试(批量报工) 4. 用户培训 5. 上线部署 --- ## 8. 注意事项 ### 8.1 兼容性 - ✅ 现有离散制造业流程完全不受影响 - ✅ 新增字段都有默认值 - ✅ 接口向后兼容 ### 8.2 性能优化 - 创建必要的索引(已在SQL中体现) - 报工历史查询限制时间范围(最近30天) - 使用分页查询 ### 8.3 数据验证 - 报工时间段不能重叠 - 累计报工数量不能超过计划数量太多(允许10%误差) - 合格数量 + 不合格数量 = 报工数量 ### 8.4 关键实现细节 #### 8.4.1 字段使用说明 **pro_report 表字段使用规则**: - **离散制造业**: - 必填:`work_order_entry_id`(工单分录ID) - 不填:`work_order_id`, `report_period_start/end`, `report_sequence`, `shift_name` - **连续制造业**: - 必填:`work_order_id`(工单ID) - 可选:`report_period_start/end`(TIME/SHIFT模式使用) - 可选:`shift_name`(SHIFT模式使用) - 可选:`report_sequence`(所有模式建议使用,便于排序) - 不填:`work_order_entry_id`(设为NULL或0) #### 8.4.2 报工人字段说明 - 使用现有字段 `report_user_id` 和 `report_user_name` 记录主报工人 - 如需记录多人协作,可在 `remark` 字段中补充说明 - **已删除字段**:`operator_ids`(与现有字段重复,已移除) #### 8.4.3 工单完成判断逻辑 ```java // 连续制造业工单完成判断 if (workOrder.getTotalReportedQuantity() >= workOrder.getQuantity()) { workOrder.setProStatus("FINISHED"); workOrder.setRealFinishDate(new Date()); } ``` #### 8.4.4 报工序号自动生成 ```java // 获取当前工单的最大报工序号 Integer maxSequence = reportMapper.selectMaxSequence(workOrderId); report.setReportSequence(maxSequence == null ? 1 : maxSequence + 1); ``` #### 8.4.5 设备关联说明 - `pro_workorder.equipment_id` 和 `pro_report.equipment_id` 都关联到 `dm_equipment.id` - 工单级别的设备ID表示主要使用的设备 - 报工级别的设备ID允许不同(支持多设备轮换) #### 8.4.6 销售订单表无需修改 - `sal_order` 表**不需要**新增任何字段 - 制造类型信息存储在 `md_material` 表中 - 工单生成时从物料表读取 `manufacture_type` 和 `report_mode` - 保持销售订单与制造类型解耦 ### 8.5 前端界面改动 #### 8.5.1 报工页面控件差异 **核心原则**:报工页面根据**工单的制造类型和报工模式**动态显示不同控件。 **实现逻辑**: ```javascript // 1. 选择工单后,查询工单详情 getWorkOrderDetail(workOrderId) { getWorkOrder(workOrderId).then(response => { this.workOrder = response.data; this.manufactureType = response.data.manufactureType; // DISCRETE 或 CONTINUOUS this.reportMode = response.data.reportMode; // PROCESS, TIME, SHIFT, QUANTITY, BATCH }); } // 2. 根据制造类型和报工模式显示不同控件 ``` **离散制造业(PROCESS模式)**: - ✅ 显示工序选择下拉框 - ✅ 显示工单分录列表 - ✅ 报工时间:单个时间点 - ✅ 报工数量:单次报工数量 - ✅ 字段:`workOrderEntryId`(关联工单分录) - ❌ 不显示:时段选择、班次选择、报工序号 **连续制造业 - 按时间(TIME模式)**: - ❌ 不显示工序选择 - ✅ 显示时段选择:开始时间 + 结束时间 - ✅ 显示报工序号(后端自动生成:第1次、第2次...) - ✅ 显示设备选择 - ✅ 显示停机时间和停机原因 - ✅ 报工数量:该时段产量 - ✅ 字段:`workOrderId`, `reportPeriodStart`, `reportPeriodEnd`, `equipmentId`, `downtimeMinutes` **连续制造业 - 按班次(SHIFT模式)**: - ❌ 不显示工序选择 - ✅ 显示班次选择下拉框(使用字典:`sys_shift_type`) - ✅ 显示时段选择:班次开始时间 + 结束时间 - ✅ 显示报工序号(后端自动生成) - ✅ 显示设备选择 - ✅ 报工数量:该班次产量 - ✅ 字段:`workOrderId`, `shiftName`, `reportPeriodStart`, `reportPeriodEnd`, `equipmentId` **连续制造业 - 按产量(QUANTITY模式)**: - ❌ 不显示工序选择 - ❌ 不显示时段选择 - ✅ 显示累计产量(只读,从工单获取) - ✅ 显示报工序号(后端自动生成) - ✅ 显示设备选择 - ✅ 报工数量:本次报工产量 - ✅ 系统自动计算:累计产量 = 前次累计 + 本次报工 - ✅ 字段:`workOrderId`, `reportQuantity`, `equipmentId` **连续制造业 - 按批次(BATCH模式)**: - ❌ 不显示工序选择 - ✅ 显示批次说明输入框 - ✅ 显示报工序号(后端自动生成) - ✅ 显示设备选择 - ✅ 报工数量:该批次产量 - ✅ 备注字段:记录批次详细信息 - ✅ 字段:`workOrderId`, `reportQuantity`, `remark`, `equipmentId` **字段映射关系**: | 制造类型 | 报工模式 | 关键字段 | 后端判断依据 | |---------|---------|---------|------------| | DISCRETE | PROCESS | `workOrderEntryId` | `workOrderEntryId != null` | | CONTINUOUS | TIME | `workOrderId`, `reportPeriodStart/End` | `workOrderId != null` | | CONTINUOUS | SHIFT | `workOrderId`, `shiftName` | `workOrderId != null` | | CONTINUOUS | QUANTITY | `workOrderId` | `workOrderId != null` | | CONTINUOUS | BATCH | `workOrderId` | `workOrderId != null` | **前端示例代码**: ```vue ``` #### 8.5.2 工序执行情况页面改动 **现有功能**: - 一键完成:为离散制造业订单自动生成工单和报工 - 定时完成(批量完成):定时批量处理超期订单 **需要修改的逻辑**: 1. **一键完成功能**: ```java // 现有逻辑:按工序路线生成多个工单 if (DISCRETE) { for (每个工序) { 创建工单:batch_number = "XS001-P01", "XS001-P02" 创建工单分录 创建报工单 } } // 新增逻辑:连续制造业生成单个工单 if (CONTINUOUS) { 创建工单:batch_number = "XS001"(不按工序) 不创建工单分录 不自动创建报工单(由用户按时间/班次/产量/批次报工) } ``` 2. **定时完成(批量完成)功能**: ```java // 需要判断制造类型 if (material.getManufactureType() == 'DISCRETE') { // 使用现有逻辑:自动完成所有工序 autoCompleteAllProcesses(); } else if (material.getManufactureType() == 'CONTINUOUS') { // 连续制造业:只生成工单,不自动报工 // 或者:跳过连续制造业订单 skipOrGenerateWorkOrderOnly(); } ``` 3. **工序进度条显示**: ```javascript // 离散制造业:显示工序进度条 if (order.manufactureType === 'DISCRETE') { 显示工序步骤条(工序1 → 工序2 → 工序3) } // 连续制造业:显示报工次数进度 if (order.manufactureType === 'CONTINUOUS') { 显示报工进度:已报工 X 次,累计 Y 吨 / 计划 Z 吨 } ``` #### 8.5.3 物料管理页面改动 **新增字段**: - 制造类型:下拉框(离散/连续) - 报工模式:下拉框(按工序/按时间/按班次/按产量/按批次) - 当制造类型=离散时,报工模式固定为"按工序" - 当制造类型=连续时,报工模式可选其他4种 **字段联动**: ```javascript onManufactureTypeChange(value) { if (value === 'DISCRETE') { this.form.reportMode = 'PROCESS' this.reportModeDisabled = true } else { this.reportModeDisabled = false this.form.reportMode = 'TIME' // 默认按时间 } } ``` #### 8.5.4 工单管理页面改动 **列表显示**: - 新增列:制造类型(离散/连续) - 新增列:报工模式 - 新增列:报工次数(连续制造业显示) - 新增列:累计报工数量(连续制造业显示) **详情页面**: - 显示设备信息 - 显示实际开始/结束时间 - 显示累计停机时间 - 连续制造业:显示报工历史列表 ### 8.6 统计报表功能改动 #### 8.6.1 WorkOrderExecutionService(工单执行情况统计) **文件**:`WorkOrderExecutionServiceImpl.java`、`WorkOrderExecutionMapper.xml` **需要修改的功能**: 1. **工单列表查询**: ```xml ``` 2. **工序列表查询(离散制造业)**: ```xml ``` 3. **报工历史查询(连续制造业)**: ```xml ``` 4. **Service层适配**: ```java @Override public List selectProcessListByWorkOrderId(Long workOrderId) { // 1. 查询工单信息,判断制造类型 WorkOrder workOrder = workOrderMapper.selectById(workOrderId); if ("DISCRETE".equals(workOrder.getManufactureType())) { // 离散制造业:查询工序列表 return workOrderExecutionMapper.selectProcessListByWorkOrderId(workOrderId); } else if ("CONTINUOUS".equals(workOrder.getManufactureType())) { // 连续制造业:返回报工历史摘要 return buildContinuousProcessSummary(workOrderId, workOrder); } } // 新增方法:构建连续制造业的"工序"摘要(实际是报工摘要) private List buildContinuousProcessSummary(Long workOrderId, WorkOrder workOrder) { List summary = new ArrayList<>(); // 创建一个虚拟的"连续生产"工序 ProcessExecutionVO continuousProcess = new ProcessExecutionVO(); continuousProcess.setId(workOrderId); continuousProcess.setProcessName("连续生产"); continuousProcess.setProcessSort(1); continuousProcess.setPlanQuantity(workOrder.getQuantity()); continuousProcess.setReportedQuantity(workOrder.getTotalReportedQuantity()); // 计算完成率 BigDecimal completionRate = BigDecimal.ZERO; if (workOrder.getQuantity().compareTo(BigDecimal.ZERO) > 0) { completionRate = workOrder.getTotalReportedQuantity() .divide(workOrder.getQuantity(), 2, RoundingMode.HALF_UP) .multiply(new BigDecimal("100")); } continuousProcess.setCompletionRate(completionRate); // 设置状态 if (completionRate.compareTo(new BigDecimal("100")) >= 0) { continuousProcess.setStatus("已完成"); } else if (completionRate.compareTo(BigDecimal.ZERO) > 0) { continuousProcess.setStatus("进行中"); } else { continuousProcess.setStatus("未开始"); } // 设置报工次数 continuousProcess.setReportCount(workOrder.getTotalReportCount()); summary.add(continuousProcess); return summary; } ``` ### 8.7 前端统计页面改动 #### 8.7.1 工单执行情况页面 **文件**:`mes-ui/src/views/mes/statement/workOrderExecution/index.vue` **需要修改的显示逻辑**: 1. **工单卡片显示**: ```javascript // 在工单卡片中显示制造类型和报工模式
{{ workOrder.manufactureType === 'DISCRETE' ? '离散制造' : '连续制造' }} {{ getReportModeText(workOrder.reportMode) }} 批次:{{ workOrder.batchNumber }} 数量:{{ workOrder.quantity }}
// 方法:获取报工模式文本 getReportModeText(mode) { const modeMap = { 'PROCESS': '按工序', 'TIME': '按时间', 'SHIFT': '按班次', 'QUANTITY': '按产量', 'BATCH': '按批次' }; return modeMap[mode] || mode; } ``` 2. **进度条显示差异**: ```javascript // 离散制造业:显示工序进度
当前工序:{{ workOrder.currentProcess || '未开始' }} {{ workOrder.completedProcessCount }}/{{ workOrder.totalProcessCount }} 个工序
// 连续制造业:显示报工次数和累计产量
已报工:{{ workOrder.totalReportCount }} 次 累计产量:{{ workOrder.totalReportedQuantity }}/{{ workOrder.quantity }}
``` 3. **展开内容差异**: ```javascript // 离散制造业:显示工序列表 // 连续制造业:显示报工历史 ``` ### 8.8 后端Service改动 #### 8.6.1 AutoCompleteService(一键完成) **文件**:`AutoCompleteServiceImpl.java` **需要修改的方法**: ```java @Override @Transactional public AjaxResult autoCompleteSaleOrder(AutoCompleteDTO dto) { // 1. 查询物料信息 Material material = materialMapper.selectById(salOrderEntry.getMaterialId()); // 2. 判断制造类型 if ("DISCRETE".equals(material.getManufactureType())) { // 现有逻辑:按工序生成多个工单 + 自动报工 return autoCompleteDiscrete(dto, material); } else if ("CONTINUOUS".equals(material.getManufactureType())) { // 新增逻辑:生成单个工单,不自动报工 return autoCompleteContinuous(dto, material); } } // 新增方法:连续制造业一键完成 private AjaxResult autoCompleteContinuous(AutoCompleteDTO dto, Material material) { // 1. 生成单个工单(不按工序) WorkOrder workOrder = new WorkOrder(); workOrder.setBatchNumber(salOrder.getNumber()); // 批次号 = 订单号 workOrder.setManufactureType("CONTINUOUS"); workOrder.setReportMode(material.getReportMode()); workOrder.setQuantity(salOrderEntry.getQuantity()); // ... 其他字段 workOrderMapper.insert(workOrder); // 2. 不创建工单分录 // 3. 不自动创建报工单 // 4. 更新订单状态为"已生成工单"(不是"生产完成") return AjaxResult.success("工单生成成功,请手动报工"); } ``` #### 8.6.2 TimedCompleteService(定时完成) **文件**:`TimedCompleteServiceImpl.java` **需要修改的方法**: ```java @Override public AjaxResult batchExecute(BatchExecuteDTO dto) { List orders = getOverdueOrders(...); for (OverdueOrderVO order : orders) { // 查询物料信息 Material material = materialMapper.selectById(order.getMaterialId()); // 判断制造类型 if ("DISCRETE".equals(material.getManufactureType())) { // 自动完成 autoCompleteService.autoCompleteSaleOrder(...); } else if ("CONTINUOUS".equals(material.getManufactureType())) { // 跳过或仅生成工单 log.info("跳过连续制造业订单: " + order.getOrderNumber()); skipCount++; } } } ``` #### 8.6.3 ReportService(报工服务) **需要修改的方法**: ```java @Override public AjaxResult submitReport(Report report) { // 1. 判断是离散还是连续制造业 if (report.getWorkOrderEntryId() != null) { // 离散制造业:现有逻辑 return submitDiscreteReport(report); } else if (report.getWorkOrderId() != null) { // 连续制造业:新逻辑 return submitContinuousReport(report); } } // 新增方法:连续制造业报工 private AjaxResult submitContinuousReport(Report report) { // 1. 查询工单 WorkOrder workOrder = workOrderMapper.selectById(report.getWorkOrderId()); // 2. 自动生成报工序号 Integer maxSequence = reportMapper.selectMaxSequence(report.getWorkOrderId()); report.setReportSequence(maxSequence == null ? 1 : maxSequence + 1); // 3. 插入报工单 reportMapper.insert(report); // 4. 更新工单累计数据 workOrder.setTotalReportCount(workOrder.getTotalReportCount() + 1); workOrder.setTotalReportedQuantity( workOrder.getTotalReportedQuantity().add(report.getReportQuantity()) ); // 5. 判断是否完成 if (workOrder.getTotalReportedQuantity().compareTo(workOrder.getQuantity()) >= 0) { workOrder.setProStatus("FINISHED"); workOrder.setRealFinishDate(new Date()); } workOrderMapper.updateById(workOrder); return AjaxResult.success("报工成功"); } ``` #### 8.6.4 WorkOrderService(工单服务) **需要修改的方法**: 1. **insertWorkOrder 方法**: ```java @Override public int insertWorkOrder(WorkOrder workOrder) { // ... 现有逻辑 ... // 新增:保存工单后,判断是否需要创建工单分录 int rows = workOrderMapper.insert(workOrder); // 判断制造类型 if ("DISCRETE".equals(workOrder.getManufactureType())) { // 离散制造业:创建工单分录(按工序) insertWorkOrderEntry(workOrder); } else if ("CONTINUOUS".equals(workOrder.getManufactureType())) { // 连续制造业:不创建工单分录 // 工单直接可用于报工 } return rows; } ``` 2. **insertWorkOrderEntry 方法**: ```java public void insertWorkOrderEntry(WorkOrder workOrder) { // 检查制造类型 if ("CONTINUOUS".equals(workOrder.getManufactureType())) { // 连续制造业不创建工单分录 return; } // ... 现有逻辑(为离散制造业创建工单分录)... } ``` 3. **批量生成工单方法**: ```java // 从销售订单批量生成工单时,需要判断物料的制造类型 public List generateWorkOrdersFromSaleOrder(SalOrder salOrder) { List workOrders = new ArrayList<>(); for (SalOrderEntry entry : salOrder.getEntries()) { // 查询物料信息 Material material = materialMapper.selectById(entry.getMaterialId()); if ("DISCRETE".equals(material.getManufactureType())) { // 离散制造业:按工序路线生成多个工单 List processWorkOrders = generateDiscreteWorkOrders(entry, material); workOrders.addAll(processWorkOrders); } else if ("CONTINUOUS".equals(material.getManufactureType())) { // 连续制造业:生成单个工单 WorkOrder workOrder = generateContinuousWorkOrder(entry, material); workOrders.add(workOrder); } } return workOrders; } ``` --- ## 9. 班次管理说明 ### 9.1 班次名称配置 **问题**:连续制造业按班次报工时,需要选择班次名称(如:早班、中班、晚班)。 **解决方案**:使用系统现有的**数据字典功能**管理班次选项。 ### 9.2 实现方式 #### 数据库配置 已在SQL脚本中添加班次字典配置: ```sql -- 添加班次类型字典 INSERT INTO `sys_dict_type` (`dict_name`, `dict_type`, `status`, `create_by`, `create_time`, `remark`) VALUES ('班次类型', 'sys_shift_type', '0', 'admin', NOW(), '用于连续制造业按班次报工'); -- 添加默认班次选项 INSERT INTO `sys_dict_data` (`dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `status`, `create_by`, `create_time`, `remark`) VALUES (1, '早班', 'MORNING', 'sys_shift_type', '', 'default', 'Y', '0', 'admin', NOW(), '08:00-16:00'), (2, '中班', 'AFTERNOON', 'sys_shift_type', '', 'default', 'N', '0', 'admin', NOW(), '16:00-00:00'), (3, '晚班', 'NIGHT', 'sys_shift_type', '', 'default', 'N', '0', 'admin', NOW(), '00:00-08:00'); ``` #### 前端使用方式 报工页面使用字典组件: ```vue ``` ### 9.3 配置管理 企业可以在**系统管理 → 字典管理**中灵活配置: 1. **修改班次名称**:将"早班"改为"白班"、"A班"等 2. **调整班次数量**:支持两班制、三班制、四班制 3. **设置默认班次**:`is_default='Y'` 4. **记录时间段**:在`remark`字段记录班次时间(仅供参考) **示例配置**: - 两班制:白班(08:00-20:00)、夜班(20:00-08:00) - 三班制:早班、中班、晚班 - 四班制:A班、B班、C班、D班 ### 9.4 优点 1. ✅ **无需新建表**:利用现有字典功能 2. ✅ **灵活配置**:可在系统管理中修改,无需改代码 3. ✅ **实施简单**:只需执行SQL脚本 4. ✅ **满足需求**:支持各种班次制度 ## 10. 废弃内容说明 以下内容在简化方案中**不实施**: ❌ 工单分录表的复杂改动(连续制造业不使用工单分录) ❌ 设备管理表(如果系统已有设备模块则复用,否则暂不实施) ❌ 停机记录表(停机信息直接记录在报工单中) ❌ 复杂的批次号规则(第一版只支持订单号作为批次号) ❌ 质量状态字段(第一版只记录合格/不合格数量) 这些功能可在后续版本中根据实际需求逐步添加。 --- **文档版本**: v3.0(完整版) **创建日期**: 2025-11-11 **最后更新**: 2025-11-11 **状态**: 待实施 **预计工期**: 13个工作日(数据库1天 + 后端5天 + 前端5天 + 测试2天) --- ## 附录:影响范围总结 ### 数据库改动 - ✅ `md_material`:2个字段 - ✅ `pro_workorder`:9个字段 - ✅ `pro_report`:9个字段 - ✅ 索引:8个 - ✅ 数据字典:1个字典类型 + 3个字典数据(班次配置) ### 后端文件改动 - ✅ 实体类:3个(Material, ProWorkorder, ProReport) - ✅ Mapper XML:4个(MaterialMapper, ProWorkorderMapper, ProReportMapper, WorkOrderExecutionMapper) - ✅ Service:5个(AutoCompleteService, TimedCompleteService, ReportService, WorkOrderService, WorkOrderExecutionService) - ✅ Controller:2个(ReportController, WorkOrderExecutionController) ### 前端页面改动 - ✅ 物料管理页面:新增2个字段 + 联动逻辑 - ✅ 报工页面:新建或改造,支持5种报工模式,使用字典组件获取班次选项 - ✅ 工序执行情况页面:一键完成 + 定时完成 + 进度条显示 - ✅ 工单管理页面:新增列 + 详情页改造 - ✅ 工单执行情况统计页面:制造类型显示 + 进度条差异 + 展开内容差异 ### 关键功能点 1. ✅ 制造类型判断(离散/连续) 2. ✅ 报工模式支持(5种) 3. ✅ 报工控件差异化 4. ✅ 一键完成功能改造 5. ✅ 定时完成功能改造 6. ✅ 工序进度条改造 7. ✅ 报工序号自动生成 8. ✅ 累计数量统计 9. ✅ 工单完成判断 10. ✅ 统计报表适配(工单执行情况) --- ## 附录B:实施过程中的Bug修复记录 ### Bug #1:工单查询未返回 `manufactureType` 字段 **日期**:2025-11-12 **问题描述**: - 前端工单列表页面点击"报工"按钮时,`workOrder.manufactureType` 为 `undefined` - 导致连续制造业工单无法正确识别,仍然提示"该工单没有工序分录,无法报工!" **根本原因**: `WorkOrderMapper.xml` 中的查询SQL和resultMap缺少 `manufacture_type` 等连续制造业相关字段的映射 **修复方案**: 1. 在 `WorkOrderMapper.xml` 的 `resultMap` 中添加字段映射: ```xml ``` 2. 在 `selectWorkOrderVo` SQL中添加查询字段: ```xml a.manufacture_type, a.report_mode, a.total_report_count, a.total_reported_quantity, a.equipment_id, a.equipment_name, a.actual_start_time, a.actual_end_time, a.downtime_minutes, ``` **影响范围**: - ✅ 不影响现有功能 - ✅ 只是补充缺失的字段映射 --- ### Bug #2:报工记录查询返回所有工单的数据(804条) **日期**:2025-11-12 **问题描述**: - 连续制造业工单点击"报工"后,页面卡死 - 控制台显示查询到804条报工记录(实际应该是0条或该工单的报工记录) - 原因是查询参数 `workOrderId` 没有传递到后端SQL **根本原因**: 1. **前端问题**:`form.vue` 中使用动态导入API失败,且使用路由参数ID而不是工单真实ID 2. **后端问题**:`ReportMapper.xml` 中缺少 `workOrderId` 的查询条件 **修复方案**: **前端修复** (`mes-ui/src/views/mes/production/report/form.vue`): 1. 添加API导入: ```javascript import {getEntryRealSort, getWorkOrderByEntryId, getWorkOrder} from '@/api/mes/production/workOrder' ``` 2. 修复API调用和参数传递: ```javascript // 正确调用API const apiCall = isContinuous ? getWorkOrder(id) : getWorkOrderByEntryId(id); // 使用响应数据中的真实工单ID if (isContinuous) { this.form.workOrderId = response.data.id; // 使用真实ID this.form.workOrderEntryId = null; } // 查询报工记录时使用真实ID const queryParams = isContinuous ? { pageSize:99999, workOrderId: response.data.id } : { pageSize:99999, workOrderEntryId:id }; ``` **后端修复** (`ReportMapper.xml`): 在 `selectReportList` 和 `selectReportListCount` 两个查询中都添加: ```xml and prp.work_order_id =#{workOrderId} ``` **影响范围**: - ✅ 不影响离散制造业(仍使用 `workOrderEntryId` 查询) - ✅ 修复连续制造业报工查询性能问题 --- ### Bug #3:报工保存失败 - `work_order_entry_id` 字段无默认值 **日期**:2025-11-12 **问题描述**: - 连续制造业报工点击"保存"按钮时报错 - 错误信息:`Field 'work_order_entry_id' doesn't have a default value` - 原因是数据库表 `pro_report` 中的 `work_order_entry_id` 字段不允许为空 **根本原因**: 数据库表设计时,`work_order_entry_id` 字段被设置为 `NOT NULL`,但连续制造业报工不使用工序分录,此字段应该为 `NULL` **修复方案**: 执行SQL修改表结构: ```sql -- 修改 pro_report 表,让 work_order_entry_id 可以为空 ALTER TABLE pro_report MODIFY COLUMN work_order_entry_id BIGINT(20) NULL COMMENT '工单子表ID(离散制造业使用)'; -- 为 work_order_entry_id 添加索引(如果不存在) CREATE INDEX IF NOT EXISTS idx_work_order_entry_id ON pro_report(work_order_entry_id); ``` **影响分析**: - ✅ **离散制造业**:不受影响,仍然传递 `work_order_entry_id`,值不为空 - ✅ **连续制造业**:可以将 `work_order_entry_id` 设置为 `NULL` - ✅ **现有报工记录**:不受影响,已有数据保持不变 - ✅ **查询功能**:不受影响,`WHERE work_order_entry_id = ?` 仍然有效 - ✅ **关联查询**:不受影响,`LEFT JOIN` 仍然正常工作 **验证SQL**: ```sql -- 验证修改是否成功 SHOW CREATE TABLE pro_report; -- 验证现有数据不受影响 SELECT COUNT(*) as total_reports, SUM(CASE WHEN work_order_id IS NOT NULL THEN 1 ELSE 0 END) as continuous_report_count, SUM(CASE WHEN work_order_entry_id IS NOT NULL THEN 1 ELSE 0 END) as discrete_report_count FROM pro_report; ``` --- ### Bug修复总结 | Bug编号 | 问题 | 影响 | 修复文件 | 状态 | |--------|------|------|---------|------| | #1 | 工单查询缺少字段映射 | 前端无法识别制造类型 | `WorkOrderMapper.xml` | ✅ 已修复 | | #2 | 报工记录查询无过滤条件 | 查询所有报工记录,页面卡死 | `form.vue`, `ReportMapper.xml` | ✅ 已修复 | | #3 | 字段不允许为空 | 连续制造业报工无法保存 | 数据库表结构 | ✅ 已修复 | **重要提示**: - 所有修复都向后兼容,不影响现有离散制造业功能 - 修复后需要重启后端服务和刷新前端页面 - 建议在测试环境验证后再部署到生产环境 --- ## 功能增强总结 | 功能编号 | 功能描述 | 实现方案 | 影响文件 | 状态 | |----------|----------|----------|----------|------| | Feature #1 | 工单列表数量显示优化 | 显示格式改为"已报工数量/总数量",类似销售订单 | `jinzhong.js` | ✅ 已完成 | | Feature #2 | 列表页面空值显示优化 | 统一将空值显示为"-" | `jinzhong.js`, `index.vue` | ✅ 已完成 | | Feature #3 | 连续制造业销售订单状态自动更新 | 连续制造业工单状态变更时自动同步销售订单状态:开始排产→生产中,报工完成→已完成 | `ReportServiceImpl.java`, `WorkOrderServiceImpl.java` | ✅ 已完成 |