资讯动态

开源BOM管理软件实战:从Excel物料清单到集中式数据库的迁移指南

发布时间:2026/10/9 15:20:21 来源:尧图企业网站定制
简介这是一份开源的物料清单管理系统BMMSC#项目源码面向需要集中管理电子零件与BOM数据的开发团队尤其适合有C#基础、希望集成Ciiva电子组件搜索API的开发者。资源包含完整的Visual Studio解决方案主要包含Ciiva.Api.Dto与ApiDemo两个项目压缩包内共50个文件其中37个cs源代码文件实现了组件搜索、库存查询、价格获取、替代制造商件查找、订阅状态校验等接口封装5个dll为依赖库2个csproj项目文件组织工程结构另有config配置、ico图标和resx资源文件等整体约315KB结构清晰便于直接编译和二次开发。该系统支持多用户实时协作与版本控制可将组件库和物料清单无缝集成到一个中央数据库。目前已有838人学习下载。借助该源码读者可快速掌握BMMS与Ciiva API的对接方式理解DTO设计与REST调用逻辑并以此为模板搭建自己的物料清单管理原型减少接口联调和基础功能开发时间。1. BOM Management Software在解决什么当Excel物料清单开始失控的时候当一份物料清单的文件名从BOM_V1.3.xlsx一路变成BOM_V1.3_final.xlsx、BOM_V1.3_真的最终版.xlsx、BOM_V1.4_绝对不改了.xlsx恭喜你已经踩进了用 Excel 管理 BOM 最经典的坑文件是单机版的但协作是多人多部门的。BOM Management Software 做的事情并不魔幻——把散落在各台电脑里的 Excel 物料清单收拢进一个集中式数据库里让 BOM 从“文件”变成“数据”从此有统一编码、有版本号、有权限边界还能在关键时刻把数据原样导回 Excel 救急。开源是这个方向最大的底气数据格式、部署方式、字段设计都由自己说了算既不会因为换软件被数据格式绑死也不用按用户数交授权费。这套方案适合正在被多项目、多版本、跨部门对不上账折磨的工程师和项目经理也适合想用最低成本把研发物料管理拉上正轨的小团队。2. 为什么BOM必须进集中式数据库从文件版本混乱到结构化建模Excel 不是不能管 BOM它是在 BOM 还小、只有一个人维护、变更频率低的时候足够用。一旦产品型号超过三个、研发采购生产都要看或者同一颗物料在不同 BOM 里出现Excel 的“文件”形态就成了瓶颈。集中式数据库的价值不在“能存多少行”而在于它把 BOM 从一份份彼此独立的表格变成了一张可以关联、可以追溯、可以约束的数据网络。2.1 Excel管理BOM的三个失控信号第一个信号是版本命名失控。我见过某设备厂的 BOM 文件里同时存在 7 个修订版本没人说得清哪一版是采购下单依据最后是采购部按自己收到的最新邮件版本下单等产线发现装不上整批物料已经进了仓库。版本文件散在个人电脑里没有统一的服务端时间戳谁改了、改了什么、为什么改全靠邮件正文里那句“这次改了一下阻值”。第二个信号是物料编码口径不一。研发喜欢叫R-100采购的 Excel 里叫RES-100仓库实物标签上叫R100。同一个东西三种写法在各自表格里都能查到一合并就成两行。Excel 本身没有唯一索引也没有强制约束一个小数点或空格就能让同一颗物料“分家”。第三个信号是变更没有闭环。工程师改完 BOM 只更新了自己的那份 Excel没有通知下游生产工单、采购订单、成本核算用的全是旧数据。等出问题再回头找根本不知道这份 Excel 是哪个时间点的快照也没法回答“上一版到底长什么样”。这三个信号只要中一个就该考虑把 Excel 挪进集中式数据库了——不是否定 Excel而是用数据库把 Excel 承载不了的那部分管起来。2.2 集中式BOM的核心数据模型四张表讲透把 Excel BOM 搬进数据库第一件事不是找软件而是先把数据模型理解清楚。一个能支撑多产品、多版本、可追溯的 BOM 库最少需要四张表物料主数据表、BOM 头表、BOM 行表、变更记录表。四张表的分工各司其职和 Excel 里“一张工作表既要列物料属性又要列父子关系”的做法彻底分开。-- 物料主数据表所有物料的唯一权威来源 CREATE TABLE materials ( id SERIAL PRIMARY KEY, material_code VARCHAR(50) NOT NULL UNIQUE, -- 物料编码全库唯一 name VARCHAR(200) NOT NULL, spec VARCHAR(500), -- 规格型号 unit VARCHAR(20) NOT NULL DEFAULT EA, -- 默认单位 category VARCHAR(50), -- 物料大类电阻、电容、结构件... created_at TIMESTAMP DEFAULT NOW() ); -- BOM 头表一个产品 一个版本 一条记录 CREATE TABLE bom_headers ( id SERIAL PRIMARY KEY, product_code VARCHAR(50) NOT NULL, -- 产品/成品编码 version VARCHAR(30) NOT NULL, -- 版本号V1.0、V1.1... status VARCHAR(20) DEFAULT DRAFT, -- DRAFT / RELEASED / SUPERSEDED created_at TIMESTAMP DEFAULT NOW(), UNIQUE (product_code, version) -- 同一产品下版本号不能重复 ); -- BOM 行表每一行是一对父子关系 CREATE TABLE bom_lines ( id SERIAL PRIMARY KEY, bom_id INTEGER REFERENCES bom_headers(id), parent_code VARCHAR(50) NOT NULL, -- 父件编码 child_code VARCHAR(50) NOT NULL, -- 子件编码 qty NUMERIC(12, 4) NOT NULL, -- 用量支持小数 unit VARCHAR(20) NOT NULL, -- 此行用量对应的单位 position_no VARCHAR(30), -- 位号如 R1、C2可空 UNIQUE (bom_id, parent_code, child_code, position_no) ); -- 变更记录表每一次改动的快照回滚的后悔药 CREATE TABLE bom_change_logs ( id SERIAL PRIMARY KEY, bom_id INTEGER REFERENCES bom_headers(id), old_version VARCHAR(30), new_version VARCHAR(30), change_summary TEXT, changed_by VARCHAR(50), created_at TIMESTAMP DEFAULT NOW() );这四张表的设计逻辑是物料主数据单独成表是为了保证“一颗物料只有一个身份”BOM 头和 BOM 行分开是为了支持同一产品多个版本并存版本号加唯一约束是从数据库层面杜绝 Excel 时代“同名文件覆盖”的隐患。变更记录表是很多人容易忽略的一张它是未来回滚到历史版本的唯一依据。2.3 选开源而不是商业ERP的三个理由不少团队的第一反应是“直接用 ERP 不就行了”。但 ERP 里的 BOM 模块往往跟采购、库存、财务强绑定实施周期以月计算而且数据模型是按 ERP 的通用逻辑设计的。如果你只想把 Excel 里的 BOM 管起来不想动整个公司的业务流开源 BOM 管理软件是更轻的一刀。理由一是数据自主可控。开源系统部署在自己的服务器上数据库里所有表结构都看得到哪天不想用了直接把数据导出回 Excel 或转成 JSON 就能走人。商业系统很难给你这种自由度数据迁出往往要付费的迁移工具和漫长的流程。理由二是模型可以贴合自己的 BOM 口径。不同行业的 BOM 差别很大电子行业要位号、要替代料机械行业要材料规格、要表面处理软件行业要版本配套。开源的代码在自己手里加字段、改校验、接接口都可行不需要等厂商排期开发。理由三是成本结构清晰。开源的直接成本是服务器和运维没有按用户数、按模块收费的授权费。对几个人到几十个人的研发团队来说这个成本模型几乎可以忽略不计。当然开源也意味着没人替你的数据负责备份和权限管理得自己做——这是用“可控”换来的“责任”账要算清楚。3. 开源BOM管理软件怎么选先定技术栈再看Excel往返能力想清楚“为什么上”之后下一步是“选哪个”。开源 BOM 管理软件在代码托管平台上能搜到不少但名字换来换去底层套路其实就那么几类。选型时最容易犯的错误是一头扎进功能对比列表比了十几个功能点最后发现连 BOM 最基本的“多级展开”都没有做对。3.1 一份选型清单技术栈、部署方式、权限模型我一般会拿一张清单去过滤项目上面只写五个维度任何一个不满足就直接排除。第一是数据模型必须支持多层 BOM也就是子件下面还能挂子件不是只有“成品对零件”这一层。第二是 Excel 导入导出能力特别是导出——能不能在数据库里改了数据后把某个版本的 BOM 原样导回 Excel 给采购或产线用。第三是权限模型产品工程师只能改自己负责的产品其他人只读这个边界开源项目里并不是都做了。第四是技术栈团队没人会 Java 就别选 Java 技术栈重的项目宁可选 Python 或 Node.js 的后面改起来不痛苦。第五是部署方式有没有现成的 Docker 化部署决定了你半小时跑起来还是花两天配环境。这里有个反直觉的经验Excel 导入导出能力在选型里要放在非常靠前的位置。很多开源项目把 Excel 导入做得花里胡哨但导出只给 CSV格式还乱。你的下游用户习惯了 Excel如果系统不能把最新 BOM 完整地导回 Excel 给他们看这个系统就会被从流程里踢出去最后又回到“系统一套、Excel 一套”的双轨状态。3.2 三类常见开源方案对比处理完选型维度把市面上的开源方案粗略归成三类对照着看会更清楚。第一类是完整的 Web 应用自带 BOM 管理界面有产品、物料、版本页面。这种方案适合直接上手用功能边界清晰但定制要读懂它已有的代码结构。第二类是低代码或零代码平台上的 BOM 模板不用写代码就能搭出表单和审批流适合团队完全没有开发人员的情况但灵活度和数据结构自主权都受限。第三类是代码框架加自己整理的数据模型——只用了通用技术栈BOM 业务逻辑自己写。选型维度完整Web应用低代码平台方案自研轻量框架上手速度快部署即用最快拖拽配置慢先写代码数据模型自主权中等受限于已有代码低受平台约束高完全自己定义BOM多级展开多数已内置看平台能力自行实现Excel导入导出多数内置依赖插件自己写脚本灵活性最高适合团队有基本IT能力无开发人员有开发能力且要深度定制三类没有绝对的优劣关键看团队的边界条件。我自己见过的最容易翻车的是第二类低代码平台对接 Excel 导入时经常在数据类型转换上出问题数量1被读成文本导入后过滤条件全失效。如果团队里哪怕有一个人能写 Python我更推荐第三类思路——不用从零造轮子用轻量框架搭个 Web 壳把第 2 章那四张表建好BOM 核心逻辑自己维护Excel 导入导出也自己写落实可控度最高。3.3 用容器化在本地跑通一套的最小步骤无论选哪一类我建议先本地跑通再谈推广。容器化是验证一个开源 BOM 项目最快的方式。下面这段是一个典型的“应用 数据库”双容器骨架适用于绝大多数提供 Docker 镜像的开源方案。实际操作时把镜像名替换成你选定项目提供的镜像即可。# docker-compose.yml services: db: image: postgres:16-alpine # 集中式数据库用 PostgreSQL 最常见 environment: POSTGRES_USER: bom_user POSTGRES_PASSWORD: bom_pass POSTGRES_DB: bomdb ports: - 5432:5432 volumes: - bom_pgdata:/var/lib/postgresql/data # 数据持久化容器删了数据不丢 app: image: your-selected-bom-app-image # 替换为选定的开源项目镜像 ports: - 8080:8080 depends_on: - db environment: DB_HOST: db DB_PORT: 5432 DB_USER: bom_user DB_PASSWORD: bom_pass DB_NAME: bomdb volumes: bom_pgdata:这段 compose 文件的逻辑是数据库和 Web 应用分成两个容器应用通过环境变量连接数据库数据存在命名卷bom_pgdata里。跑docker compose up -d之后浏览器打开http://localhost:8080就能看到登录界面。注意两个参数POSTGRES_PASSWORD只是开发环境这么写内网多人共用的系统必须换成强密码并用环境变量注入depends_on只保证启动顺序不会等数据库完全就绪如果应用报数据库连接失败等几秒再访问通常就好。提示本地验证时先不要急着导真实 BOM用几个假物料把“新建产品 → 挂子件 → 导出 Excel”这条链路走通确认它满足第 2 章说得“版本唯一”和“Excel 往返”两个核心诉求再谈正式迁移。4. 把Excel物料清单迁移进数据库清洗、导入、闭环验证选型落地之后真正决定成败的往往是迁移这一步。直接把现成的 Excel 表格导入数据库大概率会得到一堆脏数据。物料编码不统一、父子关系没层级、单位混用这些问题 Excel 时代能忍进数据库后全会变成约束冲突和查不到数据。所以迁移必须分成三步先规范化模板再写导入脚本最后闭环验证。4.1 迁移前把Excel规范化成一个可解析模板我需要反复强调这一点不是让 Excel 数据来适应数据库而是先让 Excel 数据变得“可解析”。最靠谱的做法是给出一份固定列名的模板要求各产品工程师按模板整理。模板不需要复杂六列就够父件编码、子件编码、单件用量、单位、位号、备注。其中父件编码和子件编码是核心它们把多级 BOM 拍平成一行一行的父子关系。父件编码子件编码单件用量单位位号备注P1000A1002EAU1,U2板卡A100R1004EAR1-R4贴片电阻A100C2002EAC1,C2贴片电容P1000B2001EA-外壳这份示例表达的是成品 P1000 由板卡 A100 和外壳 B200 组成而 A100 下面又挂了电阻 R100 和电容 C200。注意看第二层关系里父件是 A100不是 P1000。这种平铺规则非常重要它决定了数据库里每一行都能对应一条独立的父子关系。整理时最容易出的问题是有人将多级 BOM 写成一列“层级路径”比如P1000/A100/R100这会给导入脚本增加很多解析工作量。4.2 Python导入脚本从Excel到PostgreSQL模板规范好后导入就可以脚本化了。下面是一段我常用的 Python 脚本核心逻辑是读取 Excel → 归一化编码 → 创建 BOM 头 → 写入明细行。用pandas读表用psycopg2写库整个流程控制在事务里任何一行校验失败都会整体回滚不会留下半个 BOM。# bom_import.py Excel BOM 批量导入集中式数据库的参考脚本 用法: python bom_import.py --excel bom.xlsx --sheet BOM --db-url postgresql://... import argparse import re import pandas as pd import psycopg2 from psycopg2.extras import execute_values def normalize_code(code: str) - str: 物料编码统一大写、去空格避免 R-100 和 r-100 被当成两个料 return re.sub(r\s, , str(code)).upper() def read_bom_excel(path: str, sheet: str) - pd.DataFrame: df pd.read_excel(path, sheet_namesheet, dtypestr) required [父件编码, 子件编码, 单件用量, 单位] missing [c for c in required if c not in df.columns] if missing: raise ValueError(fExcel 缺少必需列: {missing}) df df.fillna() df[父件编码] df[父件编码].map(normalize_code) df[子件编码] df[子件编码].map(normalize_code) return df def import_bom(df: pd.DataFrame, conn, product_code: str, version: str): 写入 BOM 头并批量插入明细任一行异常则整体回滚 with conn.cursor() as cur: cur.execute( INSERT INTO bom_headers (product_code, version, status) VALUES (%s, %s, DRAFT) RETURNING id, (product_code, version), ) bom_id cur.fetchone()[0] rows [] for _, r in df.iterrows(): qty float(r[单件用量]) if qty 0: raise ValueError(f用量必须大于0: {r[子件编码]} 当前 {qty}) rows.append(( bom_id, r[父件编码], r[子件编码], qty, r[单位], r.get(位号, ), )) execute_values( cur, INSERT INTO bom_lines (bom_id, parent_code, child_code, qty, unit, position_no) VALUES %s , rows, ) conn.commit() return bom_id if __name__ __main__: parser argparse.ArgumentParser() parser.add_argument(--excel, requiredTrue, helpExcel 文件路径) parser.add_argument(--sheet, defaultBOM, help工作表名) parser.add_argument(--product, requiredTrue, help产品编码如 P1000) parser.add_argument(--version, defaultV1.0, help本次导入版本号) parser.add_argument(--db-url, defaultpostgresql://bom_user:bom_passlocalhost:5432/bomdb) args parser.parse_args() df read_bom_excel(args.excel, args.sheet) with psycopg2.connect(args.db_url) as conn: bim import_bom(df, conn, args.product, args.version) print(f导入完成, BOM ID{bim}, 明细行数{len(df)})这段脚本有三个设计要点。第一normalize_code函数在导入前统一处理大小写和空格这是解决“一料多码”的第一道闸门。第二版本号由命令行参数传入而不是自动生成时间戳目的在于让导入动作与版本命名规则解耦版本怎么叫由人定数据库只保证同一产品不重复。第三execute_values是批量写入方式几千行 BOM 也能秒级入库不要一条一条执行 INSERT。脚本里没有自动建表建表工作由第 2 章那段 SQL 预先完成——导入脚本写作靠的是对数据模型的信任表结构都不存在时直接导入只会得到一堆报错。4.3 导入后的闭环验证与快速核对SQL导入完成不等于迁移完成必须在数据库侧做一次闭环验证。最常见的验证方式是导出对比从库里查出一个产品的完整 BOM和原始 Excel 逐项核对用量。但更高效的是一组 SQL 统计先看“量”再看“异常”。-- 1. 查看某产品已导入的版本列表 SELECT product_code, version, status, created_at FROM bom_headers WHERE product_code P1000 ORDER BY created_at DESC; -- 2. 找出父件不在产品 BOM 里的孤儿行这类行多半是 Excel 里有子件但漏了父件行 SELECT bl.child_code, bl.parent_code FROM bom_lines bl JOIN bom_headers bh ON bh.id bl.bom_id WHERE bh.product_code P1000 AND bl.parent_code P1000 AND bl.parent_code NOT IN ( SELECT child_code FROM bom_lines WHERE bom_id bh.id ); -- 3. 按父件维度统计直接子件数和 Excel 透视表结果核对 SELECT parent_code, COUNT(*) AS direct_children, SUM(qty) AS total_qty FROM bom_lines bl JOIN bom_headers bh ON bh.id bl.bom_id WHERE bh.product_code P1000 AND bh.version V1.0 GROUP BY parent_code ORDER BY parent_code;先说孤儿行查询它是多层 BOM 导入后最该先跑的检查。如果查询结果里出现了A100作为父件但整张表里找不到A100作为子件说明这个父件在 Excel 里层级不完整导入后 BOM 会断链。第三条查询的SUM(qty)是给同一个父件下的重复子件做合并校验用的——如果同一父件下同一子件被分成了多行这里就能看出一行一行相加的结果和手工 Excel 透视表对不上。注意闭环验证这一步不能省。我见过太多团队导完数据就宣布“上线了”结果采购第一批物料就是按断链的 BOM 下的单。宁可多花半天把这三条 SQL 跑一遍也不要省这个后悔药。5. 踩坑记录BOM导入与数据维护的5个高频事故迁移和日常维护里踩过的坑比选型和技术选型加起来都多。下面五条是直接从现场搬过来的高频事故格式统一是“现象 → 原因 → 解决”方便你对照排查。5.1 坑一父件还没建子件先入库外键约束报错现象导入时数据库直接报foreign key violation或者更隐蔽地BOM 展开时发现一个产品下面少了一层部件产线按缺层 BOM 备料。 原因多层 BOM 在 Excel 里平铺后子件行排在父件行前面导入脚本按行顺序 insert子件先入库时父件引用的记录还不存在。 解决不依赖 Excel 行序在脚本里先扫一遍父件编码把所有父件集合拿到先插入全部物料主数据再插入 BOM 行。或者更省事导入前在 Excel 里按“BOM 层级码”排序父件在前、子件在后但这个做法依赖人工不如脚本里做集合收集可靠。5.2 坑二物料编码大小写和空格不一致一颗料变两码现象查询某颗电阻时出现两条相似记录库存和 BOM 对不上采购多下了一倍数量。 原因Excel 时代R-100和r-100、R100都被当作同一个东西进数据库后成了三行。 解决导入脚本里统一做normalize_code处理同时物料主数据表给material_code加唯一索引。只靠脚本还不够要在数据库层面挡住任何新物料入库前先走归一化函数否则唯一索引会变成一道会误伤好人的墙。5.3 坑三单位不统一EA 和 PCS 混用导致用量对不上现象同一个物料在 A 产品的 Excel 里用量单位是PCS在 B 产品里是EA导库后两者数值看起来都对但一统计总用量就乱套。 原因Excel 模板的单位列没有做合法性校验工程师按自己习惯填写。 解决给单位列建一张单位字典表导入时若遇到字典外单位直接报错不放入库。单位字典建议只保留EA、KG、M、L这类基础单位PCS在导入前映射成EA在脚本里用unit_map {PCS: EA, 个: EA, 只: EA}做归一化。5.4 坑四改了用量直接 UPDATE历史版本全部烟消云散现象某物料用量改了三次三个月后质量追溯时想查“第二批货用的是 0.1uF 还是 0.2uF”数据库里只剩最后一个值。 原因导入或修改时直接对bom_lines执行 UPDATE没有生成新的 BOM 头版本。用量就成了“最后一次写进去的值”。 解决修改 BOM 统一走“新建版本”流程——从旧版本复制一份行数据在复制件上改旧版本保留并标记为SUPERSEDED。这要求业务上明确“版本只增不减”的纪律代码层面则要禁止不带bom_id条件直接 UPDATE 全表的行为。5.5 坑五多人同时编辑同一产品后保存的把先保存的覆盖了现象研发和生产同时打开官网页面互相不知道对方在改最后保存的一方让另一方的改动彻底消失。 原因系统没有实现乐观锁或版本冲突检测。数据库本身的最后写入机制是“谁后提交谁生效”。 解决在 BOM 覆盖层加版本号字段保存时前端把之前读到的版本号随请求带上后端比对当前版本号不一致就返回冲突错误。实现不复杂但能在源头把“并行编辑”的翻车概率降到零。对没有开发能力的团队最笨也最实用的办法是给每个产品加“检出”状态谁检出了别人只能只读。6. 进阶把变更记录做成BOM的后悔药前面那张bom_change_logs表很多人当成日志表闲置着实际上它才是 BOM 管理里最值得投入的功能。把一张 BOM 的整个变更史建起来你就有了一个可以随时回到任意历史时刻的“后悔药”。我会把每次变更设计成三个状态流转DRAFT草稿 →RELEASED已发布 →SUPERSEDED已替代。具体做法是业务上先登记“我要改什么”形成草稿审批通过后复制当前版本生成新版本并标记RELEASED此时旧版本自动变成SUPERSEDED所有下游引用都指向新版本。-- 发布新版本时把旧版本标记为已替代同时写变更记录 BEGIN; UPDATE bom_headers SET status SUPERSEDED WHERE product_code P1000 AND status RELEASED; UPDATE bom_headers SET status RELEASED WHERE id 2001 AND version V1.2; INSERT INTO bom_change_logs (bom_id, old_version, new_version, change_summary, changed_by) VALUES (2001, V1.1, V1.2, R100 阻值 4.7K 改为 10K电容 C200 供应商切换, 某工程师); COMMIT;这段 SQL 放在一起执行保证“旧版本失效”和“新版本生效”是原子操作。要回滚时只需要把SUPERSEDED的旧版本重新置为RELEASED再把当前错误的版本标记回SUPERSEDED。我在某设备厂见过一次惨痛教训一颗电阻阻值改完没走变更记录产线按新 BOM 装了一整批后来客户端异常退货翻数据库发现历史版本全被覆盖根本没法追溯是哪一批用了旧阻值。那之后我养成了一个习惯任何 BOM 修改必须写下变更理由哪怕一句话——“客户要求耐压降级”也比空记录有价值。这套变更记录不用做得复杂把“什么时候、谁、改了什么、为什么改”落到一张表里就够了。等到要应付质量审计或者客户追溯时你会感谢当初留下的每一行记录。希望这套从 Excel 文件到集中式数据库、从导入脚本到变更管理的路径能帮你在 BOM 这件事上少走几个月的弯路。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑