18 KiB
生产流程新增成品入库流程
需求概述
在销售订单页面新增"生产入库"功能,实现从生产完成到成品入库的流程闭环。
业务流程
当前流程
待生产(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语句:
-- 注意: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)
新增按钮位置: 在"销售出库"按钮后面添加"生产入库"按钮
<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
请求参数:
{
"saleOrderEntryIds": [1, 2, 3], // 销售订单明细ID数组
"manufactureInto": {
"intoDate": "2025-11-21",
"delivererId": 1,
"delivererName": "张三",
"warehouseId": 1,
"warehouseNumber": "WH001",
"warehouseName": "成品仓",
"remark": "备注信息"
}
}
返回结果:
{
"code": 200,
"msg": "生产入库成功",
"data": {
"manufactureIntoId": 123,
"number": "MI202511210001"
}
}
3.2 业务逻辑
-
数据验证
- 验证所有订单明细状态必须为
F(生产完成) - 验证仓库、交货人等必填字段
- 验证所有订单明细状态必须为
-
创建完工入库单
- 生成入库单编号
- 创建入库单主表记录
- 根据销售订单明细创建入库单明细
-
更新销售订单状态
- 将选中的销售订单明细状态从
F更新为G(已入库) - 检查主表所有明细状态,如果全部为
G,则更新主表状态为G
- 将选中的销售订单明细状态从
-
库存处理
- 调用库存模块接口,增加成品库存
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 打开生产入库弹窗
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() 方法中:
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
/**
* 从销售订单创建生产入库单
*
* @param entryIds 销售订单明细ID数组
* @param manufactureInto 入库单信息
* @return 结果
*/
AjaxResult createManufactureIntoFromSaleOrder(String entryIds, ManufactureInto manufactureInto);
实现逻辑: SalOrderServiceImpl.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
/**
* 从销售订单创建生产入库单
*/
@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 完工入库单明细表添加关联字段
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 正常流程测试
- 创建销售订单
- 进行工序排产,生成工单
- 完成工单报工,订单状态变为
F - 选中状态为
F的订单,点击"生产入库" - 验证弹窗数据自动填充正确
- 填写必填项(交货人、仓库)
- 提交入库单
- 验证订单状态变为
G - 验证库存增加
- 进行销售出库
- 验证订单状态变为
C或E
9.2 异常流程测试
- 状态验证:选中非
F状态的订单,点击生产入库,应提示错误 - 必填项验证:不填交货人或仓库,应提示错误
- 并发测试:同一订单同时进行入库操作,应避免重复入库
- 回滚测试:入库失败时,订单状态不应变更
10. 注意事项
-
状态流转限制
- 必须严格按照 F → G → E/C 的顺序流转
- 不允许跳过 G 状态直接从 F 到 C
-
库存处理
- 生产入库时增加成品库存
- 销售出库时减少成品库存
- 需要考虑库存不足的情况
-
数据一致性
- 入库单与销售订单的关联关系必须准确
- 状态更新需要事务保证
-
用户体验
- 自动填充数据减少手工录入
- 明确的状态提示和错误信息
- 按钮的启用/禁用状态要准确
11. 实施步骤
-
✅ 数据库修改
- ✅ 添加字典数据 (已创建SQL文件)
- ✅ 修改入库单明细表结构 (已创建SQL文件)
-
✅ 后端开发
- ✅ Service层方法实现
- ✅ 状态流转逻辑
- ✅ 实体类字段添加
-
✅ 前端开发
- ✅ 销售订单页面添加按钮
- ✅ 完工入库单页面自动填充逻辑
- ✅ 销售出库状态验证更新
-
⏳ 测试验证
- ⏳ 数据库脚本执行
- ⏳ 功能测试
- ⏳ 用户验收测试
-
⏳ 上线部署
- ⏳ 数据库脚本执行
- ⏳ 代码部署
- ⏳ 权限配置
已完成工作 (2025-11-21)
1. 数据库脚本 ✅
1.1 字典数据
文件: 2025-11-21_02_周启威_字典新增销售订单状态.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
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
新增字段:
/** 销售订单明细ID */
@Excel(name = "销售订单明细ID")
private Long saleOrderEntryId;
2.2 Service层实现
文件: ManufactureIntoServiceImpl.java
修改内容:
- 注入
SalOrderEntryMapper - 在
insertManufactureInto()方法中添加状态更新调用 - 新增
updateSaleOrderStatus()方法
核心代码:
/**
* 更新销售订单状态为已入库(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
修改内容:
- 新增"生产入库"按钮
<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>
- 新增
openManufactureIntoForm()方法
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(',') }
});
}
- 修改
openSalOutForm()方法
// 必须是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
修改内容:
- 修改
created()方法
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);
}
}
- 新增
loadFromSaleOrder()方法
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 || "未知错误"));
}
}
测试要点
功能测试清单
-
✅ 生产入库按钮
- 按钮位置正确(工序排产和销售出库之间)
- 按钮样式正确(绿色系)
- 未选中订单时按钮禁用
-
✅ 状态验证
- 选中非F状态订单,提示错误
- 选中F状态订单,正常跳转
-
✅ 数据自动填充
- 明细数据自动填充
- 字段映射正确
- 关联ID保存正确
-
⏳ 状态流转
- 入库后订单状态变为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