
简介捷远BOM管理系统单机版是一套面向中小企业物料管理、生产计划人员及BOM初学者的轻量级工具免费且免安装单机即可运行。它围绕订单建立BOM并自动计算需求数量辅助完成领料与预算资源以三层台式电脑组装为例演示BOM搭建、领料和预算的处理思路生产型企业可在此基础上扩展更多层级。压缩包共12个文件、约3.05MB内含exe主程序、mdb数据库、xls实例、8个apt报表与单据模板及htm说明文档覆盖BOM报表、领料单和预算表等常见表单结构紧凑便于快速了解系统组成并直接试用。通过示例数据可对照BOM层级、物料需求与单据之间的对应关系降低上手成本。已有1583人学习适合希望低成本梳理BOM层级、订单需求测算和领料预算流程的读者参考也可作为理解BOM管理逻辑的入门样例。1. 用 Excel 管 BOM 的第三个月我把它换成了单机版某小型装配厂产品就五六个BOM 一直靠一张 Excel 维护。工程改了用量没有同步给采购采购按旧表下单回来一箱料装不上仓库说数量对车间说少件最后查了半天是半成品层级漏了一行。这种事故在没上 ERP、或者 ERP 只跑了财务模块的团队里几乎每周都在重演。捷远BOM管理系统单机版这类工具解决的正是这一段不装数据库服务、不配服务器解压到本地目录双击就跑物料主数据、多层 BOM、用量与损耗、版本与替代料用固定表结构管住导出直接给采购和仓库。适合产品结构不复杂、两三个人维护 BOM、暂时不想为 ERP 付实施费的小团队也适合想先把数据结构想清楚再上系统的个人。2. 解压即用不等于直接开录目录、数据库和第一份备份便携版最容易被误读成“随便放哪都行”。实际上单机版能不能稳定跑八成取决于三件事放在哪个路径、数据文件是什么格式、第一次打开之前有没有做备份。这三件事在装正式软件时由安装程序替你兜底免安装之后全部落到自己手上先花二十分钟理清楚后面省掉的是返工和重录。2.1 便携包里各文件的分工解压后先别双击主程序把目录树看一遍。这类单机版工具常见的布局是一个主程序可执行文件、若干运行库 DLL、一两个.ini或.json配置文件、一个存放数据的目录常见命名是Data、DB、database、一个模板目录导出 Excel 用的表头模板、一个日志目录。数据文件扩展名可能是.mdb、.accdb、.db或.sqlite取决于开发时选的嵌入库从扩展名基本能判断个大概。判断数据到底存在哪有两个不用猜的办法。一是打开软件录一条测试数据然后看哪个目录的文件修改时间变了、体积涨了二是直接翻配置文件里面通常写着数据库相对路径。这两步做完你就知道该备份哪个文件夹——很多人只备份了主程序目录结果数据在用户目录下的AppData里换台机器就“数据丢了”这类翻车我见过不止一次。注意数据目录不要放在 OneDrive、网盘同步盘、桌面或者C:\Program Files下。前两者会在软件写库的瞬间触发同步锁后者涉及 UAC 虚拟化写入会被重定向到用户目录表现出来就是“明明保存了重开又没了”。2.2 首次启动路径、权限和区域设置存放路径尽量短全英文不带空格例如D:\Tools\BOM。中文路径在部分老式数据库驱动上会出现连接字符串解析异常报错信息往往只有一句“未指定的错误”排查起来很费时间。如果你打算放 U 盘随身带注意盘符变化有些软件的配置文件里写死了绝对路径换机器就找不到库。首次启动一般会走一个初始化流程建库、建表、写入默认管理员账号和示例数据。这里有个顺序问题——先看有没有示例数据有的话在正式录料之前清空否则后面做 BOM 展开和成本汇总时示例物料会混进统计里对账对到怀疑人生。清空之前务必确认已经完成一次完整备份。区域设置也要看一眼。小数点和千分位分隔符在部分系统上会影响用量字段的解析1,5和1.5在导入时是两回事。常见做法是把系统区域里的数字格式统一成点号小数或者干脆在录入阶段就避免手输小数用导出模板批量导入。2.3 数据文件在哪备份怎么做单机版没有服务端备份就是复制文件但有前提软件必须完全退出。文件型数据库在运行时会持有锁或者把变更先写在日志文件里运行中直接拷贝大概率拿到一个不一致的快照——这种备份比没有备份更危险。我一般会写一个开机就跑的镜像脚本用系统自带的robocopy不装任何额外工具# 打开软件之前先跑一次把程序目录连同数据整体镜像到备份盘 $src D:\Tools\BOM # 软件根目录含数据文件 $stamp Get-Date -Format yyyyMMdd_HHmm # 时间戳精确到分钟 $dst E:\BOM_Backup\$stamp # 备份目标按时间分目录 robocopy $src $dst /MIR /R:1 /W:1 /XD Logs Temp /LOG:E:\BOM_Backup\log_$stamp.txt/MIR是镜像模式源目录删掉的文件目标端也会删保证备份和源一致/R:1 /W:1把失败重试压到一次、等待一秒避免网络盘掉线时脚本卡死/XD排除日志和临时目录减体积也避免把锁文件复制过去/LOG留一份记录出问题时能看出哪几个文件没复制成功。脚本放在开机启动里但要注意它和软件启动的先后——更稳的做法是手动执行确认日志里Copied数量正常再开软件。备份不是复制完就完事得做一次恢复演练把某个时间戳的目录整个拷到另一台机器双击能不能打开、数据在不在。这一步十分钟能验证你的备份是真备份还是心理安慰。单机版免安装的好处是卸载就是删目录坏处也在这里——没有安装程序帮你保留配置误删就是真没了。3. 从物料编码到多层展开一条 BOM 录进去的完整链路BOM 系统的价值不在界面在数据关系。物料主数据、BOM 行、替代料、版本这四张表之间的约束一旦松了后面所有导出和汇总都是错的。这一章按录入顺序走一遍先定编码再搭层级最后验证展开和反查。3.1 物料主数据编码规则先定死再录编码规则没定就开录是这类项目最常见的返工原因。今天用A-001明天新同事用成品001一个月后同一颗螺丝在系统里有三个身份反查和汇总全部失效。建议在录第一条数据之前把分段规则写进一张对照表贴在看得到的地方。段位位数含义示例取值第 1 段1物料大类1 成品、2 半成品、3 原材料、4 包材第 2 段2子类01 机加、02 钣金、03 标准件、04 电气第 3 段4流水号0001 起按子类独立递增第 4 段1状态位A 在用、B 试制、Z 停用规则定完用一段脚本把所有待录入的编码过一遍比人工核对快得多import re # 规则1位大类 2位子类 4位流水 1位状态共8位 PATTERN re.compile(r^[1-4]\d{2}\d{4}[ABZ]$) def check(codes): bad [] for c in codes: c c.strip().upper() if not PATTERN.match(c): bad.append((c, 格式不符)) elif len(set(codes)) ! len(codes): bad.append((c, 存在重复)) return bad if __name__ __main__: # 从导出的物料清单里读编码列一行一个 with open(materials.txt, encodingutf-8) as f: codes [line.strip() for line in f if line.strip()] for code, reason in check(codes): print(f{code}\t{reason})正则里[1-4]锁大类范围\d{2}和\d{4}锁位数[ABZ]锁状态位。重复判断这里写得比较粗实际用的时候把set那段挪到循环外面先算一次集合再比对物料上千条时性能差别明显。这段脚本的定位是录入前的守门员不是替代系统校验——系统里能设唯一约束的字段尽量都设上脚本只补它管不到的那部分。单位和采购单位的换算也要在主数据阶段定好。一个物料按“米”入库、按“根”采购换算系数填错BOM 展开出来的需求量会整倍数偏移而且这种错误在单层 BOM 里看不出来要到多层汇总才暴露。3.2 多层 BOM父子件、用量、损耗率BOM 行的核心字段其实就四个父件、子件、用量、损耗率。填的时候最容易出问题的是用量的基数——是按“每台”还是按“每千台”。有些系统在表头有个“基数”字段默认 1改过之后整张表所有行的用量含义都变了也有的系统固定按单台用量允许小数。两种做法都合理怕的是录的人不知道当前是哪种。字段含义常见填错父件编码上层物料填成了子件层级整个反过来子件编码下层物料引用了已停用状态 Z的编码用量装配一台需要的数量按千台基数填但系统按台算差一千倍损耗率工艺损耗百分比填成 5 而不是 0.05位号/工位装配位置选填但排产和工艺文件会用到损耗率的填法要统一。有的团队习惯填5表示 5%有的填0.05混着来会导致采购建议量偏差。我一般会要求全表统一成小数导入前用脚本扫一遍大于 1 的值逐个确认是不是填错了。多层结构怎么拆取决于车间的实际装配顺序。一个常见的拆法是三层成品 → 部件半成品→ 零件和原材料。层数不是越多越好超过四五层之后单机版的树形展开在屏幕上就不太看得清了维护成本上升收益下降。产品结构简单的话两层反而更好维护。3.3 展开与反查验证数据关系有没有搭对录完之后必须做两个方向的验证。正向是展开选一个成品看系统算出来的总用量和手工按图纸算一遍对比差在哪一行能立刻定位。反向是反查也叫 where-used选一颗螺丝看它被哪些半成品和成品引用。反查是改料、停产、替代时最常用的功能如果系统里这个查询结果为空八成是某个父件编码录错了。单机版的展开通常是递归算的要留意系统有没有设层级上限。层级超过上限时有的软件会静默截断表现出来就是“总用量少了”但没有任何报错。验证办法是挑一个层级最深的成品展开后看最底层物料有没有出现在结果里。数据量大之后用导出文件做独立核对更可靠from collections import defaultdict def load_bom(path): 读导出的BOM明细返回 父件 - [(子件, 用量)] g defaultdict(list) with open(path, encodingutf-8-sig) as f: next(f) # 跳过表头 for line in f: parent, child, qty line.rstrip(\n).split(\t) g[parent].append((child, float(qty))) return g def explode(g, root, qty1.0, depth0, seenNone): 递归展开seen 用来防止循环引用把栈打爆 seen seen or set() if root in seen: print(f检测到循环引用: {root}) return seen.add(root) for child, per in g.get(root, []): total qty * per print( * depth f{child}\t{total:.4f}) explode(g, child, total, depth 1, seen) seen.discard(root) if __name__ __main__: bom load_bom(bom_export.txt) explode(bom, 1000001A)explode里的seen集合是关键单机版数据靠人工维护父子件互相引用这种错误并不罕见脚本里不加保护就会无限递归。qty参数一路往下乘得到的是每一层的累计用量和系统展开结果对不上时先看是哪一层开始偏的再回头查那一行的用量字段。4. 版本、替代料与变更留痕别让采购拿到旧表BOM 出错造成的损失一大半不是数据本身错而是“拿错了版本”。工程改了采购手里还是上周导出的表替代料批下来了仓库不知道优先级。单机版没有工作流引擎这些只能靠字段设计和导出纪律来补。4.1 版本号与生效日期怎么设版本命名建议用字母递增A 为首版B、C 依次往后不要用日期当版本号——日期看起来直观但同一天改两次就没法区分了。系统里如果支持“生效日期”一定要填并且约定同一产品在同一时点只有一个生效版本。没有这个约束两个版本并存展开时取哪个全看系统实现这种不确定性在生产现场就是事故。改版的操作顺序也要定死先复制旧版本生成新版本在新版本上改改完设生效日期旧版本设失效日期而不是直接在原版本上编辑。直接编辑等于把历史抹掉了以后要追溯“这批货是按哪版做的”就无从查起。4.2 替代料组与用量损耗替代料一般按组管理一个主料挂若干替代料组内设优先级和替代比例。优先级决定默认取哪个替代比例决定可以混用到多少。这里容易忽略的是替代料的用量换算——主料用 1 个替代料因为规格不同可能要用 1.2 个替代关系里必须带上这个系数否则采购按 1:1 备料上线就缺。损耗率的归属也要统一放 BOM 行还是放工艺路线。放 BOM 行的好处是展开就能算采购建议量坏处是同一个物料在不同产品上损耗不同时要重复填。小团队我一般建议放 BOM 行简单直接代价是改工艺时要同步改多张表。4.3 变更留痕与导出对齐单机版通常有操作日志或者变更记录表能查到谁在什么时候改了哪一行。这个功能平时没人看出问题时是唯一的黑匣子。启用之前确认日志是追加写而不是覆盖写覆盖写的日志等于没有。导出给采购和仓库的字段要固定成一套模板不要每次临时选列。列顺序一变对方拿去做透视表就得重来。更稳的做法是把导出模板存成文件每次用同一个模板导字段名和顺序都不变。两次导出的差异对比用脚本比人眼靠谱import csv def load(path): 读导出的BOM返回 {(父件, 子件): 用量} d {} with open(path, newline, encodingutf-8-sig) as f: for r in csv.DictReader(f): key (r[父件编码].strip(), r[子件编码].strip()) d[key] float(r[用量]) return d old load(bom_202401.txt) new load(bom_202402.txt) for key in sorted(set(old) | set(new)): a, b old.get(key), new.get(key) if a is None: print(f新增\t{key[0]} - {key[1]}\t{b}) elif b is None: print(f删除\t{key[0]} - {key[1]}\t原 {a}) elif abs(a - b) 1e-9: print(f改量\t{key[0]} - {key[1]}\t{a} {b})用元组当字典键父子件组合天然唯一。浮点比较用1e-9的容差而不是直接!避免0.10.2这类精度问题误报。输出分新增、删除、改量三类直接贴进变更单里当附件比手写“本次调整了若干物料”有说服力得多。5. 避坑与排查单机版 BOM 最容易翻车的几件事单机版的问题有个共同点没有服务端日志可查报错信息往往只有一句话剩下的全靠自己从文件、路径、数据三个层面推。下面这几条是实际维护中最常遇到的每条按现象、原因、解决写。5.1 双击没反应或者提示数据库被占用现象是主程序启动后一闪而过或者弹窗提示“文件已被另一进程使用”。原因通常有两个上一次没有正常退出进程还挂在后台占着数据库文件或者杀毒软件把写库动作当成可疑行为拦了。前者去任务管理器看有没有残留进程结束掉再启动后者把软件目录加进杀软的白名单尤其是数据目录和临时目录。还有一种情况是目录权限不足。软件放在C:\Program Files下普通用户对目录没有写权限数据库文件建不出来。解决办法是整个目录挪到D:\这类用户可写的位置别去改目录权限改完下次系统更新又可能被重置。5.2 两台电脑拷来拷去数据互相覆盖现象是 A 机器录了半天拷到 B 机器上继续录再拷回来发现 A 的改动没了。原因很直接整个目录覆盖式复制后复制的赢。单机版没有合并机制两个副本之间没有任何冲突检测。解决方式有三种按团队规模选。一个人用就固定一台机器其他机器只读导出文件两三个人用约定同一时间只有一个人写写完立刻备份并通知再大一点就别用单机版硬撑了换带并发控制的方案。我见过最惨的一次是两份数据各录了一半最后人工比对合并花的时间够重录三遍。5.3 父子件循环引用展开直接卡死现象是点展开按钮后软件无响应或者展开结果里同一个物料反复出现。原因是 A 是 B 的父件B 又是 A 的父件递归没有终止条件。人工录入时这种错误不常见但从 Excel 批量导入时很容易带进来——模板里父件列和子件列填反了就会造成循环。排查用第 3 章那段带seen集合的脚本能直接把环路打印出来。修复就是删掉或改正构成环路的那一行。预防措施是导入前先跑一遍环检测别等展开卡死再回头找。5.4 Excel 导入列错位数据整体偏移现象是导入成功但数据全乱用量跑到位号列或者整行错位一格。原因通常是模板列顺序被改过或者 CSV 里某个字段本身带逗号没有加引号导致解析时多切了一列。带逗号的物料描述是重灾区。解决分两步导入前用文本编辑器看一眼前几行的分隔符数量每行应该一致导入后随机抽十条和源文件比对。模板文件建议设成只读避免有人“顺手调一下列顺序”。单机版的导入功能一般不做严格的列名校验全靠自己把关。5.5 成本汇总对不上差在损耗和单位现象是系统算出来的材料成本和财务手工算的差一截。原因通常集中在两处损耗率有没有乘进去以及采购单位和基本单位的换算系数有没有生效。有的系统在成本汇总时默认不含损耗有的含这个要在报表参数里确认清楚不能想当然。排查办法是挑一个结构最简单的成品手工按“用量 × 单价 × (1损耗率)”逐行算和系统结果逐行比。差在固定比例上多半是损耗率差在整倍数上多半是单位换算。把这一条产品对齐之后再去看复杂的多层结构问题会少很多。6. 交付前跑一遍三个校验把 BOM 数据过筛子数据录完、版本定完离能用还差最后一步全量校验。人工抽查十条八条永远抽不到那行出问题的。我的习惯是导出全量 BOM跑一个脚本把三类问题一次扫出来——循环引用、孤儿物料、零用量行。这三类占了实际错误的绝大多数而且都能在交付前修掉。import csv from collections import defaultdict def load(path): rows, parents, children [], set(), set() with open(path, newline, encodingutf-8-sig) as f: for r in csv.DictReader(f): p, c r[父件编码].strip(), r[子件编码].strip() q float(r[用量] or 0) rows.append((p, c, q)) parents.add(p) children.add(c) return rows, parents, children def check_cycle(rows): g defaultdict(list) for p, c, _ in rows: g[p].append(c) color, stack, bad defaultdict(int), [], [] def dfs(u): color[u] 1 stack.append(u) for v in g[u]: if color[v] 1: bad.append(stack[stack.index(v):] [v]) elif color[v] 0: dfs(v) stack.pop() color[u] 2 for u in list(g): if color[u] 0: dfs(u) return bad rows, parents, children load(bom_all.csv) for cyc in check_cycle(rows): print(循环引用:, - .join(cyc)) # 孤儿只当过子件、从没当过父件且不在物料主数据里的编码 orphan {c for c in children if c not in parents} print(f疑似孤儿物料 {len(orphan)} 个需和物料主数据比对) for p, c, q in rows: if q 0: print(f零用量: {p} - {c})color用 0/1/2 表示未访问、在栈上、已完成这是标准的深度优先环检测写法比反复遍历邻接表快。孤儿物料那段只是初筛——一个物料完全可以是纯采购件不挂任何子件所以结果必须和物料主数据表做交集不能直接当错误处理。零用量判断放在最后因为它的修复成本最低通常删行或者补数就行。跑完这三类再补一个方向性检查随便挑三个成品正向展开到最底层看总用量和图纸手算是否一致再挑三颗通用件反查看被哪些成品引用数量和预期是否吻合。正向反查各三条覆盖到最深层级和最通用的物料基本能确认数据关系是通的。从那以后我每次交付 BOM 数据给采购之前都强制把这三个校验跑一遍哪怕只改了一行用量——血泪经验是有一回改了一颗螺丝的用量没重跑展开采购按新表备料结果那颗螺丝在另一个半成品里也有引用总量多备了两千多颗。十几秒的脚本换的是不用在下班前重新盘一遍库存。希望帮到你。本文还有配套的精品资源点击获取