304 lines
10 KiB
Markdown
304 lines
10 KiB
Markdown
|
|
# BOM单多级优化开发文档
|
|||
|
|
|
|||
|
|
## 一、现状分析
|
|||
|
|
|
|||
|
|
### 1.1 现有系统架构
|
|||
|
|
|
|||
|
|
当前 `md_new_bom` 模块为**单层BOM结构**:每个BOM主表记录(`md_new_bom`)对应一个产品的物料信息,其下挂多条BOM明细(`md_new_bom_item`),每条明细直接对应具体物料。
|
|||
|
|
|
|||
|
|
现有表字段中,`parent_id` 和 `has_children` 已预置,但未真正实现多级递归展开。
|
|||
|
|
|
|||
|
|
**现有数据模型:**
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
md_new_bom (1) --< md_new_bom_item (N)
|
|||
|
|
└─ materialId (直接指向物料表)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 1.2 关键代码现状
|
|||
|
|
|
|||
|
|
| 层面 | 文件 | 现状 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| 实体-主表 | `NewBom.java` | 有 `parentId`、`hasChildren`、`children`(非持久化)字段 |
|
|||
|
|
| 实体-明细 | `NewBomItem.java` | 无 `subBomId` 引用字段,明细不能挂子BOM |
|
|||
|
|
| Mapper | `NewBomMapper.xml` | `selectNewBomTree` 仅取一层子节点,无递归 |
|
|||
|
|
| Service | `NewBomServiceImpl.java` | `calculateBomCost` 仅计算直接子项成本,未递归 |
|
|||
|
|
| 前端 | `newBom/index.vue` | 已有树形表格外壳,但展开逻辑不完整 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 二、需求定义:什么是本系统的多级BOM
|
|||
|
|
|
|||
|
|
本系统面向离散制造业,多级BOM的核心诉求是:**支持子装配(Sub-Assembly)层级**。
|
|||
|
|
|
|||
|
|
产品BOM的明细物料中,一部分为采购/消耗的原材料,另一部分为**半成品组件**(子装配件)。这些组件本身也有BOM。
|
|||
|
|
|
|||
|
|
**目标结构(示例):**
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
产品: A (整机BOM V1.0)
|
|||
|
|
├── 物料: 螺丝 M3 x 100 pcs (直接材料)
|
|||
|
|
├── 物料: 外壳组件 x 1 pcs (子装配BOM)
|
|||
|
|
│ ├── 物料: 外壳塑料件 x 1 (孙子层)
|
|||
|
|
│ └── 物料: 卡扣 x 4 (孙子层)
|
|||
|
|
└── 物料: PCB主板 x 1 (子装配BOM)
|
|||
|
|
├── 物料: 芯片 xxx x 2
|
|||
|
|
└── 物料: PCB板 x 1
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 三、方案设计
|
|||
|
|
|
|||
|
|
### 3.1 总体思路
|
|||
|
|
|
|||
|
|
**不动数据库表结构**(`md_new_bom` 主表不变),仅做以下扩展:
|
|||
|
|
|
|||
|
|
1. 在 `md_new_bom_item` 新增 `sub_bom_id` 字段,标识该明细行是否引用了另一个BOM
|
|||
|
|
2. 在 Service 层新增**递归成本计算**方法
|
|||
|
|
3. 前端增强树形展开展示和子BOM展开功能
|
|||
|
|
4. 提供**多级BOM展开视图**,可一键将多层BOM展开为单层物料清单(物料去重合并用量)
|
|||
|
|
|
|||
|
|
### 3.2 数据库变更
|
|||
|
|
|
|||
|
|
**ALTER TABLE `md_new_bom_item`:**
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
ALTER TABLE `md_new_bom_item`
|
|||
|
|
ADD COLUMN `sub_bom_id` bigint(20) DEFAULT NULL COMMENT '引用的子BOM ID(多级BOM用)' AFTER `process_route`,
|
|||
|
|
ADD COLUMN `is_sub_bom` tinyint(1) DEFAULT '0' COMMENT '是否子BOM项 0-否 1-是' AFTER `sub_bom_id`,
|
|||
|
|
ADD COLUMN `level` int(11) DEFAULT '0' COMMENT 'BOM层级深度' AFTER `is_sub_bom`,
|
|||
|
|
ADD KEY `idx_sub_bom_id` (`sub_bom_id`);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
> **说明:** 保留原 `materialId` 字段,但当 `is_sub_bom=1` 时,`materialId` 指向子装配件的物料ID,`sub_bom_id` 指向该物料对应的BOM ID。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 四、后端改造
|
|||
|
|
|
|||
|
|
### 4.1 实体类变更
|
|||
|
|
|
|||
|
|
**`NewBomItem.java`** 新增字段:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
/** 引用的子BOM ID(多级BOM用) */
|
|||
|
|
private Long subBomId;
|
|||
|
|
|
|||
|
|
/** 是否子BOM项 0-否 1-是 */
|
|||
|
|
private Boolean isSubBom;
|
|||
|
|
|
|||
|
|
/** BOM层级深度 */
|
|||
|
|
private Integer level;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**`NewBom.java`** 新增非持久化字段(用于前端展开):
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
/** 多级展开后的子层BOM列表(仅在多级查询时填充) */
|
|||
|
|
@TableField(exist = false)
|
|||
|
|
private List<NewBom> expandedChildren;
|
|||
|
|
|
|||
|
|
/** 多级成本(递归计算结果) */
|
|||
|
|
@TableField(exist = false)
|
|||
|
|
private BigDecimal expandedMaterialCost;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4.2 Service 层新增方法
|
|||
|
|
|
|||
|
|
在 `INewBomService.java` 和 `NewBomServiceImpl.java` 中新增:
|
|||
|
|
|
|||
|
|
#### 4.2.1 递归成本计算
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
/**
|
|||
|
|
* 递归计算多级BOM成本(支持无限层级)
|
|||
|
|
* @param bomId BOM主键
|
|||
|
|
* @param topQuantity 顶层需求量(用于按比例缩放子级用量)
|
|||
|
|
* @return 包含多级明细的成本分析
|
|||
|
|
*/
|
|||
|
|
Map<String, Object> calculateMultiLevelBomCost(Long bomId, BigDecimal topQuantity);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**核心算法:**
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
calculateMultiLevelBomCost(bomId, quantity):
|
|||
|
|
bom = selectNewBomById(bomId)
|
|||
|
|
totalMaterialCost = 0
|
|||
|
|
totalLaborCost = bom.laborCost
|
|||
|
|
totalManufacturingCost = bom.manufacturingCost
|
|||
|
|
|
|||
|
|
for each item in bom.bomItems:
|
|||
|
|
scaleFactor = quantity / bom.baseQuantity
|
|||
|
|
if item.isSubBom == true:
|
|||
|
|
childCost = calculateMultiLevelBomCost(item.subBomId, item.quantity * scaleFactor)
|
|||
|
|
totalMaterialCost += childCost.materialCost
|
|||
|
|
else:
|
|||
|
|
itemCost = item.quantity * scaleFactor * item.unitPrice * (1 + item.lossRate/100)
|
|||
|
|
totalMaterialCost += itemCost
|
|||
|
|
|
|||
|
|
return { materialCost, laborCost, manufacturingCost, totalCost, itemDetails }
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 4.2.2 完整BOM树查询
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
/**
|
|||
|
|
* 查询BOM多级展开树(递归向下查询指定层级)
|
|||
|
|
* @param bomId 起始BOM ID
|
|||
|
|
* @param maxLevel 最大展开层级(默认3层,防止性能问题)
|
|||
|
|
* @return 带完整子级结构的BOM树
|
|||
|
|
*/
|
|||
|
|
NewBom selectBomFullTree(Long bomId, Integer maxLevel);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 4.2.3 BOM展开清单(物料去重合并)
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
/**
|
|||
|
|
* 将多级BOM展开为单层物料清单(含去重合并用量)
|
|||
|
|
* @param bomId 起始BOM ID
|
|||
|
|
* @param quantity 需求量
|
|||
|
|
* @return 扁平化物料列表(含各层汇总用量)
|
|||
|
|
*/
|
|||
|
|
List<Map<String, Object>> expandBomToFlatList(Long bomId, BigDecimal quantity);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**展开合并算法:**
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
expandBomToFlatList(bomId, quantity):
|
|||
|
|
flatList = []
|
|||
|
|
recursiveCollect(bomId, quantity, visitedBomIds):
|
|||
|
|
bom = selectBomById(bomId)
|
|||
|
|
if bom.id in visitedBomIds: return // 防止循环引用
|
|||
|
|
add bom.id to visitedBomIds
|
|||
|
|
scale = quantity / bom.baseQuantity
|
|||
|
|
for each item in bom.items:
|
|||
|
|
if item.isSubBom:
|
|||
|
|
recursiveCollect(item.subBomId, item.quantity * scale, visitedBomIds)
|
|||
|
|
else:
|
|||
|
|
merge into flatList: 物料ID -> 累加用量
|
|||
|
|
return flatList
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4.3 Controller 层新增接口
|
|||
|
|
|
|||
|
|
在 `NewBomController.java` 新增端点:
|
|||
|
|
|
|||
|
|
| 方法 | 路径 | 说明 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| `GET` | `/masterdata/newBom/multiLevel/{id}` | 获取BOM多级展开树 |
|
|||
|
|
| `GET` | `/masterdata/newBom/cost/multiLevel/{id}` | 多级递归成本计算 |
|
|||
|
|
| `GET` | `/masterdata/newBom/expand/{id}` | 展开为扁平物料清单 |
|
|||
|
|
|
|||
|
|
### 4.4 多级展开时需防止的问题
|
|||
|
|
|
|||
|
|
1. **循环引用检测:** A->B->A 的情况,在递归入口处用 `Set<Long> visitedBomIds` 记录已访问的BOM ID
|
|||
|
|
2. **最大层级限制:** 默认限制 10 层,超出则提示用户"BOM层级过深"
|
|||
|
|
3. **性能:** 超过 3 层时前端应显示加载状态,后端加缓存(可选)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 五、前端改造
|
|||
|
|
|
|||
|
|
### 5.1 BOM管理页增强
|
|||
|
|
|
|||
|
|
文件:`mes-ui/src/views/mes/masterdata/newBom/index.vue`
|
|||
|
|
|
|||
|
|
**5.1.1 新增"子BOM物料"输入模式**
|
|||
|
|
|
|||
|
|
在新增/编辑BOM明细行时,增加一个切换:
|
|||
|
|
|
|||
|
|
- **普通物料行**(默认):选择物料,填写用量、损耗率
|
|||
|
|
- **子BOM行**:选择"子装配物料" + 选择该物料对应的BOM版本
|
|||
|
|
|
|||
|
|
前端在选择物料时,如果该物料在 `md_new_bom` 中有已发布的BOM,则自动提示可挂子BOM。
|
|||
|
|
|
|||
|
|
**5.1.2 明细行组件增强**
|
|||
|
|
|
|||
|
|
在现有的 BOM 明细表格中,每行增加:
|
|||
|
|
- 列:`类型` — 显示「普通」或「子BOM」,不同颜色标签区分
|
|||
|
|
- 列:`引用BOM` — 当类型为子BOM时显示引用的BOM编号
|
|||
|
|
- 操作:`展开`按钮 — 仅子BOM行可用,点击后在该行下方嵌入子BOM明细表格
|
|||
|
|
|
|||
|
|
### 5.2 新增多级BOM视图页
|
|||
|
|
|
|||
|
|
新增 `mes-ui/src/views/mes/masterdata/newBom/multiLevel.vue`:
|
|||
|
|
|
|||
|
|
- 输入:BOM选择 + 需求量
|
|||
|
|
- 展示:树形结构,多级展开/折叠
|
|||
|
|
- 每行显示:层级缩进、物料编码、物料名称、用量、成本、是否关键件
|
|||
|
|
- 底部显示:多级总成本汇总
|
|||
|
|
- 按钮:「展开为清单」— 切换到扁平视图(物料去重合并)
|
|||
|
|
|
|||
|
|
### 5.3 API 层新增
|
|||
|
|
|
|||
|
|
文件:`mes-ui/src/api/mes/masterdata/newBom.js`
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// 多级BOM查询
|
|||
|
|
export const getBomMultiLevel = (id, maxLevel) => {
|
|||
|
|
return request({ url: `/masterdata/newBom/multiLevel/${id}`, params: { maxLevel } })
|
|||
|
|
}
|
|||
|
|
// 多级成本计算
|
|||
|
|
export const getBomMultiLevelCost = (id, quantity) => {
|
|||
|
|
return request({ url: `/masterdata/newBom/cost/multiLevel/${id}`, params: { quantity } })
|
|||
|
|
}
|
|||
|
|
// BOM展开清单
|
|||
|
|
export const expandBom = (id, quantity) => {
|
|||
|
|
return request({ url: `/masterdata/newBom/expand/${id}`, params: { quantity } })
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 六、实现计划(分三个阶段)
|
|||
|
|
|
|||
|
|
### 第一阶段:基础设施(1-2天)
|
|||
|
|
|
|||
|
|
- [ ] 执行 `ALTER TABLE` 新增字段
|
|||
|
|
- [ ] 修改 `NewBomItem.java` 实体,添加 `subBomId`、`isSubBom`、`level` 字段
|
|||
|
|
- [ ] 修改 `NewBom.java` 添加非持久化展开字段
|
|||
|
|
- [ ] 修改 `NewBomMapper.xml` 新增字段映射
|
|||
|
|
- [ ] 单元测试验证字段变更不影响现有CRUD
|
|||
|
|
|
|||
|
|
### 第二阶段:核心逻辑(2-3天)
|
|||
|
|
|
|||
|
|
- [ ] 在 `INewBomService.java` 新增 3 个接口方法声明
|
|||
|
|
- [ ] 在 `NewBomServiceImpl.java` 实现 `calculateMultiLevelBomCost`(递归)
|
|||
|
|
- [ ] 在 `NewBomServiceImpl.java` 实现 `selectBomFullTree`(递归树查询)
|
|||
|
|
- [ ] 在 `NewBomServiceImpl.java` 实现 `expandBomToFlatList`(去重合并)
|
|||
|
|
- [ ] 在 `NewBomController.java` 新增 3 个API端点
|
|||
|
|
- [ ] 循环引用和最大层级检测逻辑
|
|||
|
|
|
|||
|
|
### 第三阶段:前端改造(2-3天)
|
|||
|
|
|
|||
|
|
- [ ] 前端 API 层新增 3 个接口调用
|
|||
|
|
- [ ] 修改 BOM 明细行组件,支持子BOM行类型
|
|||
|
|
- [ ] 新增 `multiLevel.vue` 多级BOM视图页
|
|||
|
|
- [ ] 树形展开/折叠交互
|
|||
|
|
- [ ] 扁平化展开清单视图(物料去重合并展示)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 七、风险与注意事项
|
|||
|
|
|
|||
|
|
1. **循环引用:** A BOM -> B BOM -> A BOM 必须被检测并阻止,递归时用 `visitedIds` 集合
|
|||
|
|
2. **性能:** 深层BOM(>5层)成本计算较慢,考虑异步计算或后台任务
|
|||
|
|
3. **数据迁移:** 已有的 BOM 明细数据 `is_sub_bom=0`,`level=0`,无需迁移
|
|||
|
|
4. **版本一致性:** 子BOM引用时,限定只能引用"已发布"状态的BOM
|
|||
|
|
5. **兼容旧数据:** 新字段 `sub_bom_id` 允许 NULL,向下兼容
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 八、预期效果
|
|||
|
|
|
|||
|
|
| 角色 | 收益 |
|
|||
|
|
|---|---|
|
|||
|
|
| 工艺人员 | 一个页面管理整机BOM,可挂子装配BOM,无需分开维护多张独立BOM |
|
|||
|
|
| 成本会计 | 一键计算整机(含所有子级)的真实材料成本 |
|
|||
|
|
| 生产计划 | 展开清单可直接输出为生产领料参考清单 |
|
|||
|
|
| 系统 | 复用现有表结构,改动小,风险可控 |
|