Files
MES/yawei-mes/.tasks/2026-03-27_v2.0.014_BOM单多级优化.md
2026-04-02 10:39:03 +08:00

304 lines
10 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.

# 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 |
| 成本会计 | 一键计算整机(含所有子级)的真实材料成本 |
| 生产计划 | 展开清单可直接输出为生产领料参考清单 |
| 系统 | 复用现有表结构,改动小,风险可控 |