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
// 在工单卡片中显示制造类型和报工模式
// 方法:获取报工模式文本
getReportModeText(mode) {
const modeMap = {
'PROCESS': '按工序',
'TIME': '按时间',
'SHIFT': '按班次',
'QUANTITY': '按产量',
'BATCH': '按批次'
};
return modeMap[mode] || mode;
}
```
2. **进度条显示差异**:
```javascript
// 离散制造业:显示工序进度
当前工序:{{ workOrder.currentProcess || '未开始' }}
{{ workOrder.completedProcessCount }}/{{ workOrder.totalProcessCount }} 个工序
// 连续制造业:显示报工次数和累计产量
已报工:{{ workOrder.totalReportCount }} 次
累计产量:{{ workOrder.totalReportedQuantity }}/{{ workOrder.quantity }}
```
3. **展开内容差异**:
```javascript
// 离散制造业:显示工序列表
// 连续制造业:显示报工历史
{{ scope.row.reportPeriodStart }} ~ {{ scope.row.reportPeriodEnd }}
```
### 8.8 后端Service改动
#### 8.6.1 AutoCompleteService(一键完成)
**文件**:`AutoCompleteServiceImpl.java`
**需要修改的方法**:
```java
@Override
@Transactional
public AjaxResult autoCompleteSaleOrder(AutoCompleteDTO dto) {
// 1. 查询物料信息
Material material = materialMapper.selectById(salOrderEntry.getMaterialId());
// 2. 判断制造类型
if ("DISCRETE".equals(material.getManufactureType())) {
// 现有逻辑:按工序生成多个工单 + 自动报工
return autoCompleteDiscrete(dto, material);
} else if ("CONTINUOUS".equals(material.getManufactureType())) {
// 新增逻辑:生成单个工单,不自动报工
return autoCompleteContinuous(dto, material);
}
}
// 新增方法:连续制造业一键完成
private AjaxResult autoCompleteContinuous(AutoCompleteDTO dto, Material material) {
// 1. 生成单个工单(不按工序)
WorkOrder workOrder = new WorkOrder();
workOrder.setBatchNumber(salOrder.getNumber()); // 批次号 = 订单号
workOrder.setManufactureType("CONTINUOUS");
workOrder.setReportMode(material.getReportMode());
workOrder.setQuantity(salOrderEntry.getQuantity());
// ... 其他字段
workOrderMapper.insert(workOrder);
// 2. 不创建工单分录
// 3. 不自动创建报工单
// 4. 更新订单状态为"已生成工单"(不是"生产完成")
return AjaxResult.success("工单生成成功,请手动报工");
}
```
#### 8.6.2 TimedCompleteService(定时完成)
**文件**:`TimedCompleteServiceImpl.java`
**需要修改的方法**:
```java
@Override
public AjaxResult batchExecute(BatchExecuteDTO dto) {
List orders = getOverdueOrders(...);
for (OverdueOrderVO order : orders) {
// 查询物料信息
Material material = materialMapper.selectById(order.getMaterialId());
// 判断制造类型
if ("DISCRETE".equals(material.getManufactureType())) {
// 自动完成
autoCompleteService.autoCompleteSaleOrder(...);
} else if ("CONTINUOUS".equals(material.getManufactureType())) {
// 跳过或仅生成工单
log.info("跳过连续制造业订单: " + order.getOrderNumber());
skipCount++;
}
}
}
```
#### 8.6.3 ReportService(报工服务)
**需要修改的方法**:
```java
@Override
public AjaxResult submitReport(Report report) {
// 1. 判断是离散还是连续制造业
if (report.getWorkOrderEntryId() != null) {
// 离散制造业:现有逻辑
return submitDiscreteReport(report);
} else if (report.getWorkOrderId() != null) {
// 连续制造业:新逻辑
return submitContinuousReport(report);
}
}
// 新增方法:连续制造业报工
private AjaxResult submitContinuousReport(Report report) {
// 1. 查询工单
WorkOrder workOrder = workOrderMapper.selectById(report.getWorkOrderId());
// 2. 自动生成报工序号
Integer maxSequence = reportMapper.selectMaxSequence(report.getWorkOrderId());
report.setReportSequence(maxSequence == null ? 1 : maxSequence + 1);
// 3. 插入报工单
reportMapper.insert(report);
// 4. 更新工单累计数据
workOrder.setTotalReportCount(workOrder.getTotalReportCount() + 1);
workOrder.setTotalReportedQuantity(
workOrder.getTotalReportedQuantity().add(report.getReportQuantity())
);
// 5. 判断是否完成
if (workOrder.getTotalReportedQuantity().compareTo(workOrder.getQuantity()) >= 0) {
workOrder.setProStatus("FINISHED");
workOrder.setRealFinishDate(new Date());
}
workOrderMapper.updateById(workOrder);
return AjaxResult.success("报工成功");
}
```
#### 8.6.4 WorkOrderService(工单服务)
**需要修改的方法**:
1. **insertWorkOrder 方法**:
```java
@Override
public int insertWorkOrder(WorkOrder workOrder) {
// ... 现有逻辑 ...
// 新增:保存工单后,判断是否需要创建工单分录
int rows = workOrderMapper.insert(workOrder);
// 判断制造类型
if ("DISCRETE".equals(workOrder.getManufactureType())) {
// 离散制造业:创建工单分录(按工序)
insertWorkOrderEntry(workOrder);
} else if ("CONTINUOUS".equals(workOrder.getManufactureType())) {
// 连续制造业:不创建工单分录
// 工单直接可用于报工
}
return rows;
}
```
2. **insertWorkOrderEntry 方法**:
```java
public void insertWorkOrderEntry(WorkOrder workOrder) {
// 检查制造类型
if ("CONTINUOUS".equals(workOrder.getManufactureType())) {
// 连续制造业不创建工单分录
return;
}
// ... 现有逻辑(为离散制造业创建工单分录)...
}
```
3. **批量生成工单方法**:
```java
// 从销售订单批量生成工单时,需要判断物料的制造类型
public List generateWorkOrdersFromSaleOrder(SalOrder salOrder) {
List workOrders = new ArrayList<>();
for (SalOrderEntry entry : salOrder.getEntries()) {
// 查询物料信息
Material material = materialMapper.selectById(entry.getMaterialId());
if ("DISCRETE".equals(material.getManufactureType())) {
// 离散制造业:按工序路线生成多个工单
List processWorkOrders = generateDiscreteWorkOrders(entry, material);
workOrders.addAll(processWorkOrders);
} else if ("CONTINUOUS".equals(material.getManufactureType())) {
// 连续制造业:生成单个工单
WorkOrder workOrder = generateContinuousWorkOrder(entry, material);
workOrders.add(workOrder);
}
}
return workOrders;
}
```
---
## 9. 班次管理说明
### 9.1 班次名称配置
**问题**:连续制造业按班次报工时,需要选择班次名称(如:早班、中班、晚班)。
**解决方案**:使用系统现有的**数据字典功能**管理班次选项。
### 9.2 实现方式
#### 数据库配置
已在SQL脚本中添加班次字典配置:
```sql
-- 添加班次类型字典
INSERT INTO `sys_dict_type` (`dict_name`, `dict_type`, `status`, `create_by`, `create_time`, `remark`)
VALUES ('班次类型', 'sys_shift_type', '0', 'admin', NOW(), '用于连续制造业按班次报工');
-- 添加默认班次选项
INSERT INTO `sys_dict_data` (`dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `status`, `create_by`, `create_time`, `remark`) VALUES
(1, '早班', 'MORNING', 'sys_shift_type', '', 'default', 'Y', '0', 'admin', NOW(), '08:00-16:00'),
(2, '中班', 'AFTERNOON', 'sys_shift_type', '', 'default', 'N', '0', 'admin', NOW(), '16:00-00:00'),
(3, '晚班', 'NIGHT', 'sys_shift_type', '', 'default', 'N', '0', 'admin', NOW(), '00:00-08:00');
```
#### 前端使用方式
报工页面使用字典组件:
```vue
```
### 9.3 配置管理
企业可以在**系统管理 → 字典管理**中灵活配置:
1. **修改班次名称**:将"早班"改为"白班"、"A班"等
2. **调整班次数量**:支持两班制、三班制、四班制
3. **设置默认班次**:`is_default='Y'`
4. **记录时间段**:在`remark`字段记录班次时间(仅供参考)
**示例配置**:
- 两班制:白班(08:00-20:00)、夜班(20:00-08:00)
- 三班制:早班、中班、晚班
- 四班制:A班、B班、C班、D班
### 9.4 优点
1. ✅ **无需新建表**:利用现有字典功能
2. ✅ **灵活配置**:可在系统管理中修改,无需改代码
3. ✅ **实施简单**:只需执行SQL脚本
4. ✅ **满足需求**:支持各种班次制度
## 10. 废弃内容说明
以下内容在简化方案中**不实施**:
❌ 工单分录表的复杂改动(连续制造业不使用工单分录)
❌ 设备管理表(如果系统已有设备模块则复用,否则暂不实施)
❌ 停机记录表(停机信息直接记录在报工单中)
❌ 复杂的批次号规则(第一版只支持订单号作为批次号)
❌ 质量状态字段(第一版只记录合格/不合格数量)
这些功能可在后续版本中根据实际需求逐步添加。
---
**文档版本**: v3.0(完整版)
**创建日期**: 2025-11-11
**最后更新**: 2025-11-11
**状态**: 待实施
**预计工期**: 13个工作日(数据库1天 + 后端5天 + 前端5天 + 测试2天)
---
## 附录:影响范围总结
### 数据库改动
- ✅ `md_material`:2个字段
- ✅ `pro_workorder`:9个字段
- ✅ `pro_report`:9个字段
- ✅ 索引:8个
- ✅ 数据字典:1个字典类型 + 3个字典数据(班次配置)
### 后端文件改动
- ✅ 实体类:3个(Material, ProWorkorder, ProReport)
- ✅ Mapper XML:4个(MaterialMapper, ProWorkorderMapper, ProReportMapper, WorkOrderExecutionMapper)
- ✅ Service:5个(AutoCompleteService, TimedCompleteService, ReportService, WorkOrderService, WorkOrderExecutionService)
- ✅ Controller:2个(ReportController, WorkOrderExecutionController)
### 前端页面改动
- ✅ 物料管理页面:新增2个字段 + 联动逻辑
- ✅ 报工页面:新建或改造,支持5种报工模式,使用字典组件获取班次选项
- ✅ 工序执行情况页面:一键完成 + 定时完成 + 进度条显示
- ✅ 工单管理页面:新增列 + 详情页改造
- ✅ 工单执行情况统计页面:制造类型显示 + 进度条差异 + 展开内容差异
### 关键功能点
1. ✅ 制造类型判断(离散/连续)
2. ✅ 报工模式支持(5种)
3. ✅ 报工控件差异化
4. ✅ 一键完成功能改造
5. ✅ 定时完成功能改造
6. ✅ 工序进度条改造
7. ✅ 报工序号自动生成
8. ✅ 累计数量统计
9. ✅ 工单完成判断
10. ✅ 统计报表适配(工单执行情况)
---
## 附录B:实施过程中的Bug修复记录
### Bug #1:工单查询未返回 `manufactureType` 字段
**日期**:2025-11-12
**问题描述**:
- 前端工单列表页面点击"报工"按钮时,`workOrder.manufactureType` 为 `undefined`
- 导致连续制造业工单无法正确识别,仍然提示"该工单没有工序分录,无法报工!"
**根本原因**:
`WorkOrderMapper.xml` 中的查询SQL和resultMap缺少 `manufacture_type` 等连续制造业相关字段的映射
**修复方案**:
1. 在 `WorkOrderMapper.xml` 的 `resultMap` 中添加字段映射:
```xml
```
2. 在 `selectWorkOrderVo` SQL中添加查询字段:
```xml
a.manufacture_type,
a.report_mode,
a.total_report_count,
a.total_reported_quantity,
a.equipment_id,
a.equipment_name,
a.actual_start_time,
a.actual_end_time,
a.downtime_minutes,
```
**影响范围**:
- ✅ 不影响现有功能
- ✅ 只是补充缺失的字段映射
---
### Bug #2:报工记录查询返回所有工单的数据(804条)
**日期**:2025-11-12
**问题描述**:
- 连续制造业工单点击"报工"后,页面卡死
- 控制台显示查询到804条报工记录(实际应该是0条或该工单的报工记录)
- 原因是查询参数 `workOrderId` 没有传递到后端SQL
**根本原因**:
1. **前端问题**:`form.vue` 中使用动态导入API失败,且使用路由参数ID而不是工单真实ID
2. **后端问题**:`ReportMapper.xml` 中缺少 `workOrderId` 的查询条件
**修复方案**:
**前端修复** (`mes-ui/src/views/mes/production/report/form.vue`):
1. 添加API导入:
```javascript
import {getEntryRealSort, getWorkOrderByEntryId, getWorkOrder} from '@/api/mes/production/workOrder'
```
2. 修复API调用和参数传递:
```javascript
// 正确调用API
const apiCall = isContinuous ? getWorkOrder(id) : getWorkOrderByEntryId(id);
// 使用响应数据中的真实工单ID
if (isContinuous) {
this.form.workOrderId = response.data.id; // 使用真实ID
this.form.workOrderEntryId = null;
}
// 查询报工记录时使用真实ID
const queryParams = isContinuous ?
{ pageSize:99999, workOrderId: response.data.id } :
{ pageSize:99999, workOrderEntryId:id };
```
**后端修复** (`ReportMapper.xml`):
在 `selectReportList` 和 `selectReportListCount` 两个查询中都添加:
```xml
and prp.work_order_id =#{workOrderId}
```
**影响范围**:
- ✅ 不影响离散制造业(仍使用 `workOrderEntryId` 查询)
- ✅ 修复连续制造业报工查询性能问题
---
### Bug #3:报工保存失败 - `work_order_entry_id` 字段无默认值
**日期**:2025-11-12
**问题描述**:
- 连续制造业报工点击"保存"按钮时报错
- 错误信息:`Field 'work_order_entry_id' doesn't have a default value`
- 原因是数据库表 `pro_report` 中的 `work_order_entry_id` 字段不允许为空
**根本原因**:
数据库表设计时,`work_order_entry_id` 字段被设置为 `NOT NULL`,但连续制造业报工不使用工序分录,此字段应该为 `NULL`
**修复方案**:
执行SQL修改表结构:
```sql
-- 修改 pro_report 表,让 work_order_entry_id 可以为空
ALTER TABLE pro_report
MODIFY COLUMN work_order_entry_id BIGINT(20) NULL COMMENT '工单子表ID(离散制造业使用)';
-- 为 work_order_entry_id 添加索引(如果不存在)
CREATE INDEX IF NOT EXISTS idx_work_order_entry_id ON pro_report(work_order_entry_id);
```
**影响分析**:
- ✅ **离散制造业**:不受影响,仍然传递 `work_order_entry_id`,值不为空
- ✅ **连续制造业**:可以将 `work_order_entry_id` 设置为 `NULL`
- ✅ **现有报工记录**:不受影响,已有数据保持不变
- ✅ **查询功能**:不受影响,`WHERE work_order_entry_id = ?` 仍然有效
- ✅ **关联查询**:不受影响,`LEFT JOIN` 仍然正常工作
**验证SQL**:
```sql
-- 验证修改是否成功
SHOW CREATE TABLE pro_report;
-- 验证现有数据不受影响
SELECT
COUNT(*) as total_reports,
SUM(CASE WHEN work_order_id IS NOT NULL THEN 1 ELSE 0 END) as continuous_report_count,
SUM(CASE WHEN work_order_entry_id IS NOT NULL THEN 1 ELSE 0 END) as discrete_report_count
FROM pro_report;
```
---
### Bug修复总结
| Bug编号 | 问题 | 影响 | 修复文件 | 状态 |
|--------|------|------|---------|------|
| #1 | 工单查询缺少字段映射 | 前端无法识别制造类型 | `WorkOrderMapper.xml` | ✅ 已修复 |
| #2 | 报工记录查询无过滤条件 | 查询所有报工记录,页面卡死 | `form.vue`, `ReportMapper.xml` | ✅ 已修复 |
| #3 | 字段不允许为空 | 连续制造业报工无法保存 | 数据库表结构 | ✅ 已修复 |
**重要提示**:
- 所有修复都向后兼容,不影响现有离散制造业功能
- 修复后需要重启后端服务和刷新前端页面
- 建议在测试环境验证后再部署到生产环境
---
## 功能增强总结
| 功能编号 | 功能描述 | 实现方案 | 影响文件 | 状态 |
|----------|----------|----------|----------|------|
| Feature #1 | 工单列表数量显示优化 | 显示格式改为"已报工数量/总数量",类似销售订单 | `jinzhong.js` | ✅ 已完成 |
| Feature #2 | 列表页面空值显示优化 | 统一将空值显示为"-" | `jinzhong.js`, `index.vue` | ✅ 已完成 |
| Feature #3 | 连续制造业销售订单状态自动更新 | 连续制造业工单状态变更时自动同步销售订单状态:开始排产→生产中,报工完成→已完成 | `ReportServiceImpl.java`, `WorkOrderServiceImpl.java` | ✅ 已完成 |