261 lines
8.4 KiB
Markdown
261 lines
8.4 KiB
Markdown
|
|
# 工单批次分组功能实现说明
|
|||
|
|
|
|||
|
|
## 需求背景
|
|||
|
|
样品生成工单后,需要有一个"分组"标识来识别同一批次生成的工单,方便管理和查看。
|
|||
|
|
|
|||
|
|
## 解决方案
|
|||
|
|
添加"工单批次"字段,用于标识同一批次生成的工单:
|
|||
|
|
- `batch_id`: 批次ID(使用时间戳生成唯一ID)
|
|||
|
|
- `batch_name`: 批次名称(用户填写的"工单名称"或自动生成)
|
|||
|
|
|
|||
|
|
## 实现内容
|
|||
|
|
|
|||
|
|
### ⚠️ 重要:数据类型修复
|
|||
|
|
**问题**: 时间戳(毫秒级)超出 INTEGER 范围,导致 "Out of range value" 错误
|
|||
|
|
**解决**: 将 `batch_id` 字段改为 BIGINT 类型
|
|||
|
|
|
|||
|
|
### 1. 数据库修改
|
|||
|
|
**文件**: `add_workorder_batch_field.sql` 或 `fix_batch_id_bigint.sql`
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
-- 重命名字段并修改类型为 BIGINT
|
|||
|
|
ALTER TABLE test_work_order
|
|||
|
|
CHANGE COLUMN test_order_id batch_id BIGINT NULL COMMENT '工单批次ID(用于分组标识)';
|
|||
|
|
|
|||
|
|
-- 添加批次名称字段
|
|||
|
|
ALTER TABLE test_work_order
|
|||
|
|
ADD COLUMN batch_name VARCHAR(100) NULL COMMENT '工单批次名称' AFTER batch_id;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**如果已经执行过重命名,只需修改类型**:
|
|||
|
|
```sql
|
|||
|
|
-- 紧急修复:只修改字段类型
|
|||
|
|
ALTER TABLE test_work_order
|
|||
|
|
MODIFY COLUMN batch_id BIGINT NULL COMMENT '工单批次ID(用于分组标识)';
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**执行步骤**:
|
|||
|
|
```bash
|
|||
|
|
# 方式1:完整迁移(如果还没执行过)
|
|||
|
|
mysql -u root -p your_database < add_workorder_batch_field.sql
|
|||
|
|
|
|||
|
|
# 方式2:紧急修复(如果已经重命名但类型错误)
|
|||
|
|
mysql -u root -p your_database < fix_batch_id_bigint.sql
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 后端修改
|
|||
|
|
|
|||
|
|
#### 2.1 DO 模型
|
|||
|
|
**文件**: `test_work_order_do.py`
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
from sqlalchemy import BigInteger # 导入 BigInteger
|
|||
|
|
|
|||
|
|
batch_id = Column(BigInteger, nullable=True, comment='工单批次ID(用于分组标识)')
|
|||
|
|
batch_name = Column(String(100), nullable=True, comment='工单批次名称')
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**注意**: 必须使用 `BigInteger` 而不是 `Integer`,因为时间戳(毫秒级)会超出 INTEGER 范围。
|
|||
|
|
|
|||
|
|
#### 2.2 VO 模型
|
|||
|
|
**文件**: `test_work_order_vo.py`
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
batch_id: Optional[int] = Field(default=None, description='工单批次ID')
|
|||
|
|
batch_name: Optional[str] = Field(default=None, description='工单批次名称')
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 2.3 样品生成工单逻辑
|
|||
|
|
**文件**: `warehouse_sample_service.py`
|
|||
|
|
|
|||
|
|
**生成批次ID和名称**:
|
|||
|
|
```python
|
|||
|
|
# 生成批次ID(使用时间戳)
|
|||
|
|
batch_id = int(datetime.now().timestamp() * 1000) # 毫秒级时间戳
|
|||
|
|
batch_name = request_model.work_order_name or f'批次-{datetime.now().strftime("%Y%m%d%H%M%S")}'
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**创建工单时关联批次**:
|
|||
|
|
```python
|
|||
|
|
work_order = TestWorkOrder(
|
|||
|
|
name=work_order_name,
|
|||
|
|
batch_id=batch_id, # 关联批次ID
|
|||
|
|
batch_name=batch_name, # 批次名称
|
|||
|
|
...
|
|||
|
|
)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 2.4 工单查询 DAO
|
|||
|
|
**文件**: `test_work_order_dao.py`
|
|||
|
|
|
|||
|
|
- 添加 `batch_id` 和 `batch_name` 到查询字段
|
|||
|
|
- 将查询条件从 `test_order_id` 改为 `batch_id`
|
|||
|
|
|
|||
|
|
### 3. 前端修改
|
|||
|
|
|
|||
|
|
#### 3.1 工单列表页面
|
|||
|
|
**文件**: `test_work_order/index.vue`
|
|||
|
|
|
|||
|
|
**添加"工单分组"列**:
|
|||
|
|
```vue
|
|||
|
|
<el-table-column label="工单分组" align="center" prop="batchName" width="150" />
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**修改查询参数**:
|
|||
|
|
```javascript
|
|||
|
|
queryParams: {
|
|||
|
|
batchId: null, // 从 testOrderId 改为 batchId
|
|||
|
|
...
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**修改验证逻辑**:
|
|||
|
|
```javascript
|
|||
|
|
// 验证工单是否属于同一批次
|
|||
|
|
const batchIds = [...new Set(this.selectedWorkOrders.map(wo => wo.batchId))];
|
|||
|
|
if (batchIds.length > 1) {
|
|||
|
|
this.$modal.msgError("选中的工单必须属于同一批次");
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 使用说明
|
|||
|
|
|
|||
|
|
### 样品生成工单
|
|||
|
|
1. 进入样品管理页面
|
|||
|
|
2. 选择样品,点击"生成工单"
|
|||
|
|
3. **填写"工单名称"**(可选):
|
|||
|
|
- 如果填写,批次名称 = 工单名称
|
|||
|
|
- 如果不填,批次名称 = `批次-20260108215713`(自动生成)
|
|||
|
|
4. 提交后,所有工单都会有相同的批次ID和批次名称
|
|||
|
|
|
|||
|
|
### 工单列表查看
|
|||
|
|
1. 进入工单列表页面
|
|||
|
|
2. 可以看到"工单分组"列,显示批次名称
|
|||
|
|
3. 同一批次的工单会显示相同的分组名称
|
|||
|
|
4. 可以通过分组名称识别和筛选工单
|
|||
|
|
|
|||
|
|
### 工单生成订单
|
|||
|
|
1. 选择工单时,系统会验证是否属于同一批次
|
|||
|
|
2. 只能选择同一批次的工单生成订单
|
|||
|
|
3. 如果选择了不同批次的工单,会提示错误
|
|||
|
|
|
|||
|
|
## 数据示例
|
|||
|
|
|
|||
|
|
### 数据库数据
|
|||
|
|
```
|
|||
|
|
id | batch_id | batch_name | name | test_eut_id
|
|||
|
|
----|-----------------|-----------------|----------------|------------
|
|||
|
|
721 | 1704726433000 | 测试批次A | 测试批次A-SN001 | 119
|
|||
|
|
722 | 1704726433000 | 测试批次A | 测试批次A-SN002 | 120
|
|||
|
|
723 | 1704726433000 | 测试批次A | 测试批次A-SN003 | 121
|
|||
|
|
724 | 1704726500000 | 批次-20260108 | SN004-入射 | 122
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 前端显示
|
|||
|
|
| 序号 | 工单分组 | 产品SN | 测试类别 | 测试项 |
|
|||
|
|
|------|----------|--------|----------|--------|
|
|||
|
|
| 1 | 测试批次A | SN001 | 入射 | 测试1 |
|
|||
|
|
| 2 | 测试批次A | SN002 | 入射 | 测试1 |
|
|||
|
|
| 3 | 测试批次A | SN003 | 入射 | 测试1 |
|
|||
|
|
| 4 | 批次-20260108 | SN004 | 入射 | 测试1 |
|
|||
|
|
|
|||
|
|
## 优势
|
|||
|
|
|
|||
|
|
1. **清晰的分组标识**:通过批次名称快速识别同一批次的工单
|
|||
|
|
2. **灵活的命名**:用户可以自定义批次名称,也可以使用自动生成的名称
|
|||
|
|
3. **独立的批次管理**:批次ID独立于订单,工单可以独立存在
|
|||
|
|
4. **便于筛选**:可以通过批次名称筛选和查看工单
|
|||
|
|
|
|||
|
|
## 与订单的关系
|
|||
|
|
|
|||
|
|
- **batch_id**: 工单批次ID(样品生成工单时设置,标识同一批次)
|
|||
|
|
- **order_id**: 关联订单ID(从工单生成订单后设置,标识已汇总到订单)
|
|||
|
|
|
|||
|
|
**工单状态**:
|
|||
|
|
1. 新建工单: `batch_id = 时间戳`, `order_id = NULL`
|
|||
|
|
2. 已汇总工单: `batch_id = 时间戳`, `order_id = 订单ID`
|
|||
|
|
|
|||
|
|
## 相关文件
|
|||
|
|
- `add_workorder_batch_field.sql` - 数据库迁移脚本 ✅
|
|||
|
|
- `test_work_order_do.py` - 工单DO模型 ✅
|
|||
|
|
- `test_work_order_vo.py` - 工单VO模型 ✅
|
|||
|
|
- `warehouse_sample_service.py` - 样品服务 ✅
|
|||
|
|
- `test_work_order_dao.py` - 工单DAO ✅
|
|||
|
|
- `test_work_order/index.vue` - 工单列表前端 ✅
|
|||
|
|
|
|||
|
|
## 完成时间
|
|||
|
|
2026-01-08
|
|||
|
|
|
|||
|
|
## 最终修复:DAO 层字段序列化问题
|
|||
|
|
|
|||
|
|
### 问题描述
|
|||
|
|
数据库中有工单数据(batch_id 和 batch_name 都有值),但工单列表页面不显示任何数据。
|
|||
|
|
|
|||
|
|
### 根本原因
|
|||
|
|
DAO 层的 `select()` 语句中,部分字段(如 `batch_id`、`batch_name` 等)没有使用 `.label()` 显式指定列名。虽然 SQLAlchemy 会自动推断列名,但在序列化为 JSON 时可能导致字段名不一致。
|
|||
|
|
|
|||
|
|
### 解决方案
|
|||
|
|
**文件**: `test_work_order_dao.py`
|
|||
|
|
|
|||
|
|
在 `get_test_work_order_list` 方法中,为所有 `TestWorkOrder` 的字段添加 `.label()`:
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
query = (
|
|||
|
|
select(
|
|||
|
|
TestWorkOrder.id.label('id'),
|
|||
|
|
TestWorkOrder.batch_id.label('batch_id'),
|
|||
|
|
TestWorkOrder.batch_name.label('batch_name'),
|
|||
|
|
TestWorkOrder.test_eut_id.label('test_eut_id'),
|
|||
|
|
# ... 其他字段都添加 .label()
|
|||
|
|
)
|
|||
|
|
# ... 其他查询逻辑
|
|||
|
|
)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 为什么需要 .label()
|
|||
|
|
|
|||
|
|
1. **一致性**: 确保所有字段都有明确的列名
|
|||
|
|
2. **序列化**: `SqlalchemyUtil.serialize_result` 依赖 `Row._asdict()` 获取列名
|
|||
|
|
3. **camelCase 转换**: `CamelCaseUtil.snake_to_camel` 需要正确的 snake_case 列名
|
|||
|
|
|
|||
|
|
### 数据流程
|
|||
|
|
```
|
|||
|
|
DAO (Row对象)
|
|||
|
|
→ PageUtil (调用 CamelCaseUtil.transform_result)
|
|||
|
|
→ SqlalchemyUtil (Row._asdict() 转字典)
|
|||
|
|
→ CamelCaseUtil (snake_case → camelCase)
|
|||
|
|
→ Controller (返回 JSON)
|
|||
|
|
→ 前端 (显示数据)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 当前状态
|
|||
|
|
✅ **已完成** - 工单批次分组功能已全部实现并修复
|
|||
|
|
|
|||
|
|
### 完成清单
|
|||
|
|
- [x] 数据库字段类型修复(BIGINT)
|
|||
|
|
- [x] DO 模型更新(BigInteger)
|
|||
|
|
- [x] VO 模型添加字段
|
|||
|
|
- [x] 业务逻辑生成批次ID和名称
|
|||
|
|
- [x] DAO 查询返回批次字段
|
|||
|
|
- [x] 前端显示工单分组列
|
|||
|
|
- [x] **DAO 层字段 label 修复** - 确保所有字段正确序列化
|
|||
|
|
- [x] 工单列表正常显示批次信息
|
|||
|
|
|
|||
|
|
### 验证步骤
|
|||
|
|
1. ✅ 重启后端服务
|
|||
|
|
2. ✅ 清除浏览器缓存(Ctrl + Shift + R)
|
|||
|
|
3. ✅ 检查浏览器开发者工具 Network 标签
|
|||
|
|
4. ✅ 确认 API 返回正确的 camelCase 字段(batchId, batchName)
|
|||
|
|
5. ✅ 工单列表显示"工单分组"列
|
|||
|
|
6. ✅ 批次名称正确显示
|
|||
|
|
|
|||
|
|
## 相关文档
|
|||
|
|
- `工单列表显示修复完成.md` - 详细的修复说明
|
|||
|
|
- `工单列表不显示问题诊断.md` - 诊断步骤
|
|||
|
|
- `QUICKFIX_批次ID类型错误.md` - BIGINT 类型修复
|
|||
|
|
- `修复订单DAO中的test_order_id引用.md` - 订单DAO修复
|
|||
|
|
|
|||
|
|
## 最终完成时间
|
|||
|
|
2026-01-08 23:30
|