10 KiB
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 主表不变),仅做以下扩展:
- 在
md_new_bom_item新增sub_bom_id字段,标识该明细行是否引用了另一个BOM - 在 Service 层新增递归成本计算方法
- 前端增强树形展开展示和子BOM展开功能
- 提供多级BOM展开视图,可一键将多层BOM展开为单层物料清单(物料去重合并用量)
3.2 数据库变更
ALTER TABLE md_new_bom_item:
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 新增字段:
/** 引用的子BOM ID(多级BOM用) */
private Long subBomId;
/** 是否子BOM项 0-否 1-是 */
private Boolean isSubBom;
/** BOM层级深度 */
private Integer level;
NewBom.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 递归成本计算
/**
* 递归计算多级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树查询
/**
* 查询BOM多级展开树(递归向下查询指定层级)
* @param bomId 起始BOM ID
* @param maxLevel 最大展开层级(默认3层,防止性能问题)
* @return 带完整子级结构的BOM树
*/
NewBom selectBomFullTree(Long bomId, Integer maxLevel);
4.2.3 BOM展开清单(物料去重合并)
/**
* 将多级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 多级展开时需防止的问题
- 循环引用检测: A->B->A 的情况,在递归入口处用
Set<Long> visitedBomIds记录已访问的BOM ID - 最大层级限制: 默认限制 10 层,超出则提示用户"BOM层级过深"
- 性能: 超过 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
// 多级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视图页 - 树形展开/折叠交互
- 扁平化展开清单视图(物料去重合并展示)
七、风险与注意事项
- 循环引用: A BOM -> B BOM -> A BOM 必须被检测并阻止,递归时用
visitedIds集合 - 性能: 深层BOM(>5层)成本计算较慢,考虑异步计算或后台任务
- 数据迁移: 已有的 BOM 明细数据
is_sub_bom=0,level=0,无需迁移 - 版本一致性: 子BOM引用时,限定只能引用"已发布"状态的BOM
- 兼容旧数据: 新字段
sub_bom_id允许 NULL,向下兼容
八、预期效果
| 角色 | 收益 |
|---|---|
| 工艺人员 | 一个页面管理整机BOM,可挂子装配BOM,无需分开维护多张独立BOM |
| 成本会计 | 一键计算整机(含所有子级)的真实材料成本 |
| 生产计划 | 展开清单可直接输出为生产领料参考清单 |
| 系统 | 复用现有表结构,改动小,风险可控 |