# 生产流程新增成品入库流程 ## 需求概述 在销售订单页面新增"生产入库"功能,实现从生产完成到成品入库的流程闭环。 ## 业务流程 ### 当前流程 ``` 待生产(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 生产入库 ``` **按钮启用条件:** - 必须选中至少一条订单明细 - 选中的订单明细状态必须为 `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 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 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 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 生产入库 ``` 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