BOM这东西第一次做的人往往把它当成一张表格做完才发现它其实是一条贯穿设计、工艺、采购、生产的数据链。系列做到第七篇也就是BOM设计与管理的下半场我想把上一版里没讲透的那部分补上——上一版我们解决了“能从NX装配里把料抓出来、能拼成一棵树、能导出成Excel”那是“出表”的能力这一篇要解决的是“管表”的能力也就是同一份BOM在版本迭代、设计变更、多视图转换、权限发布这些真实场景下怎么不失控。读到这里的多半是做过NX二次开发、或者正在给自家研发部门搭设计管理平台的同行也有刚接手这块业务、被BOM对不上号折磨过的设计工程师。不管你是哪种基础这篇都尽量把设计取舍和踩过的坑摊开讲清楚代码和表结构给到可以直接照着搭的程度。1. 先划清BOM管理层的职责边界1.1 上一篇留下的三个尾巴上一篇收尾的时候留了几个问题没解决一是同一份BOM在不同时间点导出结果不一致谁也说不清哪版是对的二是设计改了某个零件但下游工艺还没收到通知等人发现的时候工装都开下去了三是同一个产品设计部门给一份BOM、工艺部门自己又维护一份两边数量对不上开会扯半天。这三个问题表面看是流程问题根子上是数据没有“身份”和“时间”这两个维度。只把BOM当成一次性的查询结果它就永远只能当报表看。真正要让它变成可管理的对象必须给它加上三个东西唯一标识这份BOM属于哪个产品、哪个版本、哪个视图、状态机草稿、审核中、已发布、已冻结、已作废、时间戳什么时候生效、什么时候失效。这三样东西不加上后面做的所有比对、变更、发布都是在沙地上盖楼。我在第一个项目里就是先做了导出功能结果用了三个月业务方开始问“上个月那份BOM还能调出来吗”才发现压根没存快照只能重跑一遍装配而装配已经改过了重跑出来的数据对不上历史那场面相当难解释。所以这一篇的第一件事就是把BOM从“视图”升级成“实体”让它有表头、有明细、有状态、有版本能被引用、能被追溯。1.2 管理层的四层职责拆解我把BOM管理拆成四层每层职责不重叠这样后面加功能不会互相打架层级职责典型实现变更频率数据层存储BOM头、行、快照、属性关系库表 索引低规则层取数规则、属性映射、数量汇总、有效性判断服务端业务代码中视图层EBOM、PBOM、制造BOM、采购BOM的呈现前端树表 视图配置中发布层审批流、导出、对外接口、审计工作流 消息低这个分法的好处是视图层永远不碰原始数据只做投影发布层永远不碰业务规则只做流转。有一次业务方临时要求“采购BOM里把标准件单独拎出来、按供应商分组”如果这需求落在数据层就得改表结构、跑数据迁移落在规则层也就是加一个分组策略和几个过滤条件半天能改完。分层不是为了好看是为了让需求变更的时候爆炸半径可控。1.3 和PDM/PLM的边界怎么划这里必须提前说清楚不然做到一半会陷入“到底要不要自己维护零部件主数据”的纠结。我的经验是设计管理平台负责“结构”和“关系”PDM或PLM负责“主数据”和“流程终点”。具体说零件号、名称、材质、重量这些主属性权威来源应该是主数据系统平台只做引用和缓存装配结构和层级关系是NX里的真实装配平台负责把它结构化落地BOM的多视图转换和比对是平台的核心价值别指望主数据系统帮你做。有些团队图省事把主数据也塞进平台自己维护前期跑得快后面一旦和主数据系统对不上就是两套真相清洗数据的时间比开发时间还长。我后来的做法是平台里存一份主数据的“影子表”带同步时间和来源标记展示时用影子表保证性能一旦发现不一致以主数据为准并给出告警而不是自己偷偷改。这个设计在后期做数据治理时省了太多事。2. BOM数据模型一份数据要同时伺候设计和制造2.1 表头与明细的字段设计表结构是整个BOM管理的地基字段设计得好后面查询和比对都轻松设计得差一个视图要写三张临时表才能拼出来。下面是我目前在用的核心表结构去掉了跟具体业务强绑定的字段保留通用骨架-- BOM表头一份BOM的身份 CREATE TABLE t_bom_header ( id BIGINT PRIMARY KEY AUTO_INCREMENT, bom_code VARCHAR(64) NOT NULL COMMENT BOM唯一编码, product_code VARCHAR(64) NOT NULL COMMENT 所属产品/顶层件号, product_name VARCHAR(128), version VARCHAR(32) NOT NULL COMMENT 版本号 A/B/C 或 01/02, bom_type VARCHAR(16) NOT NULL COMMENT EBOM/PBOM/MBOM/采购BOM, status TINYINT NOT NULL DEFAULT 0 COMMENT 0草稿 1审核中 2已发布 3已冻结 4作废, effective_from DATETIME NULL COMMENT 生效时间, effective_to DATETIME NULL COMMENT 失效时间, source_part_rev VARCHAR(32) NULL COMMENT 来源NX部件版本, snapshot_id BIGINT NULL COMMENT 关联的结构快照, created_by VARCHAR(64), created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_code_ver (bom_code, version, bom_type), KEY idx_product (product_code, status) ) COMMENTBOM表头; -- BOM明细行树形结构用父子指针 路径双保险 CREATE TABLE t_bom_line ( id BIGINT PRIMARY KEY AUTO_INCREMENT, header_id BIGINT NOT NULL, parent_line_id BIGINT NULL COMMENT 父行顶层为NULL, path VARCHAR(512) NOT NULL COMMENT /1/3/7/ 形式便于子树查询, level_no INT NOT NULL, seq_no INT NOT NULL COMMENT 同级顺序号1,2,3..., item_no VARCHAR(64) NOT NULL COMMENT 件号, item_name VARCHAR(128), item_rev VARCHAR(32) COMMENT 零件版本, qty DECIMAL(18,6) NOT NULL DEFAULT 1, unit VARCHAR(16) DEFAULT EA, material VARCHAR(64), weight DECIMAL(18,6), item_type VARCHAR(16) COMMENT 自制/外购/标准件/虚拟件, substitute_flag TINYINT DEFAULT 0 COMMENT 是否替代件, source_attr VARCHAR(32) COMMENT 来源NX属性/手工录入, remark VARCHAR(512), KEY idx_header (header_id), KEY idx_parent (header_id, parent_line_id), KEY idx_path (header_id, path(255)), KEY idx_item (item_no) ) COMMENTBOM明细行;说几个设计上的取舍。第一path字段用/id/id/id/的形式存全路径是为了做子树查询——比如“查这个组件下所有受影响的零件”用path LIKE /12/%一条语句就出来了比递归CTE在MySQL里快得多也避免了很多老版本数据库不支持递归的麻烦。代价是每次移动节点要重算子树路径但BOM结构一旦发布基本不动这个代价可以接受。第二parent_line_id和path同时存在是冗余但这是有意的冗余。父指针负责前端树上展开收起路径负责后端范围查询两个各干各的活。只用父指针查子树要写循环只用路径前端拿不到直接的父子关系。第三qty用DECIMAL(18,6)而不是整数因为现实中存在按重量、按长度计量的物料比如线束按米、胶水按克。这一条是吃过亏的——早期用INT结果电子线束的BOM数量全被四舍五入采购多买了一倍。2.2 多视图BOM的映射机制EBOM到PBOM到MBOM不是简单的复制中间要经过工艺路线的重构设计上是一个焊接总成工艺上要拆成焊接件、涂装件、装配件。很多平台在这里选择“各视图各维护一份”结果数据永远对不上另一种是“每次转换都全量重算”结果工艺部门手工加的辅料焊丝、胶水、保护膜每次转换都被抹掉。我用的方案是“基线 增量”的混合模型。EBOM发布时生成一份只读基线PBOM以基线为起点记录两件事一是从EBOM继承来的行带origin_line_id指向来源行二是工艺侧新增或修改的行带is_manual1标记。当EBOM升版时平台做一次“差异增量同步”只同步发生变更的行手工行原样保留冲突行单独列出来让人确认。这个机制的关键在同步策略表变更类型同步行为冲突处理EBOM新增行自动插入PBOM无EBOM删除行若PBOM行未手工改过则删除否则标记待确认人工确认EBOM数量变更若PBOM行数量未被手工覆盖则同步冲突列表提示EBOM属性变更同步非工艺属性保留工艺属性无PBOM手工行完全不动无用这张表做规则工艺部门的同事第一次用的时候还不太信试了两次发现手工加的辅料真的还在后面就愿意用了。工具能不能推下去往往就取决于这类细节。2.3 版本与生效期别只用一个版本号很多团队只用一个自增版本号觉得“数字大的就是新的”这在简单场景够用一旦牵扯到多产品并行、批量生效、临时替代就不够了。我的做法是三层版本序列A、B、C或者01、02业务语义明确人看得懂同序列内单向递增。生效区间effective_from和effective_to支持未来生效、限时生效比如某批次专用件只在一段时间内有效。状态机草稿 → 审核中 → 已发布 → 已冻结 → 作废每个状态迁移都留审计记录。查询当前有效版本的原则是状态为已发布且now()落在生效区间内如果有多条取effective_from最大的那条。这个规则写在一个统一的视图或函数里所有前端和后端接口都走它不允许各处自己写查询条件。这条铁律是踩坑换来的——早期有两个模块各自实现了“取最新版本”的逻辑一个按创建时间、一个按版本号在跨版本并行的时候结果不一样排查了整整两天。3. NX侧取数的工程化实现3.1 装配遍历的三种路线怎么选NX Open里遍历装配结构常见的有三条路一是UF_ASSEM系列函数二是NXOpen.Assemblies的对象模型三是直接用装配导航器Assembly Navigator的UI接口。我的选择是主体用NXOpen.Assemblies的对象模型必要时用UF_ASSEM补充底层信息。原因很实在对象模型有类型、有智能提示、Component上有Quantity、DisplayName、Prototype这些现成属性写起来效率高而UF_ASSEM的优势在于能拿到一些对象模型没暴露的底层数据和更好的性能表现比如大装配下批量读取组件状态。UI接口那条路基本不用考虑除非是做交互式工具。它是靠模拟用户操作实现的在批处理和服务端场景直接跑不起来而且非常脆弱。遍历的核心递归逻辑大概长这样using NXOpen; using NXOpen.Assemblies; using System.Collections.Generic; using System.Text; public class BomNode { public string ItemNo; public string ItemName; public string ItemRev; public double Qty; public int Level; public string Path; public ListBomNode Children new ListBomNode(); } public class AssemblyWalker { private readonly Session _session Session.GetSession(); // 根组件开始递归累积数量 public BomNode Walk(Component root) { var node new BomNode { ItemNo GetAttr(root.Prototype, DB_PART_NO), ItemName GetAttr(root.Prototype, DB_PART_NAME), ItemRev GetAttr(root.Prototype, DB_PART_REV), Qty 1, Level 0, Path / }; FillChildren(root, node, 1, new StringBuilder(/)); return node; } private void FillChildren(Component parent, BomNode parentNode, int level, StringBuilder sb) { if (level 30) return; // 防止循环引用导致无限递归 foreach (Component child in parent.GetChildren()) { // 抑制组件、空组件直接跳过 if (child.IsSuppressed) continue; var proto child.Prototype; if (proto null) continue; int len sb.Length; sb.Append(child.Name).Append(/); var childNode new BomNode { ItemNo GetAttr(proto, DB_PART_NO), ItemName GetAttr(proto, DB_PART_NAME), ItemRev GetAttr(proto, DB_PART_REV), Qty child.Quantity, // 本例中的出现数量 Level level, Path sb.ToString() }; // 同一父件下相同件号合并数量保留首个路径 var exist parentNode.Children.Find(c c.ItemNo childNode.ItemNo); if (exist ! null) { exist.Qty childNode.Qty; } else { parentNode.Children.Add(childNode); FillChildren(child, childNode, level 1, sb); } sb.Length len; // 回溯路径 } } private string GetAttr(Part part, string attrName) { if (part null) return string.Empty; try { // 先取用户属性再取系统属性都没有返回空 if (part.HasUserAttribute(attrName, NXOpen.NXObject.AttributeType.String)) { return part.GetStringAttribute(attrName)?.Trim() ?? string.Empty; } } catch { // 属性读取失败不能中断整棵树返回空串并记录日志 } return string.Empty; } }这段代码里有几个点是必须注意的。child.Quantity拿到的是这个组件在父件下的出现次数不是累计数量。如果要算整机用量得在父节点合并的时候乘上去或者遍历完之后统一做一次“向上累计”。前者实时性更好后者逻辑更清晰我倾向于在写库之前做一次独立的累计计算这样遍历和计算职责分开出问题好定位。IsSuppressed这个判断不能省。NX里被抑制的组件在装配树里还挂着但实际不参与装配如果不排除BOM会出现“幽灵件”。我见过一个项目导出的BOM里某型号的支架数量是2实际只有1查了半天发现是被抑制的备用组件被算进去了。Path字段用组件名的拼接而不是件号是因为组件名在同一装配下唯一而件号可能重复比如同一零件多次装配。这个路径只用于日志和调试入库时还是会重新生成基于行ID的路径。3.2 数量统计里的三个陷阱数量统计看着简单实际是最容易出错的地方我整理过三类典型情况陷阱一实例数与累计数混淆。组件在父件下的数量实例数和整机总用量累计数是两个概念。父件本身在顶层出现3次子件在父件下出现2次子件整机用量是6。直接在遍历里累加最容易错正确做法是递归返回子树用量再乘以本级数量。陷阱二单位不一致。有的零件属性里单位是EA有的是M米有的是KG。数量直接相加没有意义。我在入库前加了一道单位校验同一件号的所有出现单位必须一致不一致就报警并挂起这条BOM不允许发布。这个校验上线之后抓出过一批历史遗留数据问题。陷阱三替代件和可选件。有些装配里挂了替代件主件和替代件只能二选一不能同时算进BOM。这类组件通常有个属性标记比如SUBSTITUTE_GROUP同一组内只保留主件。处理逻辑是在遍历完之后做一次分组过滤而不是在遍历中途跳过因为中途跳过会漏掉替代件的信息后面做替代料管理就没数据了。3.3 大装配的性能优化一台上千个零件的装配用上面的递归跑一次要几分钟如果每次都全量加载用户根本受不了。我做过的优化按收益排序第一设置加载选项为“仅结构”。通过PartLoadOptions控制只加载装配结构不加载几何这一步通常能省掉70%以上的时间。代码如下PartLoadOptions loadOptions _session.Parts.LoadOptions; loadOptions.LoadOption PartLoadOptions.LoadOptionType.StructureOnly; _session.Parts.LoadOptions loadOptions; // 记得用完恢复第二属性读取做缓存。同一个零件在装配里可能出现很多次属性读一次缓存起来就行。用一个Dictionarystring, PartAttr以零件全路径为键命中率很高。第三分批提交数据库。别遍历完一次性插入几千行那样事务太长出一次错全回滚。改成每500行提交一次配合一个批次号失败可以续跑。第四把BOM生成放到异步任务。用户点“生成BOM”之后立即返回任务ID后台跑完再通知。前端给个进度条体验上比转圈等着好太多。这一条严格说不算算法优化但用户感知上收益最大。4. BOM比对与变更管理4.1 比对算法的关键在“键”的选择BOM比对这个功能十个项目九个要做做得好不好差别全在“用什么当键”上。用件号当键零件换版本会被当成删除加新增用件号加版本当键同一零件的不同位置会被拆开用路径当键结构一动全变。我的方案是双键比对主键用件号位置路径判断结构变化辅键用件号判断版本变化。具体分三类结果比对结果判定条件业务含义新增新BOM有、旧BOM无按主键增加了零件或位置删除旧BOM有、新BOM无按主键减少了零件或位置修改主键相同数量/版本/属性不同数量调整、版本升级、属性变更移动件号相同但位置路径变了结构重组“移动”这一类是最容易被忽略的。只按主键比一次结构重组会被报成几十条新增加几十条删除业务方看了直接崩溃。识别出移动之后把结果折叠成一条“结构重组”可读性完全不一样。比对实现上我建议在内存里做而不是在SQL里做。把两版BOM各查成字典键是主键值是行对象然后遍历比对。数据量在一万行以内几百毫秒能出结果比写一堆JOIN和EXCEPT清楚得多也好调试。public static ListDiffResult Compare( Dictionarystring, BomLine oldMap, Dictionarystring, BomLine newMap) { var results new ListDiffResult(); foreach (var kv in newMap) { if (!oldMap.TryGetValue(kv.Key, out var oldLine)) { results.Add(new DiffResult { Type 新增, Line kv.Value }); continue; } if (Math.Abs(oldLine.Qty - kv.Value.Qty) 0.000001m) results.Add(new DiffResult { Type 数量变更, Old oldLine, Line kv.Value }); if (oldLine.ItemRev ! kv.Value.ItemRev) results.Add(new DiffResult { Type 版本变更, Old oldLine, Line kv.Value }); } foreach (var kv in oldMap) { if (!newMap.ContainsKey(kv.Key)) results.Add(new DiffResult { Type 删除, Old kv.Value }); } return results; }数量比较用了一个0.000001m的容差这是为了规避浮点误差。别小看这个早期直接用!比结果出现了一堆“1.000000 变成 1.0000001”的假变更业务方以为系统疯了。4.2 快照让任何时点的BOM都能被调出来上一节讲的比对前提是能拿到“旧BOM”。如果旧BOM是实时从NX装配里跑的那这个前提就不成立因为装配已经改过了。所以必须有快照机制。快照的设计要点结构快照与业务快照分离。结构快照是NX装配遍历出来的原始结果只读带装配的部件版本信息业务快照是发布时的正式BOM可能包含手工调整。两者用snapshot_id关联。这样做的价值在于当有人质疑“这个BOM当时是怎么来的”时可以把结构快照翻出来对照而不是只能看最终结果。快照的存储我建议用“头表 明细表 压缩原文”三件套。头表存元信息明细表存结构化数据用于查询比对压缩原文JSON存完整快照用于归档还原。第三份看着冗余但它是唯一能在数据库结构变更后还原历史的手段。这个设计在一次字段类型调整中救过场——明细表迁移出了问题靠JSON原文重新灌回去的。4.3 变更影响面怎么算BOM变更管理里最有价值的功能不是“记录改了什么”而是“告诉你会影响什么”。影响面计算的核心是向上追溯 横向关联向上追溯这个零件被哪些父件使用父件又被哪些总成使用一直追到顶层产品。用前面存的path字段一条LIKE就能拿到所有上级。横向关联这个零件关联了哪些工艺文件、哪些工装、哪些采购订单。这部分要依赖平台和其他系统的接口。实现上向上追溯我做成一个物化的parent_map表每次BOM发布时重算查询时直接查表不走递归。这样十万行级别的数据毫秒级出结果。物化表会带来一致性问题靠“发布时刷新 每日全量校验”来兜底。有一点必须提醒影响面计算不要做成“阻塞发布”的强制流程。有些团队设计成“零件变更必须走影响评估流程才能发布”结果是流程走到一半卡在某个部门那里后面所有的工作全停了。我的做法是影响面作为“提示信息”随变更单一起推送谁关心谁看流程本身不依赖它。工具要帮人判断不要替人决策。5. 前端呈现与导出让人愿意用才算成功5.1 树形表格的三个坑BOM前端基本就是一个树形表格看着简单坑不少。第一个坑是层级深了卡。超过五六百行之后普通渲染会明显卡顿。解决方案是虚拟滚动加懒加载只渲染可视区域的行子节点展开时再请求。我用的是el-table tree-props配合分页虽然不如专业虚拟滚动流畅但胜在实现简单、出问题少。第二个坑是列多。一个BOM动辄二三十个属性列全显示出来横向滚动到手软。我的做法是列配置化每个视图有自己默认显示的列用户可以自己勾选。默认列控制在八个以内层级序号、件号、名称、版本、数量、单位、类型、状态。其余的都收起来。第三个坑是编辑态和只读态混乱。同一个页面已发布的BOM是只读的草稿状态可以编辑。这个状态判断要在行级别做不能只在页面上做。我见过一个页面在顶部放了个“编辑”按钮结果用户点开之后还是能改因为单元格的编辑属性没跟着状态走回过头数据就乱了。5.2 导出这件事别只做一个按钮导出BOM看起来是最简单的功能实际上是投诉最多的功能。原因很简单不同部门要的格式完全不一样。设计部门要按层级缩进的完整结构工艺部门要按件号汇总的扁平表采购部门要按供应商分组的清单。我的方案是模板化导出导出配置存成一套一套的模板包含列映射、分组规则、层级处理方式、表头样式。用户选模板导出觉得不够就自己改模板再存一份。用EPPlus或NPOI这类库实现代码量不大灵活度很高。模板配置表大致是这样模板名分组方式层级呈现必含列附加规则设计完整BOM层级缩进一级件号、名称、数量显示所有层级工艺汇总BOM件号扁平件号、名称、总用量相同件号合并采购清单供应商扁平件号、名称、数量、供应商只含外购件备件清单无扁平件号、名称、数量、更换周期只含标记为备件的行导出还有两个细节一是大数据量分批写别一次把十万行塞进内存二是文件名带时间戳和版本号我吃过亏两个同事同时导出一份BOM文件同名被覆盖各自拿着别人的文件去开会吵了一下午。5.3 权限和审计别等出事才补BOM的权限至少分三级查看、编辑、发布。编辑权限还要按视图分EBOM的编辑权归设计PBOM的编辑权归工艺互不干扰。发布权限单独给不能和编辑权绑在一起否则就是自己写完自己发审核形同虚设。审计日志记录四类操作谁看了、谁改了、改了什么、什么时候。数据结构上我建议记录“变更前后值”而不是只记“改过了”。有一次质量追溯要求提供某个零件数量变更的历史因为我们记了前后值直接导出来交差隔壁系统只记了操作时间只能人工比对每次快照花了两天。6. 踩坑实录与常见问题速查6.1 典型问题速查表做了几轮之后我把高频问题整理成一张表新同事上手先看这张表能省掉一半的试错时间现象根因处理方式导出的BOM数量翻倍抑制组件未排除或递归中重复累加遍历时判断IsSuppressed累计逻辑单独实现同一零件在两版BOM里被报成删除加新增键用了路径结构微调导致路径变化改用双键比对识别“移动”类变更大装配生成BOM超时全量加载几何设LoadOption为StructureOnly属性缓存BOM发布后下游数量对不上手工行在同步时被覆盖引入origin_line_id和is_manual标记属性读取全是空属性读到了原型件而非实例或属性名大小写不一致统一走GetAttr封装属性名做trim和大小写归一前端树展开几百行就卡死一次性渲染全量节点懒加载 只渲染可视区历史BOM调不出来只存当前数据没做快照快照三件套头、明细、压缩原文版本号相同但内容不同并发编辑没有版本锁发布时校验版本唯一编辑加乐观锁6.2 几个只能靠经验记住的细节第一个细节NX里部件的显示名和属性里的件号经常不是一回事。有些团队直接在部件名里写件号有些用属性混合使用的项目特别多。我的处理原则是件号一律以属性为准属性为空时才回退到部件名并且在BOM里标记这条数据的来源方便后续做数据治理。这个回退逻辑看着无害实际很有用——它能让你知道有多少零件还没规范填属性。第二个细节装配里的组件名在同一层级唯一但跨层级可以重名。用组件名生成路径的时候一定要把层级信息带上否则两个不同位置的同名组件路径会撞车。我早期就是这么撞的结果两条不同的BOM行被合并成一条数量凭空多了一倍查了一个下午。第三个细节NX Open的属性读取在和UI交互时可能抛异常比如部件没完全加载。这些异常不能让它冒泡到顶把整个任务干掉必须包起来单独处理记录到日志然后继续下一个组件。BOM生成任务最怕的就是跑了两分钟因为一个零件属性读取失败全部重来。第四个细节浮点数量在数据库里存DECIMAL在C#里用decimal别用double。中间有一步用double做计算加上去入库时的类型转换就出现过后端显示和数据库存储不一致的情况明明显示1库里是0.999999。6.3 关于工作流的一个心得BOM的审批流不要设计得太复杂。我见过一个项目设计了五级审批从设计员到部门经理到工艺到质量到归档实际跑起来每个节点都要等一个BOM从生成到发布平均要七天。后来精简成两级设计负责人审核 工艺确认时间压缩到一天以内。流程的价值在于卡住真正需要的关口不在于节点多。判断标准很简单如果某个审批节点上的人从来不改任何东西、只是点同意这个节点就该砍掉。BOM这种结构化数据质量主要靠规则校验保证——件号是否为空、数量是否为正、单位是否一致、关键属性是否齐全这些机器能做的别交给人。6.4 我个人的一点体会做到第七篇回头看这套东西最大的变化不是功能越做越多而是越来越清楚哪些不做。BOM管理最容易掉进去的坑是“什么都能配”——视图能配、字段能配、规则能配、流程能配配到最后没人搞得清当前跑的是哪套配置出问题排查成本极高。我现在的做法是核心配置留死外围适度开放表结构、比对算法、同步策略这些写死在代码里改要发版列显示、导出模板、筛选条件这些开放给用户配。这样既保住了稳定性也让业务方有参与感。还有一个体会是BOM这条线上数据的准确率比功能的数量重要得多。宁可少做三个功能也要把件号、数量、版本这三样做准。业务方对新功能的兴趣最多维持两周但对数据错误的容忍度是零。这一点我在几个项目里反复验证过做完一堆花哨功能之后人家记住的永远是你某次给错了数量。