Files
MES/yawei-mes/.tasks/2025-11-11_连续制造业改进方案.md
2026-04-02 10:39:03 +08:00

2173 lines
76 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 连续制造业改进方案
## 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`<br>`PROCESS` | `CONTINUOUS`<br>`TIME` | `CONTINUOUS`<br>`SHIFT` | `CONTINUOUS`<br>`QUANTITY` | `CONTINUOUS`<br>`BATCH` |
| **2. 工单生成** | 按工序生成多个<br>`XS001-P01`, `P02` | 生成一个工单<br>`XS001` | 生成一个工单<br>`XS001` | 生成一个工单<br>`XS001` | 生成一个工单<br>`XS001` |
| **3. 工单分录** | 创建(每个工序) | **不创建** | **不创建** | **不创建** | **不创建** |
| **4. 报工方式** | 按工序完成 | 按时间段 | 按班次 | 按产量 | 按批次 |
| **5. 报工示例** | 工序1完成 → 报工 | 08:00-09:00<br>09:00-10:00 | 早班<br>中班<br>晚班 | 累计100吨<br>累计200吨 | 批次1<br>批次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吨 → 报工1100吨08:00-09:30
└─ 累计达到200吨 → 报工2100吨09:30-11:00
└─ 累计达到300吨 → 报工3100吨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完成 → 报工150吨批次号20250111-001
└─ 批次2完成 → 报工248吨批次号20250111-002
└─ 批次3完成 → 报工352吨批次号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`
**改动1resultMap 增加字段映射**
```xml
<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 增加字段**
```xml
<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 增加字段**
```xml
<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 增加字段**
```xml
<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`
**改动1resultMap 增加字段映射**
```xml
<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 增加字段(关键:关联物料表)**
```xml
<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 增加字段**
```xml
<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 增加字段**
```xml
<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新增查询方法查询连续制造业工单**
```xml
<!-- 查询连续制造业工单列表 -->
<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`
**改动1resultMap 增加字段映射**
```xml
<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 增加字段(关键:关联工单表判断制造类型)**
```xml
<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 增加字段**
```xml
<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查询报工记录**
```xml
<!-- 根据工单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 &lt;= #{periodStart} and report_period_end &gt; #{periodStart})
or (report_period_start &lt; #{periodEnd} and report_period_end &gt;= #{periodEnd})
or (report_period_start &gt;= #{periodStart} and report_period_end &lt;= #{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`
**改动:查询订单明细时关联物料的制造类型**
```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
```java
/**
* 查询连续制造业工单列表
*/
List<ProWorkorder> selectContinuousWorkOrders(@Param("proStatus") String proStatus);
```
#### ProReportMapper.java
```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 工单生成逻辑
**离散制造业(保持不变)**
```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和API1天
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. 根据制造类型和报工模式显示不同控件
<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` |
**前端示例代码**
```vue
<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 工序执行情况页面改动
**现有功能**
- 一键完成:为离散制造业订单自动生成工单和报工
- 定时完成(批量完成):定时批量处理超期订单
**需要修改的逻辑**
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
<!-- 需要增加制造类型和报工模式字段 -->
<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>
```
2. **工序列表查询(离散制造业)**
```xml
<!-- 现有逻辑:查询工单分录 -->
<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>
```
3. **报工历史查询(连续制造业)**
```xml
<!-- 新增:直接查询工单的报工记录 -->
<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>
```
4. **Service层适配**
```java
@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`
**需要修改的显示逻辑**
1. **工单卡片显示**
```javascript
// 在工单卡片中显示制造类型和报工模式
<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;
}
```
2. **进度条显示差异**
```javascript
// 离散制造业:显示工序进度
<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>
```
3. **展开内容差异**
```javascript
// 离散制造业:显示工序列表
<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`
**需要修改的方法**
```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<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报工服务
**需要修改的方法**
```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<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脚本中添加班次字典配置
```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
<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 配置管理
企业可以在**系统管理 → 字典管理**中灵活配置:
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 XML4个MaterialMapper, ProWorkorderMapper, ProReportMapper, WorkOrderExecutionMapper
- ✅ Service5个AutoCompleteService, TimedCompleteService, ReportService, WorkOrderService, WorkOrderExecutionService
- ✅ Controller2个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
<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"/>
```
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
<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修改表结构
```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` | ✅ 已完成 |