单机版BOM管理:物料编码、多层展开与版本校验实践
简介:捷远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", encoding="utf-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, encoding="utf-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, qty=1.0, depth=0, seen=None): """递归展开,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="", encoding="utf-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.1+0.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="", encoding="utf-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 数据给采购之前,都强制把这三个校验跑一遍,哪怕只改了一行用量——血泪经验是有一回改了一颗螺丝的用量没重跑展开,采购按新表备料,结果那颗螺丝在另一个半成品里也有引用,总量多备了两千多颗。十几秒的脚本,换的是不用在下班前重新盘一遍库存。希望帮到你。
本文还有配套的精品资源,点击获取