76 KiB
连续制造业改进方案
1. 核心思路(5分钟读懂)
1.1 改进目标
在现有MES系统基础上,支持连续制造业(化工、钢铁、水泥等)的生产报工流程,实现按时间段/班次报工,兼容现有离散制造业模式。
1.2 核心设计:物料级别的类型标识
关键点:在 md_material(物料表)中新增2个字段作为"开关"
manufacture_type -- 制造类型: DISCRETE(离散) / CONTINUOUS(连续)
report_mode -- 报工模式: PROCESS(按工序) / TIME(按时间) / SHIFT(按班次) / QUANTITY(按产量) / BATCH(按批次)
报工模式说明:
| 报工模式 | 适用场景 | 报工方式 | 示例 |
|---|---|---|---|
PROCESS |
离散制造业 | 按工序完成报工 | 工序1完成 → 报工1次 |
TIME |
连续制造业 | 按固定时间段报工 | 每小时报工一次 |
SHIFT |
连续制造业 | 按班次报工 | 每班次结束报工一次 |
QUANTITY |
连续制造业 | 按达到的产量报工 | 每生产100吨报工一次 |
BATCH |
连续制造业 | 按生产批次报工 | 每完成一个批次报工一次 |
数据流转逻辑:
物料表设置类型
↓
销售订单选择物料(继承物料的类型)
↓
生成工单时判断物料类型 → 决定工单生成方式
↓
报工时判断工单类型 → 决定报工方式
2. 完整业务流程(核心)
2.1 流程对比
| 环节 | 离散制造业 | 连续-按时间 | 连续-按班次 | 连续-按产量 | 连续-按批次 |
|---|---|---|---|---|---|
| 1. 物料设置 | DISCRETEPROCESS |
CONTINUOUSTIME |
CONTINUOUSSHIFT |
CONTINUOUSQUANTITY |
CONTINUOUSBATCH |
| 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)
// 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. 设计原则
- 向后兼容:不影响现有离散制造业流程
- 最小改动:复用现有表结构,新增字段标识
- 简化逻辑:连续制造业不使用工单分录,报工直接关联工单
- 物料驱动:所有判断逻辑都基于物料的
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
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
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表结构
-- 标识物料的制造类型和报工配置
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表结构
-- 支持连续制造业的工单管理
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表结构
-- 支持连续制造业的报工记录
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:销售订单明细表新增字段(可选)
用于标识订单明细的生产状态
-- 扩展销售订单明细的状态管理
-- status字段已存在,可以使用以下状态值:
-- A: 待生产
-- B: 生产中
-- C: 部分完成
-- D: 已完成
-- 无需新增字段,使用现有的status和extend_field即可
步骤5:数据迁移
-- 为现有数据设置默认值(确保兼容性)
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
// 新增字段
private String manufactureType; // 制造类型: DISCRETE/CONTINUOUS
private String reportMode; // 报工模式: PROCESS/TIME
private Integer reportInterval; // 报工间隔(分钟)
// getter/setter 略
ProWorkorder.java
文件路径:com.yavii.domain.ProWorkorder
// 新增字段
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
// 新增字段
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 增加字段映射
<resultMap id="MaterialResult" type="Material">
<id property="id" column="id"/>
<result property="number" column="number"/>
<result property="name" column="name"/>
<result property="specification" column="specification"/>
<result property="unitId" column="unit_id"/>
<result property="classId" column="class_id"/>
<result property="typeId" column="type_id"/>
<result property="status" column="status"/>
<result property="routeId" column="route_id"/>
<!-- ========== 新增字段映射 ========== -->
<result property="manufactureType" column="manufacture_type"/>
<result property="reportMode" column="report_mode"/>
<!-- ================================= -->
<result property="remark" column="remark"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
<result property="updateTime" column="update_time"/>
</resultMap>
改动2:查询SQL 增加字段
<sql id="selectMaterialVo">
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
</sql>
改动3:插入SQL 增加字段
<insert id="insertMaterial" parameterType="Material">
insert into md_material
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="number != null">number,</if>
<if test="name != null">name,</if>
<!-- 其他现有字段... -->
<if test="manufactureType != null">manufacture_type,</if>
<if test="reportMode != null">report_mode,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="number != null">#{number},</if>
<if test="name != null">#{name},</if>
<!-- 其他现有字段... -->
<if test="manufactureType != null">#{manufactureType},</if>
<if test="reportMode != null">#{reportMode},</if>
</trim>
</insert>
改动4:更新SQL 增加字段
<update id="updateMaterial" parameterType="Material">
update md_material
<trim prefix="SET" suffixOverrides=",">
<if test="name != null">name = #{name},</if>
<!-- 其他现有字段... -->
<if test="manufactureType != null">manufacture_type = #{manufactureType},</if>
<if test="reportMode != null">report_mode = #{reportMode},</if>
</trim>
where id = #{id}
</update>
✅ ProWorkorderMapper.xml
文件路径:resources/mapper/ProWorkorderMapper.xml
改动1:resultMap 增加字段映射
<resultMap id="ProWorkorderResult" type="ProWorkorder">
<id property="id" column="id"/>
<result property="number" column="number"/>
<result property="batchNumber" column="batch_number"/>
<result property="materialId" column="material_id"/>
<result property="materialName" column="material_name"/>
<result property="quantity" column="quantity"/>
<result property="proStatus" column="pro_status"/>
<result property="lastReportTime" column="last_report_time"/>
<!-- ========== 新增字段映射 ========== -->
<result property="manufactureType" column="manufacture_type"/>
<result property="reportMode" column="report_mode"/>
<result property="totalReportCount" column="total_report_count"/>
<result property="totalReportedQuantity" column="total_reported_quantity"/>
<result property="equipmentId" column="equipment_id"/>
<result property="equipmentName" column="equipment_name"/>
<result property="actualStartTime" column="actual_start_time"/>
<result property="actualEndTime" column="actual_end_time"/>
<result property="downtimeMinutes" column="downtime_minutes"/>
<!-- ================================= -->
<!-- 其他现有字段... -->
</resultMap>
改动2:查询SQL 增加字段(关键:关联物料表)
<sql id="selectProWorkorderVo">
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
</sql>
改动3:插入SQL 增加字段
<insert id="insertProWorkorder" parameterType="ProWorkorder" useGeneratedKeys="true" keyProperty="id">
insert into pro_workorder
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="number != null">number,</if>
<if test="batchNumber != null">batch_number,</if>
<!-- 其他现有字段... -->
<if test="manufactureType != null">manufacture_type,</if>
<if test="reportMode != null">report_mode,</if>
<if test="totalReportCount != null">total_report_count,</if>
<if test="totalReportedQuantity != null">total_reported_quantity,</if>
<if test="equipmentId != null">equipment_id,</if>
<if test="equipmentName != null">equipment_name,</if>
<if test="actualStartTime != null">actual_start_time,</if>
<if test="actualEndTime != null">actual_end_time,</if>
<if test="downtimeMinutes != null">downtime_minutes,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="number != null">#{number},</if>
<if test="batchNumber != null">#{batchNumber},</if>
<!-- 其他现有字段... -->
<if test="manufactureType != null">#{manufactureType},</if>
<if test="reportMode != null">#{reportMode},</if>
<if test="totalReportCount != null">#{totalReportCount},</if>
<if test="totalReportedQuantity != null">#{totalReportedQuantity},</if>
<if test="equipmentId != null">#{equipmentId},</if>
<if test="equipmentName != null">#{equipmentName},</if>
<if test="actualStartTime != null">#{actualStartTime},</if>
<if test="actualEndTime != null">#{actualEndTime},</if>
<if test="downtimeMinutes != null">#{downtimeMinutes},</if>
</trim>
</insert>
改动4:更新SQL 增加字段
<update id="updateProWorkorder" parameterType="ProWorkorder">
update pro_workorder
<trim prefix="SET" suffixOverrides=",">
<if test="proStatus != null">pro_status = #{proStatus},</if>
<!-- 其他现有字段... -->
<if test="manufactureType != null">manufacture_type = #{manufactureType},</if>
<if test="reportMode != null">report_mode = #{reportMode},</if>
<if test="totalReportCount != null">total_report_count = #{totalReportCount},</if>
<if test="totalReportedQuantity != null">total_reported_quantity = #{totalReportedQuantity},</if>
<if test="equipmentId != null">equipment_id = #{equipmentId},</if>
<if test="equipmentName != null">equipment_name = #{equipmentName},</if>
<if test="actualStartTime != null">actual_start_time = #{actualStartTime},</if>
<if test="actualEndTime != null">actual_end_time = #{actualEndTime},</if>
<if test="downtimeMinutes != null">downtime_minutes = #{downtimeMinutes},</if>
<if test="lastReportTime != null">last_report_time = #{lastReportTime},</if>
</trim>
where id = #{id}
</update>
改动5:新增查询方法(查询连续制造业工单)
<!-- 查询连续制造业工单列表 -->
<select id="selectContinuousWorkOrders" resultMap="ProWorkorderResult">
<include refid="selectProWorkorderVo"/>
where w.manufacture_type = 'CONTINUOUS'
<if test="proStatus != null and proStatus != ''">
and w.pro_status = #{proStatus}
</if>
order by w.create_time desc
</select>
✅ ProReportMapper.xml
文件路径:resources/mapper/ProReportMapper.xml
改动1:resultMap 增加字段映射
<resultMap id="ProReportResult" type="ProReport">
<id property="id" column="id"/>
<result property="number" column="number"/>
<result property="workOrderEntryId" column="work_order_entry_id"/>
<result property="reportQuantity" column="report_quantity"/>
<result property="qualifiedQuantity" column="qualified_quantity"/>
<result property="unqualifiedQuantity" column="unqualified_quantity"/>
<result property="reportTime" column="report_time"/>
<result property="qualityStatus" column="quality_status"/>
<!-- ========== 新增字段映射 ========== -->
<result property="workOrderId" column="work_order_id"/>
<result property="reportPeriodStart" column="report_period_start"/>
<result property="reportPeriodEnd" column="report_period_end"/>
<result property="reportSequence" column="report_sequence"/>
<result property="shiftName" column="shift_name"/>
<result property="equipmentId" column="equipment_id"/>
<result property="equipmentName" column="equipment_name"/>
<result property="downtimeMinutes" column="downtime_minutes"/>
<result property="downtimeReason" column="downtime_reason"/>
<!-- ================================= -->
<result property="remark" column="remark"/>
<!-- 其他现有字段... -->
</resultMap>
改动2:查询SQL 增加字段(关键:关联工单表判断制造类型)
<sql id="selectProReportVo">
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
</sql>
改动3:插入SQL 增加字段
<insert id="insertProReport" parameterType="ProReport" useGeneratedKeys="true" keyProperty="id">
insert into pro_report
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="number != null">number,</if>
<if test="workOrderEntryId != null">work_order_entry_id,</if>
<!-- 其他现有字段... -->
<if test="workOrderId != null">work_order_id,</if>
<if test="reportPeriodStart != null">report_period_start,</if>
<if test="reportPeriodEnd != null">report_period_end,</if>
<if test="reportSequence != null">report_sequence,</if>
<if test="shiftName != null">shift_name,</if>
<if test="equipmentId != null">equipment_id,</if>
<if test="equipmentName != null">equipment_name,</if>
<if test="downtimeMinutes != null">downtime_minutes,</if>
<if test="downtimeReason != null">downtime_reason,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="number != null">#{number},</if>
<if test="workOrderEntryId != null">#{workOrderEntryId},</if>
<!-- 其他现有字段... -->
<if test="workOrderId != null">#{workOrderId},</if>
<if test="reportPeriodStart != null">#{reportPeriodStart},</if>
<if test="reportPeriodEnd != null">#{reportPeriodEnd},</if>
<if test="reportSequence != null">#{reportSequence},</if>
<if test="shiftName != null">#{shiftName},</if>
<if test="equipmentId != null">#{equipmentId},</if>
<if test="equipmentName != null">#{equipmentName},</if>
<if test="downtimeMinutes != null">#{downtimeMinutes},</if>
<if test="downtimeReason != null">#{downtimeReason},</if>
</trim>
</insert>
改动4:新增查询方法(按工单ID查询报工记录)
<!-- 根据工单ID查询报工记录(连续制造业使用) -->
<select id="selectReportsByWorkOrderId" resultMap="ProReportResult">
<include refid="selectProReportVo"/>
where r.work_order_id = #{workOrderId}
order by r.report_period_start desc, r.report_sequence desc
</select>
<!-- 检查时间段是否重叠 -->
<select id="checkTimeOverlap" resultType="int">
select count(1)
from pro_report
where work_order_id = #{workOrderId}
and (
(report_period_start <= #{periodStart} and report_period_end > #{periodStart})
or (report_period_start < #{periodEnd} and report_period_end >= #{periodEnd})
or (report_period_start >= #{periodStart} and report_period_end <= #{periodEnd})
)
</select>
<!-- 获取下一个报工序号 -->
<select id="getNextSequence" resultType="int">
select IFNULL(MAX(report_sequence), 0) + 1
from pro_report
where work_order_id = #{workOrderId}
</select>
✅ SalOrderMapper.xml(可选改动)
文件路径:resources/mapper/SalOrderMapper.xml
改动:查询订单明细时关联物料的制造类型
<sql id="selectSalOrderEntryVo">
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
</sql>
4.3 Mapper 接口改动
ProWorkorderMapper.java
/**
* 查询连续制造业工单列表
*/
List<ProWorkorder> selectContinuousWorkOrders(@Param("proStatus") String proStatus);
ProReportMapper.java
/**
* 根据工单ID查询报工记录
*/
List<ProReport> 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 工单生成逻辑
离散制造业(保持不变):
// 按工序路线生成多个工单
for (RouteProcess process : routeProcessList) {
WorkOrder workOrder = new WorkOrder();
workOrder.setManufactureType("DISCRETE");
workOrder.setBatchNumber(orderNumber + "-P" + processSort);
// ...
}
连续制造业(新增):
// 只生成一个工单
WorkOrder workOrder = new WorkOrder();
workOrder.setManufactureType("CONTINUOUS");
workOrder.setReportMode(material.getReportMode()); // 从物料继承
workOrder.setBatchNumber(orderNumber); // 不使用-P序号
workOrder.setEquipmentId(dto.getEquipmentId());
// 不创建工单分录
4.3 报工逻辑
离散制造业(保持不变):
// 按工序报工,关联工单分录
Report report = new Report();
report.setWorkOrderEntryId(entryId);
report.setReportQuantity(quantity);
连续制造业(新增):
// 按时间段报工,直接关联工单
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天)
- 执行SQL脚本,新增字段
- 执行数据迁移脚本
- 验证现有功能不受影响
第二阶段:后端开发(5天)
2.1 实体类和Mapper改动(1天)
- 修改
Material.java:新增manufactureType,reportMode - 修改
ProWorkorder.java:新增 9个字段 - 修改
ProReport.java:新增 9个字段 - 修改
MaterialMapper.xml:新增字段映射 - 修改
ProWorkorderMapper.xml:新增字段映射 - 修改
ProReportMapper.xml:新增字段映射
2.2 Service层改动(2天)
-
AutoCompleteService:
- 修改
autoCompleteSaleOrder方法,增加制造类型判断 - 新增
autoCompleteContinuous方法(连续制造业一键完成) - 新增
autoCompleteDiscrete方法(重构现有逻辑)
- 修改
-
TimedCompleteService:
- 修改
batchExecute方法,增加制造类型判断 - 跳过或特殊处理连续制造业订单
- 修改
-
ReportService:
- 修改
submitReport方法,增加制造类型判断 - 新增
submitContinuousReport方法(连续制造业报工) - 新增
submitDiscreteReport方法(重构现有逻辑)
- 修改
-
WorkOrderService:
- 修改工单生成逻辑,支持连续制造业
- 新增工单完成判断逻辑(累计数量 >= 计划数量)
2.3 Controller和API(1天)
- 新增连续制造业报工接口
- 新增报工历史查询接口
- 新增产量趋势统计接口
- 修改现有接口,支持新字段
2.4 单元测试(1天)
- 测试连续制造业工单生成
- 测试连续制造业报工
- 测试一键完成功能(离散+连续)
- 测试定时完成功能(离散+连续)
第三阶段:前端开发(5天)
3.1 物料管理页面(1天)
- 新增制造类型字段(下拉框)
- 新增报工模式字段(下拉框)
- 实现字段联动逻辑
- 表单验证
3.2 报工页面改造(2天)
-
创建连续制造业报工页面:
- 按时间报工界面(时段选择)
- 按班次报工界面(班次选择)
- 按产量报工界面(累计产量)
- 按批次报工界面(批次号)
-
改造现有报工页面:
- 增加制造类型判断
- 根据报工模式动态显示控件
- 报工序号自动生成
3.3 工序执行情况页面改造(1天)
-
一键完成功能改造:
- 增加制造类型判断
- 连续制造业:只生成工单,不自动报工
- 离散制造业:保持现有逻辑
-
定时完成功能改造:
- 增加制造类型判断
- 连续制造业:跳过或仅生成工单
-
进度条显示改造:
- 离散制造业:显示工序进度条
- 连续制造业:显示报工次数和累计产量
3.4 工单管理页面(0.5天)
- 列表新增列:制造类型、报工模式、报工次数、累计报工数量
- 详情页显示:设备信息、实际时间、停机时间
- 连续制造业:显示报工历史列表
3.5 集成测试(0.5天)
- 端到端测试(物料 → 订单 → 工单 → 报工)
- 兼容性测试(离散制造业功能不受影响)
第四阶段:测试上线(2天)
- 功能测试(所有报工模式)
- 兼容性测试(离散制造业)
- 性能测试(批量报工)
- 用户培训
- 上线部署
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 工单完成判断逻辑
// 连续制造业工单完成判断
if (workOrder.getTotalReportedQuantity() >= workOrder.getQuantity()) {
workOrder.setProStatus("FINISHED");
workOrder.setRealFinishDate(new Date());
}
8.4.4 报工序号自动生成
// 获取当前工单的最大报工序号
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 报工页面控件差异
核心原则:报工页面根据工单的制造类型和报工模式动态显示不同控件。
实现逻辑:
// 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. 根据制造类型和报工模式显示不同控件
<template v-if="manufactureType === 'DISCRETE'">
<!-- 离散制造业:显示工序选择 -->
</template>
<template v-if="manufactureType === 'CONTINUOUS' && reportMode === 'TIME'">
<!-- 连续制造业-按时间:显示时段选择 -->
</template>
离散制造业(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 |
前端示例代码:
<template>
<el-form ref="reportForm" :model="reportForm">
<!-- 公共字段 -->
<el-form-item label="报工数量" prop="reportQuantity">
<el-input-number v-model="reportForm.reportQuantity" :min="0" />
</el-form-item>
<!-- 离散制造业:工序选择 -->
<template v-if="manufactureType === 'DISCRETE'">
<el-form-item label="工序" prop="workOrderEntryId">
<el-select v-model="reportForm.workOrderEntryId">
<el-option v-for="entry in workOrderEntryList"
:key="entry.id"
:label="entry.processName"
:value="entry.id" />
</el-select>
</el-form-item>
</template>
<!-- 连续制造业-按时间 -->
<template v-if="manufactureType === 'CONTINUOUS' && reportMode === 'TIME'">
<el-form-item label="报工时段">
<el-date-picker v-model="reportForm.reportPeriod"
type="datetimerange"
value-format="yyyy-MM-dd HH:mm:ss" />
</el-form-item>
<el-form-item label="设备">
<el-select v-model="reportForm.equipmentId">
<el-option v-for="equip in equipmentList"
:key="equip.id"
:label="equip.name"
:value="equip.id" />
</el-select>
</el-form-item>
<el-form-item label="停机时间(分钟)">
<el-input-number v-model="reportForm.downtimeMinutes" :min="0" />
</el-form-item>
</template>
<!-- 连续制造业-按班次 -->
<template v-if="manufactureType === 'CONTINUOUS' && reportMode === 'SHIFT'">
<el-form-item label="班次">
<el-select v-model="reportForm.shiftName">
<el-option v-for="dict in dict.type.sys_shift_type"
:key="dict.value"
:label="dict.label"
:value="dict.label" />
</el-select>
</el-form-item>
</template>
</el-form>
</template>
<script>
export default {
dicts: ['sys_shift_type'], // 使用班次字典
data() {
return {
manufactureType: null,
reportMode: null,
reportForm: {
workOrderId: null, // 连续制造业使用
workOrderEntryId: null, // 离散制造业使用
reportQuantity: null,
reportPeriodStart: null,
reportPeriodEnd: null,
shiftName: null,
equipmentId: null,
downtimeMinutes: 0
}
}
}
}
</script>
8.5.2 工序执行情况页面改动
现有功能:
- 一键完成:为离散制造业订单自动生成工单和报工
- 定时完成(批量完成):定时批量处理超期订单
需要修改的逻辑:
-
一键完成功能:
// 现有逻辑:按工序路线生成多个工单 if (DISCRETE) { for (每个工序) { 创建工单:batch_number = "XS001-P01", "XS001-P02" 创建工单分录 创建报工单 } } // 新增逻辑:连续制造业生成单个工单 if (CONTINUOUS) { 创建工单:batch_number = "XS001"(不按工序) 不创建工单分录 不自动创建报工单(由用户按时间/班次/产量/批次报工) } -
定时完成(批量完成)功能:
// 需要判断制造类型 if (material.getManufactureType() == 'DISCRETE') { // 使用现有逻辑:自动完成所有工序 autoCompleteAllProcesses(); } else if (material.getManufactureType() == 'CONTINUOUS') { // 连续制造业:只生成工单,不自动报工 // 或者:跳过连续制造业订单 skipOrGenerateWorkOrderOnly(); } -
工序进度条显示:
// 离散制造业:显示工序进度条 if (order.manufactureType === 'DISCRETE') { 显示工序步骤条(工序1 → 工序2 → 工序3) } // 连续制造业:显示报工次数进度 if (order.manufactureType === 'CONTINUOUS') { 显示报工进度:已报工 X 次,累计 Y 吨 / 计划 Z 吨 }
8.5.3 物料管理页面改动
新增字段:
- 制造类型:下拉框(离散/连续)
- 报工模式:下拉框(按工序/按时间/按班次/按产量/按批次)
- 当制造类型=离散时,报工模式固定为"按工序"
- 当制造类型=连续时,报工模式可选其他4种
字段联动:
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
需要修改的功能:
- 工单列表查询:
<!-- 需要增加制造类型和报工模式字段 -->
<select id="selectWorkOrderExecutionList">
SELECT
pw.id, pw.number, pw.material_name, pw.specification,
pw.quantity, pw.batch_number, pw.pro_status,
<!-- 新增字段 -->
pw.manufacture_type, pw.report_mode,
pw.total_report_count, pw.total_reported_quantity,
<!-- 现有字段 -->
pw.begin_pro_date, pw.plan_finish_date,
pw.current_process_name, pw.completion_rate
FROM pro_workorder pw
WHERE pw.status = 'A'
</select>
- 工序列表查询(离散制造业):
<!-- 现有逻辑:查询工单分录 -->
<select id="selectProcessListByWorkOrderId">
SELECT pwe.id, pwe.process_name, pwe.process_sort,
pwe.report_quantity, pwe.reported_quantity
FROM pro_workorder_entry pwe
WHERE pwe.workorder_id = #{workOrderId}
AND pwe.type = 'report'
ORDER BY pwe.process_sort ASC
</select>
- 报工历史查询(连续制造业):
<!-- 新增:直接查询工单的报工记录 -->
<select id="selectReportListByWorkOrderId">
SELECT pr.id, pr.number, pr.report_user_name,
pr.report_time, pr.report_quantity,
pr.report_sequence, pr.shift_name,
pr.report_period_start, pr.report_period_end
FROM pro_report pr
WHERE pr.work_order_id = #{workOrderId}
AND pr.status = 'A'
ORDER BY pr.report_sequence ASC
</select>
- Service层适配:
@Override
public List<ProcessExecutionVO> 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<ProcessExecutionVO> buildContinuousProcessSummary(Long workOrderId, WorkOrder workOrder) {
List<ProcessExecutionVO> 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
需要修改的显示逻辑:
- 工单卡片显示:
// 在工单卡片中显示制造类型和报工模式
<div class="header-right">
<span class="manufacture-type">
{{ workOrder.manufactureType === 'DISCRETE' ? '离散制造' : '连续制造' }}
</span>
<span class="report-mode">
{{ getReportModeText(workOrder.reportMode) }}
</span>
<span class="batch-number">批次:{{ workOrder.batchNumber }}</span>
<span class="quantity">数量:{{ workOrder.quantity }}</span>
</div>
// 方法:获取报工模式文本
getReportModeText(mode) {
const modeMap = {
'PROCESS': '按工序',
'TIME': '按时间',
'SHIFT': '按班次',
'QUANTITY': '按产量',
'BATCH': '按批次'
};
return modeMap[mode] || mode;
}
- 进度条显示差异:
// 离散制造业:显示工序进度
<div v-if="workOrder.manufactureType === 'DISCRETE'" class="process-progress">
<span class="current-process">
当前工序:{{ workOrder.currentProcess || '未开始' }}
</span>
<span class="process-count">
{{ workOrder.completedProcessCount }}/{{ workOrder.totalProcessCount }} 个工序
</span>
</div>
// 连续制造业:显示报工次数和累计产量
<div v-else class="continuous-progress">
<span class="report-count">
已报工:{{ workOrder.totalReportCount }} 次
</span>
<span class="reported-quantity">
累计产量:{{ workOrder.totalReportedQuantity }}/{{ workOrder.quantity }}
</span>
</div>
- 展开内容差异:
// 离散制造业:显示工序列表
<el-table v-if="workOrder.manufactureType === 'DISCRETE'"
:data="workOrder.processList">
<el-table-column label="工序名称" prop="processName" />
<el-table-column label="完成率" prop="completionRate" />
<!-- ... 其他工序字段 -->
</el-table>
// 连续制造业:显示报工历史
<el-table v-else :data="workOrder.reportList">
<el-table-column label="报工序号" prop="reportSequence" />
<el-table-column label="报工时间" prop="reportTime" />
<el-table-column label="报工数量" prop="reportQuantity" />
<el-table-column label="班次" prop="shiftName" />
<el-table-column label="时段" width="200">
<template slot-scope="scope">
{{ scope.row.reportPeriodStart }} ~ {{ scope.row.reportPeriodEnd }}
</template>
</el-table-column>
</el-table>
8.8 后端Service改动
8.6.1 AutoCompleteService(一键完成)
文件:AutoCompleteServiceImpl.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
需要修改的方法:
@Override
public AjaxResult batchExecute(BatchExecuteDTO dto) {
List<OverdueOrderVO> 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(报工服务)
需要修改的方法:
@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(工单服务)
需要修改的方法:
- insertWorkOrder 方法:
@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;
}
- insertWorkOrderEntry 方法:
public void insertWorkOrderEntry(WorkOrder workOrder) {
// 检查制造类型
if ("CONTINUOUS".equals(workOrder.getManufactureType())) {
// 连续制造业不创建工单分录
return;
}
// ... 现有逻辑(为离散制造业创建工单分录)...
}
- 批量生成工单方法:
// 从销售订单批量生成工单时,需要判断物料的制造类型
public List<WorkOrder> generateWorkOrdersFromSaleOrder(SalOrder salOrder) {
List<WorkOrder> workOrders = new ArrayList<>();
for (SalOrderEntry entry : salOrder.getEntries()) {
// 查询物料信息
Material material = materialMapper.selectById(entry.getMaterialId());
if ("DISCRETE".equals(material.getManufactureType())) {
// 离散制造业:按工序路线生成多个工单
List<WorkOrder> 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脚本中添加班次字典配置:
-- 添加班次类型字典
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');
前端使用方式
报工页面使用字典组件:
<template>
<!-- 班次选择(仅SHIFT模式显示) -->
<el-form-item v-if="reportMode === 'SHIFT'" label="班次" prop="shiftName">
<el-select v-model="form.shiftName" placeholder="请选择班次">
<el-option
v-for="dict in dict.type.sys_shift_type"
:key="dict.value"
:label="dict.label"
:value="dict.label"
/>
</el-select>
</el-form-item>
</template>
<script>
export default {
// 声明使用的字典类型
dicts: ['sys_shift_type'],
data() {
return {
form: {
shiftName: ''
}
}
}
}
</script>
9.3 配置管理
企业可以在系统管理 → 字典管理中灵活配置:
- 修改班次名称:将"早班"改为"白班"、"A班"等
- 调整班次数量:支持两班制、三班制、四班制
- 设置默认班次:
is_default='Y' - 记录时间段:在
remark字段记录班次时间(仅供参考)
示例配置:
- 两班制:白班(08:00-20:00)、夜班(20:00-08:00)
- 三班制:早班、中班、晚班
- 四班制:A班、B班、C班、D班
9.4 优点
- ✅ 无需新建表:利用现有字典功能
- ✅ 灵活配置:可在系统管理中修改,无需改代码
- ✅ 实施简单:只需执行SQL脚本
- ✅ 满足需求:支持各种班次制度
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种报工模式,使用字典组件获取班次选项
- ✅ 工序执行情况页面:一键完成 + 定时完成 + 进度条显示
- ✅ 工单管理页面:新增列 + 详情页改造
- ✅ 工单执行情况统计页面:制造类型显示 + 进度条差异 + 展开内容差异
关键功能点
- ✅ 制造类型判断(离散/连续)
- ✅ 报工模式支持(5种)
- ✅ 报工控件差异化
- ✅ 一键完成功能改造
- ✅ 定时完成功能改造
- ✅ 工序进度条改造
- ✅ 报工序号自动生成
- ✅ 累计数量统计
- ✅ 工单完成判断
- ✅ 统计报表适配(工单执行情况)
附录B:实施过程中的Bug修复记录
Bug #1:工单查询未返回 manufactureType 字段
日期:2025-11-12
问题描述:
- 前端工单列表页面点击"报工"按钮时,
workOrder.manufactureType为undefined - 导致连续制造业工单无法正确识别,仍然提示"该工单没有工序分录,无法报工!"
根本原因:
WorkOrderMapper.xml 中的查询SQL和resultMap缺少 manufacture_type 等连续制造业相关字段的映射
修复方案:
-
在
WorkOrderMapper.xml的resultMap中添加字段映射:<result property="manufactureType" column="manufacture_type"/> <result property="reportMode" column="report_mode"/> <result property="totalReportCount" column="total_report_count"/> <result property="totalReportedQuantity" column="total_reported_quantity"/> <result property="equipmentId" column="equipment_id"/> <result property="equipmentName" column="equipment_name"/> <result property="actualStartTime" column="actual_start_time"/> <result property="actualEndTime" column="actual_end_time"/> <result property="downtimeMinutes" column="downtime_minutes"/> -
在
selectWorkOrderVoSQL中添加查询字段: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
根本原因:
- 前端问题:
form.vue中使用动态导入API失败,且使用路由参数ID而不是工单真实ID - 后端问题:
ReportMapper.xml中缺少workOrderId的查询条件
修复方案:
前端修复 (mes-ui/src/views/mes/production/report/form.vue):
-
添加API导入:
import {getEntryRealSort, getWorkOrderByEntryId, getWorkOrder} from '@/api/mes/production/workOrder' -
修复API调用和参数传递:
// 正确调用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 两个查询中都添加:
<if test="workOrderId != null and workOrderId != ''"> and prp.work_order_id =#{workOrderId}</if>
影响范围:
- ✅ 不影响离散制造业(仍使用
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修改表结构:
-- 修改 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:
-- 验证修改是否成功
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 |
✅ 已完成 |