Files
MES/yawei-mes/.tasks/2025-11-21_生产流程新增成品入库流程.md
2026-04-02 10:39:03 +08:00

656 lines
18 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.

# 生产流程新增成品入库流程
## 需求概述
在销售订单页面新增"生产入库"功能,实现从生产完成到成品入库的流程闭环。
## 业务流程
### 当前流程
```
待生产(A) → 生产中(B) → 生产完成(F) → 已发货(C)
```
### 新增流程
```
待生产(A) → 生产中(B) → 生产完成(F) → 已入库(G) → 部分发货(E)/已发货(C)
```
## 功能详细设计
### 1. 数据字典修改
#### 1.1 新增销售订单状态
`sys_dict_data` 表中新增状态:
| 字段 | 值 |
|------|-----|
| dict_label | 已入库 |
| dict_value | G |
| dict_type | salorder_status |
| dict_sort | 5 |
| css_class | success |
| remark | 生产完成后已入库,待发货 |
**SQL语句**
```sql
-- 注意ID 需要根据实际数据库中的最大ID来设置这里假设使用 206 之后的ID
INSERT INTO `sys_dict_data`
VALUES (NULL, 7, '已入库', 'G', 'salorder_status', NULL, 'success', 'N', '0',
'admin', NOW(), '', NULL, '生产完成后已入库,待发货');
```
**说明:**
- 当前数据库中已有状态A(待生产)、B(生产中)、C(已发货)、D(已关闭)、E(部分发货)、F(生产完成)
- 新增 G(已入库) 状态dict_sort 设为 7在F之后
### 2. 前端页面修改
#### 2.1 销售订单列表页面 (`index.vue`)
**新增按钮位置:**
在"销售出库"按钮后面添加"生产入库"按钮
```vue
<el-col :span="1.5">
<el-button
plain
style="color: #0E7C7B; background-color: #A7E8BD"
icon="el-icon-download"
size="mini"
@click="openManufactureIntoForm()"
:disabled="multiple"
>生产入库</el-button>
</el-col>
```
**按钮启用条件:**
- 必须选中至少一条订单明细
- 选中的订单明细状态必须为 `F`(生产完成)
#### 2.2 生产入库弹窗设计
**弹窗标题:** 生产入库
**表单字段:**
| 字段名 | 字段说明 | 数据来源 | 是否必填 | 备注 |
|--------|----------|----------|----------|------|
| 入库单编号 | number | 自动生成 | 是 | 禁用输入 |
| 入库日期 | intoDate | 默认当前日期 | 是 | 可修改 |
| 交货人 | delivererId | 手动选择 | 是 | 下拉选择 |
| 仓库 | warehouseId | 手动选择 | 是 | 下拉选择 |
| 备注 | remark | 手动输入 | 否 | 文本域 |
**明细表格字段:**
| 字段名 | 字段说明 | 数据来源 | 是否必填 | 备注 |
|--------|----------|----------|----------|------|
| 序号 | sort | 自动生成 | 是 | 禁用 |
| 产品名 | materialName | 销售订单 | 是 | 禁用 |
| 产品编号 | materialNumber | 销售订单 | 是 | 禁用 |
| 规格型号 | specification | 销售订单 | 否 | 可修改 |
| 单位 | materialUnitName | 销售订单 | 是 | 禁用 |
| 数量 | quantity | 销售订单 | 是 | 可修改 |
| 批次编号 | batchNumber | 手动输入 | 否 | 可输入 |
| 生产日期 | manufactureDate | 默认当前日期 | 否 | 可修改 |
### 3. 后端接口设计
#### 3.1 新增接口
**接口路径:** `/sale/saleOrder/createManufactureInto`
**请求方法:** POST
**请求参数:**
```json
{
"saleOrderEntryIds": [1, 2, 3], // 销售订单明细ID数组
"manufactureInto": {
"intoDate": "2025-11-21",
"delivererId": 1,
"delivererName": "张三",
"warehouseId": 1,
"warehouseNumber": "WH001",
"warehouseName": "成品仓",
"remark": "备注信息"
}
}
```
**返回结果:**
```json
{
"code": 200,
"msg": "生产入库成功",
"data": {
"manufactureIntoId": 123,
"number": "MI202511210001"
}
}
```
#### 3.2 业务逻辑
1. **数据验证**
- 验证所有订单明细状态必须为 `F`(生产完成)
- 验证仓库、交货人等必填字段
2. **创建完工入库单**
- 生成入库单编号
- 创建入库单主表记录
- 根据销售订单明细创建入库单明细
3. **更新销售订单状态**
- 将选中的销售订单明细状态从 `F` 更新为 `G`(已入库)
- 检查主表所有明细状态,如果全部为 `G`,则更新主表状态为 `G`
4. **库存处理**
- 调用库存模块接口,增加成品库存
### 4. 状态流转规则
#### 4.1 允许的状态流转
```
A(待生产) → B(生产中) [工序排产]
B(生产中) → F(生产完成) [工单完成]
F(生产完成) → G(已入库) [生产入库] ✨新增
G(已入库) → E(部分发货) [销售出库-部分]
G(已入库) → C(已发货) [销售出库-全部]
E(部分发货) → C(已发货) [销售出库-剩余]
任意状态 → D(已关闭) [手动关闭]
```
#### 4.2 状态验证规则
- **生产入库按钮**:只能对状态为 `F` 的订单明细操作
- **销售出库按钮**:只能对状态为 `G` 的订单明细操作(需修改原有逻辑)
- **发货完成按钮**:只能对状态为 `G``E` 的订单明细操作
### 5. 前端方法实现
#### 5.1 打开生产入库弹窗
```javascript
openManufactureIntoForm() {
// 验证选中的订单明细状态
const selectedEntries = this.saleOrderList.filter(item =>
this.entryIds.includes(item.id)
);
// 检查是否都是生产完成状态
const hasInvalidStatus = selectedEntries.some(item => item.status !== 'F');
if (hasInvalidStatus) {
this.$modal.msgWarning("只能对生产完成(F)状态的订单进行入库操作!");
return;
}
// 跳转到生产入库页面携带订单明细ID
this.$router.push({
path: "/mes/manufactureInto-add/index",
query: { saleOrderEntryIds: this.entryIds.join(',') }
});
}
```
#### 5.2 完工入库单页面自动填充
`manufactureInto/form.vue``created()` 方法中:
```javascript
created() {
// 检查是否从销售订单跳转过来
const saleOrderEntryIds = this.$route.query.saleOrderEntryIds;
if (saleOrderEntryIds) {
this.loadFromSaleOrder(saleOrderEntryIds);
}
// 原有逻辑...
}
async loadFromSaleOrder(entryIds) {
try {
// 调用接口获取销售订单明细
const { listEntryByIds } = await import("@/api/mes/sale/saleOrder");
const response = await listEntryByIds({ ids: entryIds });
const saleOrderEntries = response.data;
// 自动填充明细数据
this.form.manufactureIntoEntryList = saleOrderEntries.map((entry, index) => ({
sort: index + 1,
materialId: entry.materialId,
materialName: entry.materialName,
materialNumber: entry.materialNumber,
specification: entry.materialSpecification,
materialUnitId: entry.unitId,
materialUnitName: entry.unitName,
quantity: entry.quantity,
batchNumber: '', // 需要手动填写
manufactureDate: new Date(), // 默认当前日期
saleOrderEntryId: entry.id // 保存关联关系
}));
this.maxIndex = saleOrderEntries.length;
} catch (error) {
console.error('加载销售订单数据失败:', error);
this.$modal.msgError("加载订单数据失败:" + error.message);
}
}
```
### 6. 后端实现要点
#### 6.1 Service层新增方法
**接口定义:** `ISalOrderService.java`
```java
/**
* 从销售订单创建生产入库单
*
* @param entryIds 销售订单明细ID数组
* @param manufactureInto 入库单信息
* @return 结果
*/
AjaxResult createManufactureIntoFromSaleOrder(String entryIds, ManufactureInto manufactureInto);
```
**实现逻辑:** `SalOrderServiceImpl.java`
```java
@Override
@Transactional
public AjaxResult createManufactureIntoFromSaleOrder(String entryIds, ManufactureInto manufactureInto) {
// 1. 查询销售订单明细
String[] ids = entryIds.split(",");
List<SalOrderEntry> entries = salOrderEntryMapper.selectByIds(ids);
// 2. 验证状态
for (SalOrderEntry entry : entries) {
if (!"F".equals(entry.getStatus())) {
throw new ServiceException("订单明细【" + entry.getMaterialName() + "】状态不是生产完成,无法入库");
}
}
// 3. 创建入库单
manufactureIntoService.insertManufactureInto(manufactureInto);
// 4. 更新销售订单状态为已入库(G)
for (String id : ids) {
SalOrderEntry entry = new SalOrderEntry();
entry.setId(Long.parseLong(id));
entry.setStatus("G");
salOrderEntryMapper.updateSalOrderEntry(entry);
}
// 5. 更新主表状态
updateMainOrderStatus(entries);
return AjaxResult.success("生产入库成功", manufactureInto);
}
```
#### 6.2 Controller层新增接口
**文件:** `SalOrderController.java`
```java
/**
* 从销售订单创建生产入库单
*/
@PreAuthorize("@ss.hasPermi('sale:saleOrder:manufactureInto')")
@Log(title = "销售订单-生产入库", businessType = BusinessType.INSERT)
@PostMapping("/createManufactureInto")
public AjaxResult createManufactureInto(@RequestBody Map<String, Object> params) {
String entryIds = (String) params.get("saleOrderEntryIds");
ManufactureInto manufactureInto = JSON.parseObject(
JSON.toJSONString(params.get("manufactureInto")),
ManufactureInto.class
);
return salOrderService.createManufactureIntoFromSaleOrder(entryIds, manufactureInto);
}
```
### 7. 数据库修改
#### 7.1 完工入库单明细表添加关联字段
```sql
ALTER TABLE `wm_manufacture_into_entry`
ADD COLUMN `sale_order_entry_id` BIGINT(20) NULL COMMENT '销售订单明细ID' AFTER `manufacture_date`;
ALTER TABLE `wm_manufacture_into_entry`
ADD INDEX `idx_sale_order_entry_id` (`sale_order_entry_id`);
```
### 8. 权限配置
#### 8.1 新增权限标识
在系统管理 → 菜单管理中,为销售订单菜单添加按钮权限:
| 权限名称 | 权限标识 | 备注 |
|---------|---------|------|
| 生产入库 | sale:saleOrder:manufactureInto | 销售订单-生产入库按钮 |
### 9. 测试用例
#### 9.1 正常流程测试
1. 创建销售订单
2. 进行工序排产,生成工单
3. 完成工单报工,订单状态变为 `F`
4. 选中状态为 `F` 的订单,点击"生产入库"
5. 验证弹窗数据自动填充正确
6. 填写必填项(交货人、仓库)
7. 提交入库单
8. 验证订单状态变为 `G`
9. 验证库存增加
10. 进行销售出库
11. 验证订单状态变为 `C``E`
#### 9.2 异常流程测试
1. **状态验证**:选中非 `F` 状态的订单,点击生产入库,应提示错误
2. **必填项验证**:不填交货人或仓库,应提示错误
3. **并发测试**:同一订单同时进行入库操作,应避免重复入库
4. **回滚测试**:入库失败时,订单状态不应变更
### 10. 注意事项
1. **状态流转限制**
- 必须严格按照 F → G → E/C 的顺序流转
- 不允许跳过 G 状态直接从 F 到 C
2. **库存处理**
- 生产入库时增加成品库存
- 销售出库时减少成品库存
- 需要考虑库存不足的情况
3. **数据一致性**
- 入库单与销售订单的关联关系必须准确
- 状态更新需要事务保证
4. **用户体验**
- 自动填充数据减少手工录入
- 明确的状态提示和错误信息
- 按钮的启用/禁用状态要准确
### 11. 实施步骤
1.**数据库修改**
- ✅ 添加字典数据 (已创建SQL文件)
- ✅ 修改入库单明细表结构 (已创建SQL文件)
2.**后端开发**
- ✅ Service层方法实现
- ✅ 状态流转逻辑
- ✅ 实体类字段添加
3.**前端开发**
- ✅ 销售订单页面添加按钮
- ✅ 完工入库单页面自动填充逻辑
- ✅ 销售出库状态验证更新
4.**测试验证**
- ⏳ 数据库脚本执行
- ⏳ 功能测试
- ⏳ 用户验收测试
5.**上线部署**
- ⏳ 数据库脚本执行
- ⏳ 代码部署
- ⏳ 权限配置
---
## 已完成工作 (2025-11-21)
### 1. 数据库脚本 ✅
#### 1.1 字典数据
**文件:** `2025-11-21_02_周启威_字典新增销售订单状态.sql`
```sql
INSERT INTO `sys_dict_data`
VALUES (NULL, 7, '已入库', 'G', 'salorder_status', NULL, 'success', 'N', '0',
'admin', NOW(), '', NULL, '生产完成后已入库,待发货');
```
#### 1.2 表结构修改
**文件:** `2025-11-21_03_周启威_入库单明细表添加销售订单关联字段.sql`
```sql
ALTER TABLE `wm_manufacture_into_entry`
ADD COLUMN `sale_order_entry_id` BIGINT(20) NULL COMMENT '销售订单明细ID' AFTER `manufacture_date`;
ALTER TABLE `wm_manufacture_into_entry`
ADD INDEX `idx_sale_order_entry_id` (`sale_order_entry_id`);
```
### 2. 后端实现 ✅
#### 2.1 实体类修改
**文件:** `ManufactureIntoEntry.java`
新增字段:
```java
/** 销售订单明细ID */
@Excel(name = "销售订单明细ID")
private Long saleOrderEntryId;
```
#### 2.2 Service层实现
**文件:** `ManufactureIntoServiceImpl.java`
**修改内容:**
1. 注入 `SalOrderEntryMapper`
2.`insertManufactureInto()` 方法中添加状态更新调用
3. 新增 `updateSaleOrderStatus()` 方法
**核心代码:**
```java
/**
* 更新销售订单状态为已入库(G)
*/
private void updateSaleOrderStatus(ManufactureInto manufactureInto) {
List<ManufactureIntoEntry> entryList = manufactureInto.getManufactureIntoEntryList();
if (StringUtils.isNotNull(entryList)) {
for (ManufactureIntoEntry entry : entryList) {
if (entry.getSaleOrderEntryId() != null) {
SalOrderEntry salOrderEntry = new SalOrderEntry();
salOrderEntry.setId(entry.getSaleOrderEntryId());
salOrderEntry.setStatus("G"); // G=已入库
salOrderEntryMapper.updateById(salOrderEntry);
}
}
}
}
```
### 3. 前端实现 ✅
#### 3.1 销售订单列表页面
**文件:** `mes-ui/src/views/mes/sale/saleOrder/index.vue`
**修改内容:**
1. **新增"生产入库"按钮**
```vue
<el-col :span="1.5">
<el-button
plain
style="color: #0E7C7B; background-color: #A7E8BD"
icon="el-icon-download"
size="mini"
@click="openManufactureIntoForm()"
:disabled="multiple"
>生产入库</el-button>
</el-col>
```
2. **新增 `openManufactureIntoForm()` 方法**
```javascript
openManufactureIntoForm(){
const selectedEntries = this.saleOrderList.filter(item =>
this.entryIds.includes(item.id)
);
const hasInvalidStatus = selectedEntries.some(item => item.status !== 'F');
if (hasInvalidStatus) {
this.$modal.msgWarning("只能对生产完成(F)状态的订单进行入库操作!");
return;
}
this.$router.push({
path: "/wm/manufactureInto-add/index",
query: { saleOrderEntryIds: this.entryIds.join(',') }
});
}
```
3. **修改 `openSalOutForm()` 方法**
```javascript
// 必须是G(已入库)或E(部分发货)状态才能发货
if(saleOrder[0].status !="G" && saleOrder[0].status !="E"){
this.$modal.msgWarning("只能对已入库(G)或部分发货(E)状态的订单进行发货操作!请重新勾选!")
return;
}
```
#### 3.2 完工入库单表单页面
**文件:** `mes-ui/src/views/mes/warehouse/manufactureInto/form.vue`
**修改内容:**
1. **修改 `created()` 方法**
```javascript
created() {
this.formType=false;
this.getUserList();
this.getWarehouseList();
// 检查是否从销售订单跳转过来
const saleOrderEntryIds = this.$route.query.saleOrderEntryIds;
if (saleOrderEntryIds) {
this.loadFromSaleOrder(saleOrderEntryIds);
}
const id = this.$route.params && this.$route.params.id;
if ( typeof(id) !='undefined' ){
this.getForm(id);
}
}
```
2. **新增 `loadFromSaleOrder()` 方法**
```javascript
async loadFromSaleOrder(entryIds) {
try {
this.$modal.loading("正在加载订单数据...");
const { listEntryByIds } = await import("@/api/mes/sale/saleOrder");
const response = await listEntryByIds({ ids: entryIds });
const saleOrderEntries = response.data;
if (!saleOrderEntries || saleOrderEntries.length === 0) {
this.$modal.msgError("未找到销售订单数据");
return;
}
this.form.manufactureIntoEntryList = saleOrderEntries.map((entry, index) => ({
sort: index + 1,
materialId: entry.materialId,
materialName: entry.materialName,
materialNumber: entry.materialNumber,
specification: entry.materialSpecification,
materialUnitId: entry.unitId,
materialUnitName: entry.unitName,
quantity: entry.quantity,
batchNumber: '',
manufactureDate: new Date().format("yyyy-MM-dd"),
saleOrderEntryId: entry.id
}));
this.maxIndex = saleOrderEntries.length;
this.$modal.closeLoading();
this.$modal.msgSuccess(`已自动填充 ${saleOrderEntries.length} 条订单明细数据`);
} catch (error) {
this.$modal.closeLoading();
console.error('加载销售订单数据失败:', error);
this.$modal.msgError("加载订单数据失败:" + (error.message || "未知错误"));
}
}
```
---
## 测试要点
### 功能测试清单
1.**生产入库按钮**
- 按钮位置正确(工序排产和销售出库之间)
- 按钮样式正确(绿色系)
- 未选中订单时按钮禁用
2.**状态验证**
- 选中非F状态订单提示错误
- 选中F状态订单正常跳转
3.**数据自动填充**
- 明细数据自动填充
- 字段映射正确
- 关联ID保存正确
4.**状态流转**
- 入库后订单状态变为G
- 销售出库只能选G或E状态
- 状态显示正确
### 待测试项
- [ ] 执行数据库脚本
- [ ] 完整流程测试F → G → E/C
- [ ] 并发测试
- [ ] 异常回滚测试
---
## 附录
### A. 相关文件清单
**前端文件:**
- `mes-ui/src/views/mes/sale/saleOrder/index.vue` - 销售订单列表页
- `mes-ui/src/views/mes/warehouse/manufactureInto/form.vue` - 完工入库单表单
- `mes-ui/src/api/mes/sale/saleOrder.js` - 销售订单API
**后端文件:**
- `yjh-mes/src/main/java/cn/sourceplan/sale/service/ISalOrderService.java` - Service接口
- `yjh-mes/src/main/java/cn/sourceplan/sale/service/impl/SalOrderServiceImpl.java` - Service实现
- `yjh-mes/src/main/java/cn/sourceplan/sale/controller/SalOrderController.java` - Controller
**数据库文件:**
- `.sql/upgrade_add_manufacture_into_status.sql` - 升级脚本
### B. 状态码对照表
| 状态码 | 状态名称 | 颜色 | 说明 |
|-------|---------|------|------|
| A | 待生产 | info (灰色) | 订单已创建,待排产 |
| B | 生产中 | warning (橙色) | 已排产,生产中 |
| F | 生产完成 | success (绿色) | 工单已完成,待入库 |
| G | 已入库 | success (绿色) | 已完成入库,待发货 ✨新增 |
| E | 部分发货 | primary (蓝色) | 已部分发货 |
| C | 已发货 | success (绿色) | 已全部发货 |
| D | 已关闭 | danger (红色) | 订单已关闭 |
---
**文档版本:** v1.0
**创建日期:** 2025-11-21
**创建人:** Cascade AI
**最后更新:** 2025-11-21