资讯动态

开源BOM管理软件:用集中式数据库替代Excel物料清单

发布时间:2026/10/9 15:42:46 来源:尧图企业网站定制
简介这套开源物料清单管理工具是一份完整的C#桌面应用源码面向电子制造企业的研发与采购人员解决多用户协同维护元器件清单、跟踪版本变更等管理难题。得益于与Ciiva电子元件搜索接口的深度集成系统能够在一个集中式数据库中统一管理元件库、物料清单及相关数据源适合希望二次开发或深入学习API集成与WinForms项目架构的中高级开发者。压缩包共50个文件主体为37个C#源程序涵盖API调用封装、数据对象定义、主窗体交互界面等模块另附5个动态链接库以及项目工程、配置文件用于支撑编译运行资源整体大小约315KB结构清晰小巧。目前已有838人学习下载后可直接查看请求与应答模型、库存价格查询等具体实现也可参照其界面与逻辑分层方式对理解电子元器件数据交互和物料清单管理流程具有实用参考价值。1. BOM 管理软件把 Excel 物料清单收进集中式数据库的开源方案在产线上干了几年你会发现最要命的不是缺料而是没人能说清某台设备的 BOM 到底哪个版本是对的。Excel 表在每个人电脑里各存一份改完不通知通知了不更新最终装配时只能用“以现场为准”。这个开源软件就是为解决这个问题来的它把散在 Excel 里的物料清单统一收进集中式数据库支持批量导入、版本对比、多级 BOM 展开和角色权限控制。适合电子工程师、工艺工程师和 PMC 去复现几千行的 BOM 也能在几分钟内收进库里之后每次改动都有迹可循。2. 为什么选集中式数据库Excel 管 BOM 的四宗罪与数据模型设计2.1 Excel 管 BOM 的四个典型痛点先说结论用 Excel 管 BOM 并不是不行而是“单机可用协同必炸”。四个痛点我挨个说。第一个是版本。一个产品 BOM 从样机到量产至少要改七八轮每轮改完“另存为 v2_final_终版”之类的文件名没过几天就会出现“到底哪个最终版”的口角。等到板上芯片电压标错溯源时只能打开十几份 Excel 逐个比较真正定位到变更行要花掉大半天而且没人愿意给这种追溯过程签字。第二个是权限。业务员、采购、工艺都有改表需求但 Excel 没有细粒度权限。要么每个人都能动文件要么锁死文件让协作效率归零。用共享文件夹也只在“文件共享”层面做覆盖不能做到“某个人只能改 BOM 里的替代料碰不了主料用量”。这类需求一提出来Excel 基本就败了。第三个是数据一致性。同一个物料在 BOM 里写“电阻 10kΩ 0603”在另一份表里写“10K 0603”系统不认为是同一个东西。哪怕人眼能认出是同一颗料软件做不了自动合并。更麻烦的是单位混用一根导线写成 0.5 m 或 500 mm对 Excel 来说完全是两个字符串。数据一多漏配错配自然就来了。第四个是追溯。批次号、变更原因、生效日期Excel 列一多就没人填。一旦产线问“这批料是什么时候换的”只能翻聊天记录或者猜。电子行业追责时最怕的就是“没有记录”。这四个痛点本质上都是“文件操作”而不是“数据操作”这也是我把注意力转向开源 BOM 管理软件的原因。2.2 选集中式数据库的理由从“文件共享”到“数据主权”集中式数据库和 Excel 最大的区别是“数据先入表权限后分配”。常见方案是 SQLite 起步数据量大了切 PostgreSQL。之所以选 SQLite 先跑是因为它零配置、单文件备份、对几十万行的 BOM 足够用到了多用户并发写再迁到 PostgreSQL表结构不用大改。还有一点集中式数据库天然支持事务。导入一份 BOM 时如果第 100 行出错事务回滚可以恢复到导入前状态这在 Excel 里是做不到的。Excel 的后悔药只有复制备份文件备份多了自己都分不清哪个是最新的。而事务机制可以保证导入过程要么全部成功要么全部失败不会出现“改了一半剩下半份没法要”的局面。从成本上看开源软件比商业 PLM 轻得多。一个几百种物料的小团队上重型 PLM 的采购和实施成本能顶小半年工资而这个开源项目用一台普通办公机跑 SQLite 服务端完全够。而且开源的好处是你能看到数据是怎么存的出了诡异问题能直接查库不会像商业软件那样是个黑匣子。如果你是从 CAD 或 EDA 工具转过来的比如 KiCad 生成的 BOM 文件或者 SAP 物料分类视图导出的表格导入这套系统后Excel 就只承担“输入/输出”的角色中间过程的加工、清洗、对比都在数据库里完成。2.3 数据模型设计核心表与字段定义这个开源项目的核心模型其实不复杂就是“料号-层级-数量-变更”四件事。我拆开看主表是 boms、bom_items、materials、versions 四张。boms 表存 BOM 头信息比如产品型号、描述、当前生效版本bom_items 存明细包含父项和子项的料号、用量、位号、替代料materials 存物料主数据统一单位、物料描述、分类versions 存每次变更的版本号、变更人、生效日期。下面这段建表 SQL 是我在这个场景下整理的参考结构不是项目原样但足够复现。对照这个结构你可以理解为什么它能支撑版本对比和多级展开而不是把 Excel 原样塞进去CREATE TABLE materials ( id INTEGER PRIMARY KEY AUTOINCREMENT, part_number TEXT NOT NULL UNIQUE, description TEXT, unit TEXT DEFAULT pcs, category TEXT ); CREATE TABLE boms ( id INTEGER PRIMARY KEY AUTOINCREMENT, product_number TEXT NOT NULL, name TEXT NOT NULL, current_version TEXT, created_at TEXT DEFAULT (datetime(now)) ); CREATE TABLE bom_items ( id INTEGER PRIMARY KEY AUTOINCREMENT, bom_id INTEGER NOT NULL REFERENCES boms(id), parent_part TEXT, child_part TEXT NOT NULL, quantity REAL NOT NULL DEFAULT 1, position TEXT DEFAULT , alternate_part TEXT, version TEXT, UNIQUE(bom_id, parent_part, child_part, position) ); CREATE TABLE versions ( id INTEGER PRIMARY KEY AUTOINCREMENT, bom_id INTEGER NOT NULL REFERENCES boms(id), version TEXT NOT NULL, file_name TEXT, change_note TEXT, updated_by TEXT, created_at TEXT DEFAULT (datetime(now)) );字段含义里值得注意的有几处part_number 统一用大写字母加数字方便做 join 匹配导入前必须做大小写归一化quantity 用 REAL 而不是 INTEGER是因为有些 BOM 用量会写 0.1 或 0.05比如胶水或锡膏按克算position 存位号如 R1、R2一个 child_part 可能有多个位号所以 UNIQUE 约束里带上 position避免同一行被覆盖。alternate_part 用来存替代料多个替代料可以用逗号分隔但实际项目中更好的做法是单独建一张替代料关系表我为了演示简化为一个字段。versions 表专门记录版本变化每次导入文件时插入一行这样随时能查“当前版本从哪来”。这就是集中式数据库的核心好处Excel 文件只是输入数据库里永远只有一份经过清洗的“真源”。后续的展开、对比、权限控制全都在这一套表结构上做文章。3. 快速部署与首轮导入把第一份 Excel BOM 吃进数据库3.1 环境准备与启动流程这个项目用 Python 写依赖不多。我一般会先新建虚拟环境再装依赖。下载解压后项目根部有 requirements.txt最核心的依赖是 Flask提供 Web 界面、openpyxl解析 Excel、sqlite3内置。启动命令如下cd bom-manager python -m venv venv source venv/bin/activate # Windows 下用 venv\Scripts\activate pip install -r requirements.txt python run.py --port 8000参数说明run.py 是入口脚本--port 指定端口默认 8000如果被占用就换 8001然后前端访问 http://localhost:8001。首次启动会在项目目录下自动生成 bom.db 文件这就是集中式数据库本体。如果你不想用浏览器也可以直接用 Python 调它的 API 接口但新手从 Web 界面开始更容易。启动之后页面会有一个“创建 BOM”的按钮。第一次用的时候我发现它不会自动建演示数据需要自己上传 Excel。这一步没什么坑但前提是 Excel 要按模板整理。如果你希望先试试水可以用项目自带的 sample 目录里面通常有一份 demo_bom.xlsx直接导入它就能看到效果。3.2 Excel BOM 模板要求与首轮导入不是所有 Excel 都能直接导入。这个项目认两张表第一张叫“头信息”第二张叫“BOM明细”。头信息里至少要有产品型号和 BOM 版本明细里关键列是母件料号、子件料号、用量、位号、备注。列名可以自定义但导入时要映射所以界面里会有下拉框让你选择“哪一列是子件料号、哪一列是用量”。我准备一个最小示例from openpyxl import Workbook wb Workbook() ws wb.active ws.title BOM明细 ws.append([母件料号, 子件料号, 用量, 位号, 备注]) ws.append([PCB-V1.0, R010, 2, R1,R2, 1% 10K]) ws.append([PCB-V1.0, C020, 4, C1,C2,C3,C4, 100nF 0603]) wb.save(demo_bom.xlsx)这段只是造一份测试文件真实导入时你手上肯定有从 CAD 或 EDA 工具导出的表。像 KiCad 生成的 BOM 文件或者 SAP 物料视图导出的表格都可以先整理成两行表头再导入。导入页面里把 Excel 文件拖进去勾选对应列名映射点击“导入”即可。这里有两个导入模式需要特别注意一个是“新增版本”一个是“替换现有物料”。我第一次使用时直接点了“覆盖导入”结果把整个 BOM 的旧版本全部冲掉。后来才意识到正确做法是每次上传都作为新版本追加到 versions 表只在审批通过后切换 current_version。这个设计保证任何误操作都有后悔药。所以第一轮导入时建议选“新增版本”后续确认无误再手动切换。3.3 用 SQL 查询验证导入结果导入完别急着信界面先用 SQL 验证数据条数和关键字段。我用 sqlite3 命令看一眼sqlite3 bom.db SELECT COUNT(*) FROM bom_items; SELECT product_number, name FROM boms; SELECT child_part, quantity, position FROM bom_items WHERE bom_id 1;逻辑说明第一条是看明细总数如果 Excel 有 100 行就应该接近 100 行第二条查所有 BOM 头第三条看一个 BOM 的具体用量。如果发现 count 比 Excel 行数少说明导入器做了去重或跳过了空行这是常见行为不是错误。此时应该打开导入日志确认哪些行被跳过而不是直接开始改数据。我一般会把这个验证过程做成一个“导入后必查三步”的清单-- 检查是否有空料号的行 SELECT COUNT(*) FROM bom_items WHERE child_part IS NULL OR child_part ; -- 检查用量是否正常 SELECT child_part, quantity FROM bom_items WHERE quantity 0; -- 检查版本号是否全部填上 SELECT COUNT(*) FROM bom_items WHERE version IS NULL OR version ;这三个查询分别对应空料号、零数量、版本缺失三类问题。空料号说明 Excel 里有多余空行数量为 0 或负数是 BOM 常见的脏数据版本为空则会导致后面的版本对比失效。这三步走完数据才算真正进了库。如果导入后想改数据最好不要直接在界面上逐行改而是用 SQL 批量更新。比如把某个位号字段统一格式化或者把某个料号的描述补全SQL 都更高效。但注意改之前先备份 bom.db 文件毕竟这是你辛辛苦苦建起来的真源一条 UPDATE 语句写错后果比 Excel 误删严重。4. 日常使用与进阶配置物料归并、版本对比与权限管理4.1 多级 BOM 展开与物料归并逻辑单层 BOM 好理解真正麻烦的是多级 BOM一个装配件里包含子装配子装配又包含零件。这个软件用递归展开实现多级 BOM 的“拍平”。拍平后每个底层物料只出现一次但它的数量会乘上每一级的用量。例如某套件 A 需要 2 个模块 B每个 B 需要 4 个电阻 R10。拍平后 R10 的总量是 2×48。这是常见做法叫“BOM 展开”或“多级展开”。项目里我一般会写一段递归 SQL 或 Python 脚本完成这件事。如果直接用 SQL可以用公共表表达式递归SQLite 3.8.3 以上支持 WITH RECURSIVE。参考写法WITH RECURSIVE bom_tree AS ( SELECT bom_id, child_part, quantity, 1 AS lvl FROM bom_items WHERE parent_part A UNION ALL SELECT b.bom_id, b.child_part, t.quantity * b.quantity, t.lvl 1 FROM bom_items b JOIN bom_tree t ON b.parent_part t.child_part ) SELECT child_part, SUM(quantity) AS total_qty FROM bom_tree GROUP BY child_part;这段逻辑是第一层找直接子件第二层向上递归直到没有子装配为止最后汇总每个底层料号的数量。注意递归 CTE 的终止条件如果 BOM 里有循环引用比如 A 包含 B、B 又包含 A这条 SQL 会死循环。我在一个模拟项目里碰到过一次后来加了 lvl 10 的深度限制避免数据库被拖死。深度限制可以在 SQL 里加 WHERE t.lvl 10。物料归并是展开后的一个必然动作。同一个底料在多个子装配里都可能出现归并时除了数量求和还要把位号用逗号拼到一起。位号太多时 Excel 单格有长度限制可以只保留前 200 个字符剩下的放到备注里。这个逻辑在 Python 里做比纯 SQL 更灵活我一般先把展开结果取出来再按料号 groupby用,.join(position_list)拼接位号。实测几千行的 BOM 展开后Python 端处理只需要一两秒不会成为瓶颈。4.2 版本对比与变更通知版本对比是这个软件的核心亮点。两版 BOM 对比时项目按“子件料号位号”作为键找出新增、删除、数量变化三类差异并以表格形式展示。具体实现上是给 versions 表加一个 snapshot 字段每次导入时保存整份明细的 JSON 快照。对比时用 Python 读两个快照做一个字典差集。代码参考import json def diff_bom(old_snapshot, new_snapshot): old {item[key]: item[qty] for item in old_snapshot} new {item[key]: item[qty] for item in new_snapshot} added new.keys() - old.keys() removed old.keys() - new.keys() changed {k for k in old.keys() new.keys() if old[k] ! new[k]} return {added: list(added), removed: list(removed), changed: list(changed)}diff_bom 函数返回三类差异列表。逻辑先把明细转成以“料号-位号”为 key 的字典然后做集合差运算。这样就算有几百行明细对比也在毫秒级。参数说明old_snapshot 和 new_snapshot 都可以直接从 versions 表里按版本号读出来格式是[{key: R010|R1,R2, qty: 2}, ...]。变更通知则是靠定时任务每小时扫一次 versions 表发现新版本就推送消息到内部群如果要求不高的团队直接在导入时勾选“邮件通知”即可。定时任务在 Linux 上常用 cron每小时的命令可以写成0 * * * * python /path/to/check_update.py。Windows 上则可以用计划任务触发。真正值得注意的坑是对比前必须确保两版快照的 key 生成规则一致否则会出现“全删全增”的假差异。比如旧版用part_number做 key新版用part_numberposition对比结果就会乱套。4.3 用户角色与权限谁可以改、谁只能看开源版本默认没有很重的 RBAC但一般会带一个简化的用户角色admin、editor、viewer。admin 能创建用户、删除数据editor 可以导入和编辑 BOMviewer 只能查询。权限在 Web 层做拦截后端接口会校验登录用户的角色字段。在配置里常见的做法是用 Flask-Login 加一个 require_role 装饰器。参考from functools import wraps from flask import session, abort def require_role(role): def wrapper(fn): wraps(fn) def decorated(*args, **kwargs): if session.get(role) ! role: abort(403) return fn(*args, **kwargs) return decorated return wrapper这个装饰器挂在需要限制的路由函数上比如require_role(editor)。参数说明session[role] 在登录时写入实际用的时候可以用flask_login.current_user.role代替更安全。我之前只在前端隐藏编辑按钮、后端没做校验结果有人绕过了界面直接调接口把 BOM 改了从那以后所有类似项目都坚持后端校验。如果你需要更细的权限比如“采购只能看价格列工艺只能看装配关系”那就需要给 bom_items 表加单独的权限字段或者按列做掩码。开源版一般不做这么细但数据模型允许你扩展在 materials 表加 price_visible、process_visible 等字段前端根据角色渲染列。实现不复杂难的是想清楚权限粒度建议先从最小粒度开始用不到的功能不要加权限模型也是维护成本。4.4 替代料与多供应商编码真正贴近生产的用法一个容易被忽略的点是替代料。很多 BOM 软件只管主料号不管替代料导致缺料时找不到可用替代方案。这个开源项目在 bom_items 里留了 alternate_part 字段但实际使用时我会把它拆成一张 alt_parts 表理由是一个物料可能有多个替代料保存成逗号串很不好做查询。参考结构CREATE TABLE alt_parts ( id INTEGER PRIMARY KEY AUTOINCREMENT, bom_item_id INTEGER NOT NULL REFERENCES bom_items(id), alt_part TEXT NOT NULL, is_preferred INTEGER DEFAULT 0, UNIQUE(bom_item_id, alt_part) );这样做的直接好处是可以用一条 SQL 找到“哪些产品型号用了某颗主料它又有哪些替代料”SELECT b.product_number, i.child_part, a.alt_part FROM bom_items i JOIN boms b ON i.bom_id b.id LEFT JOIN alt_parts a ON i.id a.bom_item_id WHERE i.child_part R010;多供应商编码也是同样的道理。同一个零件的不同供应商料号可以挂在同一颗物料主数据下在采购视图里展示。但注意替代料不能随便进 BOM 计算否则用量汇总会重复建议替代料只做参考标记不参与展开计算。这也是我踩过的一个坑后续在避坑章节里会再说。5. 避坑与常见问题排查编码乱码、层级丢失、重复物料与性能瓶颈5.1 Excel 中文列名导入后乱码现象导入后明细页显示“物料名称”变成乱码但数字字段正常。原因Excel 文件编码是 GBK/GB2312而项目默认按 UTF-8 解析。尤其是国内很多 EDA 工具导出的 Excel看起来正常实际上内部编码是 GBK导入器用 UTF-8 读中文列名就乱了。解决导入前先把 Excel 另存为 UTF-8 编码的 CSV或者用 openpyxl 读取时指定编码。如果是 CSV 导入打开文件确认编码格式。若用 Pandas 读取可以df pd.read_csv(bom.csv, encodinggbk)。如果已经乱码入库用 UPDATE 语句按 id 把 description 字段重新赋值别全表刷。还有一个土办法在 Excel 里选中列用公式UNICODE()检查第一个字符的码位能快速判断是哪类编码。5.2 多级 BOM 层级关系丢失现象导入的 Excel 明明有缩进层级导入后所有行都是平级子装配件和零件混在一起。原因Excel 里的层级是视觉缩进不是结构化字段。导入器只认“母件料号”和“子件料号”不认空格。很多人喜欢用几个空格或 Tab 表示层级导入器根本看不见。解决整理表格时保证每一行都正确填写母件料号不能只在第一行写后续行留空。建议用一个辅助列用公式把上一行母件料号填充下来再导入。比如新增一列父项写C5然后下拉填充。导入前用脚检查母件料号列是否为空SELECT COUNT(*) FROM bom_items WHERE parent_part IS NULL OR parent_part ;如果检查结果不是 0就先回 Excel 补全再导入。另外我习惯把“父项”列放在子件料号左边因为导入界面的列映射部分实现是按列索引匹配的列位置错了全乱。5.3 重复物料和单位不一致现象同一个电阻在三个 BOM 里分别写“10K”“10kohm”“10KΩ”展开后出现三行同料号不同描述。更麻烦的是单位不一致一根导线写成 0.5 m 或 500 mm导入系统后会被当成两种物料。原因物料主数据没有统一规范Excel 里的描述是自由文本软件无法智能识别“这是同一个料”。解决导入前先跑一遍“物料主数据清洗”把描述去空格、统一大写、单位统一转成标准单位。可以在 materials 表加唯一索引比如part_number unit description。具体清洗脚本可以用 Pythondef clean_part_no(s): return str(s).strip().upper().replace(Ω, ohm).replace(K, K) def clean_unit(s): mapping {个: pcs, pcs: pcs, 米: m, mm: mm} return mapping.get(str(s).strip().split()[0], str(s).strip())这段脚本在导入前对 part_number 和 unit 做标准化。逻辑很简单去掉空格、统一大小写、把中文单位映射成英文。注意清洗的逻辑要放在导入函数的第一步否则生成的 part_number 永远对不上。如果发现已有数据混乱先跑一次UPDATE materials SET description REPLACE(description, Ω, ohm)之类的语句再考虑合并。5.4 大批量导入时长时间无响应现象一次导入 2 万行的 BOM界面转了 5 分钟没反应最后报超时。原因导入逻辑逐行 INSERT并且每条都做唯一性检查数据库连接没有开事务每插入一条都要 fsync 一次慢是必然的。解决把导入改成批量提交。常见做法是每 500 行 INSERT 后 commit 一次或者用 executemany 一次插入多行。同时给 bom_items 表建复合索引(bom_id, parent_part, child_part)查询才能快。参考批量插入写法import sqlite3 conn sqlite3.connect(bom.db) cur conn.cursor() rows [(1, PCB-V1.0, R010, 2, R1,R2, , v1), (1, PCB-V1.0, C020, 4, C1,C2,C3,C4, , v1)] cur.executemany(INSERT INTO bom_items (bom_id, parent_part, child_part, quantity, position, version) VALUES (?,?,?,?,?,?), rows) conn.commit()这里 executemany 的参数是元组列表SQL 里的?占位符依次对应每条数据的字段。注意每条元素数量必须和 SQL 字段数一致少了会直接报 OperationalError。另外实测中打开PRAGMA journal_modeWAL;也能减少锁竞争。落库之后记得跑一下ANALYZE让查询计划走索引。5.5 版本字段为空导致对比失败现象两版 BOM 对比时页面报错对比结果是空的。原因明细行里的 version 字段没填。导入器把版本号从文件名或头信息取过来如果取不到就置空。版本为空版本对比功能就找不到基准自然只能报错。解决导入前在 Excel 头信息里补全版本号如果已经导入用 UPDATE 按 bom_id 回填 current_version。再保险一点导入流程里强制校验版本字段不为空否则拒绝导入。回填语句参考UPDATE bom_items SET version V1.0 WHERE bom_id 1 AND (version IS NULL OR version );执行前先 SELECT 确认影响范围别把别的 BOM 一起刷了。正确做法是先把版本表里的版本号查出来一条条核对再执行 UPDATE。6. 用导出脚本把数据库 BOM 变回 Excel保住格式与公式的小技巧很多公司产线还是认 Excel 表单。数据库管理好了最后还得导回去给产线用。这个开源项目自带导出功能但默认导出的样式比较朴素表格边框、列宽都不会保留。我一般会写个小脚本做“格式恢复”。示例使用 openpyxl把查到的 bom_items 明细写入工作表并设置边框和自动筛选import sqlite3 from openpyxl import Workbook from openpyxl.styles import Border, Side from openpyxl.utils import get_column_letter conn sqlite3.connect(bom.db) rows conn.execute(SELECT parent_part, child_part, quantity, position FROM bom_items WHERE bom_id1).fetchall() wb Workbook() ws wb.active ws.append([母件料号, 子件料号, 用量, 位号]) for row in rows: ws.append(row) thin Border(leftSide(stylethin), rightSide(stylethin), topSide(stylethin), bottomSide(stylethin)) for cell in ws[1]: cell.border thin ws.auto_filter.ref ws.dimensions wb.save(export_bom.xlsx)逻辑说明这段脚本先连本地 SQLite 库把查询结果写入 Excel然后统一加边框并开启自动筛选。参数说明bom_id1要替换成你的 BOM 主键如果需要多级展开就先把递归查询结果存成视图再导出。列宽可以在导出前用ws.column_dimensions[A].width 20调整避免中文列名显示不全。我踩过最大的坑是忘了处理位号列里的换行符。某些 EDA 导出的 BOM 中位号是逗号分隔的导回 Excel 后被识别为公式的一部分或者自动变成科学计数法。解决办法是导出的单元格格式设置成文本或者在写入前把位号里的逗号换成空格比如position.replace(,, )。从那以后我每次导完都强制检查一列位号列的长度和中文字段是否溢出确认导出的 Excel 行数和数据库查询数一致才敢发给产线。希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑