
简介这份资源围绕IEEE 802.1Q标准框架下的MRPMultiple Registration Protocol多注册协议展开面向网络工程师、嵌入式开发者及工业网络方向的学习者帮助理解环形网络中数据流控制、冗余链路管理与环保护机制的底层实现。压缩包共2个文件包含1个c源文件与1个h头文件整体约6KB其中源文件承载协议函数定义、状态机与事件处理逻辑头文件则提供数据结构、接口声明与常量定义便于对照阅读与二次开发。资源重点覆盖环形拓扑支持、动态注册、环保护、流量控制以及与VLAN优先级兼容等核心特性读者可借此梳理MRP在工业自动化、电力传输、轨道交通等实时性要求较高场景中的工作流程并掌握协议定制与扩展的切入点。目前已有119人学习适合希望深入协议源码细节的开发者参考。1. 从一份 mrp.rar 说起MRP 运算到底在算什么如果你手上正躺着一个叫mrp.rar的压缩包解压后是一套 MRPMaterial Requirements Planning物料需求计划相关的源码或工具那你大概率不是来听物料管理发展史的。你真正想知道的是这套东西能不能跑、跑出来对不对、能不能塞进我现在的生产或进销存流程里。MRP 的核心任务其实很朴素——根据主生产计划、BOM 结构和库存现状倒推出每个物料在什么时间需要采购或生产多少。听起来简单但真正落地时批量策略、提前期、损耗率、安全库存这几项参数一叠加运算结果就会变得非常“玄学”。这份资源适合做生产计划、ERP 二次开发、进销存系统维护的工程师也适合想自己搭一套轻量物料运算逻辑的开发者。下面我按“先看懂它在算什么再动手跑通最后把坑填上”的顺序拆一遍。2. MRP 运算骨架BOM 展开、净需求与提前期倒排2.1 先分清独立需求与相关需求MRP 运算最容易翻车的地方不是代码写错而是需求类型没分清楚。独立需求来自市场预测或客户订单比如“下个月要卖 500 台整机”相关需求是由 BOM 派生出来的比如“500 台整机需要 2000 个螺丝”。很多 MRP 源码包会把这两类需求混在一张表里处理结果就是螺丝也被当成独立需求去预测库存直接爆掉。常见做法是建两张表一张demand_independent存成品或备件的独立需求一张demand_dependent存 BOM 展开后的相关需求。运算时先跑独立需求再逐层展开 BOM把子件需求写入相关需求表。判断依据很简单有父项物料的需求就是相关需求没有父项的就是独立需求。-- 独立需求表只存成品或对外备件 CREATE TABLE demand_independent ( item_code VARCHAR(32) PRIMARY KEY, demand_qty DECIMAL(12,2), due_date DATE ); -- 相关需求表BOM 展开后写入 CREATE TABLE demand_dependent ( item_code VARCHAR(32), parent_code VARCHAR(32), demand_qty DECIMAL(12,2), due_date DATE );item_code是物料编码parent_code记录它是由哪个父项展开出来的due_date是需求日期。相关需求表不设主键因为同一个子件可能被多个父项在同一时间需要后面汇总净需求时再按物料和日期分组。2.2 BOM 逐层展开与低层码BOM 展开不是简单递归因为同一个物料可能出现在不同层级。比如一个通用支架既用在第一层也用在第三层。如果按深度优先展开这个支架会被计算两次库存扣减就乱了。标准做法是给每个物料分配低层码Low Level Code也就是它在所有 BOM 路径中出现的最深层次。展开时按低层码从 0 到 N 逐层处理同一物料只在它最低的那一层被汇总一次。def explode_bom(parent_code, parent_qty, level, demand_rows, bom_map, llc): 按低层码逐层展开 BOM if parent_code not in bom_map: return for child in bom_map[parent_code]: child_code child[child_code] qty_per child[qty_per] scrap_rate child.get(scrap_rate, 0.0) # 考虑损耗率的实际需求量 gross_qty parent_qty * qty_per * (1 scrap_rate) # 只有当前层级等于该物料的低层码时才写入需求 if level llc.get(child_code, level): demand_rows.append({ item_code: child_code, parent_code: parent_code, demand_qty: gross_qty, level: level }) explode_bom(child_code, gross_qty, level 1, demand_rows, bom_map, llc)bom_map是父项到子件列表的映射qty_per是单位用量scrap_rate是损耗率。关键判断是level llc.get(child_code, level)只有到达该物料的最低层时才记录需求避免重复计算。llc需要提前用一次遍历算好不能边展开边算。2.3 净需求计算库存、在途与安全库存毛需求汇总完之后净需求 毛需求 - 可用库存 - 在途量 安全库存。这里有个容易忽略的点可用库存要扣掉已分配量。很多简易 MRP 只拿现存量去减结果已经发给生产线的物料又被算进可用库存净需求偏小最后缺料停线。def calc_net_requirement(gross_qty, on_hand, allocated, in_transit, safety_stock): available on_hand - allocated net gross_qty - available - in_transit safety_stock return max(net, 0)on_hand是现存量allocated是已分配量in_transit是采购在途或生产在制safety_stock是安全库存。max(net, 0)保证净需求不为负负值代表有富余库存不需要补充。这个函数看着简单但参数来源必须和库存系统对齐否则算出来的净需求就是黑匣子。2.4 提前期倒排与计划订单净需求算出数量还不够还得知道什么时候下单。MRP 用提前期倒排计划订单下达日期 需求日期 - 提前期。采购件用采购提前期自制件用生产提前期。如果提前期是变动的比如供应商交期随批量变化那就得按批量分段取提前期不能一刀切。from datetime import timedelta def backward_schedule(require_date, lead_time_days): 按提前期倒排计划订单下达日期 return require_date - timedelta(dayslead_time_days)require_date是净需求对应的需求日期lead_time_days是提前期天数。倒排之后还要做能力校验如果下达日期已经早于今天说明交期不满足需要标记例外信息而不是硬算一个过去的日期。3. 把 mrp.rar 跑起来环境、数据与第一次运算3.1 解压后先看目录结构再动手拿到mrp.rar之后别急着双击运行。先解压到一个纯英文路径下中文路径在某些老式数据库连接组件里会直接报错。解压后重点看三个地方有没有sql或db目录存放建表脚本有没有config或settings文件存放数据库连接信息有没有readme或doc说明运行入口。如果压缩包里只有源码没有建表脚本那大概率需要你自己根据实体类反推表结构这种情况先找model或entity目录。常见做法是先把数据库建好再改配置文件最后跑主运算类。顺序反了会出现表不存在、字段不匹配、编码乱码一连串问题。我一般会先备份一份原始压缩包解压后的目录单独放改坏了直接重来。3.2 数据库准备与基础数据导入MRP 运算依赖几张核心表物料主数据、BOM 结构、库存现存量、独立需求、提前期参数。如果资源包里带了示例数据先用示例数据跑通再换自己的数据。换数据时注意物料编码长度和类型要和 BOM 表里的子件编码一致一个是VARCHAR(20)一个是VARCHAR(32)关联时可能因为尾部空格匹配不上。# 以 MySQL 为例先建库再导入脚本 mysql -u root -p -e CREATE DATABASE mrp_demo DEFAULT CHARSET utf8mb4; mysql -u root -p mrp_demo sql/schema.sql mysql -u root -p mrp_demo sql/sample_data.sql第一条命令建库并指定字符集第二条导入表结构第三条导入示例数据。如果你的 MySQL 版本是 8.0 以上注意utf8mb4和旧版utf8的区别物料名称里有生僻字时用utf8mb4才不会乱码。导入完成后用SHOW TABLES;确认表数量再用SELECT COUNT(*) FROM bom;看 BOM 行数是否和预期一致。3.3 配置参数与第一次运算配置文件里通常有几项关键参数数据库连接、运算日期范围、是否考虑安全库存、是否考虑损耗率、批量策略。第一次跑建议先把安全库存和损耗率关掉只跑最基础的毛需求展开和净需求计算确认 BOM 展开层数和数量对得上再逐项打开高级参数。# mrp.properties 示例 db.urljdbc:mysql://127.0.0.1:3306/mrp_demo?useUnicodetruecharacterEncodingutf8mb4 db.userroot db.passwordyour_password mrp.plan.start2025-01-01 mrp.plan.end2025-03-31 mrp.use.safety.stockfalse mrp.use.scrap.ratefalse mrp.lot.policyfixed mrp.lot.size100mrp.plan.start和mrp.plan.end是运算覆盖的日期范围超出范围的需求不参与运算。mrp.use.safety.stock和mrp.use.scrap.rate先设为false减少变量。mrp.lot.policy是批量策略fixed表示固定批量mrp.lot.size是批量大小。第一次跑完重点核对三件事成品需求数量是否等于独立需求表汇总第一层子件数量是否等于成品数量乘以单位用量计划订单下达日期是否等于需求日期减去提前期。3.4 运算结果核对与日志排查跑完之后不要只看程序有没有报错要看结果表里的数据是否符合业务常识。常见核对方法是从成品往下抽一条 BOM 路径手工算一遍毛需求和净需求再和结果表对比。如果数量对不上先查 BOM 表里有没有循环引用再查低层码是否算错最后查库存可用量是否扣除了已分配量。-- 核对某成品的第一层子件毛需求 SELECT d.item_code, d.demand_qty, b.qty_per, d.demand_qty * b.qty_per AS expected_qty FROM demand_dependent d JOIN bom b ON d.item_code b.child_code WHERE d.parent_code FG-001;这条 SQL 把相关需求表和 BOM 表关联算出理论毛需求再和结果表里的计划订单数量对比。如果expected_qty和结果不一致优先检查损耗率是否被意外启用或者批量策略是否把数量向上取整了。日志里重点看WARN和ERROR级别尤其是“物料编码不存在”“BOM 循环引用”“提前期为负”这几类信息它们往往指向基础数据问题而不是程序 bug。4. 避坑与排查MRP 运算里最容易翻车的五件事4.1 现象BOM 展开数量翻倍原因低层码未提前计算解决先算 LLC 再展开这是血泪经验里排第一的坑。程序跑完发现某个通用件的需求是手工计算的两倍查来查去发现它在 BOM 里出现在两个层级展开时被加了两次。根因是低层码没有提前算好或者算的时候只遍历了一条路径。解决方法是单独写一个calc_llc函数遍历所有 BOM 路径取最大深度展开时严格按低层码逐层处理同一物料只在最低层写入需求。4.2 现象净需求偏小导致缺料原因可用库存没扣已分配量解决库存口径对齐库存表里有一个on_hand字段但仓库已经把一部分物料分配给生产线了这部分在allocated字段里。如果净需求只减on_hand算出来的可用库存虚高净需求偏小采购计划下少了上线就缺料。解决方法是净需求计算统一用on_hand - allocated并且确认allocated字段的更新时机和仓库发料动作同步。4.3 现象计划订单下达日期跑到过去原因提前期大于需求日期减今天解决标记例外而不是硬算倒排日期时如果提前期是 30 天而需求日期是 10 天后算出来的下达日期就是 20 天前。程序如果直接写入这个日期采购模块会生成一张过期订单。正确做法是判断plan_start_date today时标记为例外信息提示计划员要么催料要么调整需求日期而不是硬算一个没有意义的过去日期。4.4 现象批量策略导致库存积压原因固定批量远大于净需求解决按需调整批量或改用动态批量固定批量 100净需求 12计划订单就下 100多出来的 88 变成库存。短期看是库存积压长期看是资金占用。解决方法是根据物料 ABC 分类设置批量策略A 类高价值物料用按需批量B 类用固定批量但批量值调小C 类低价值物料才用较大的固定批量。批量策略没有万能公式得结合采购周期和仓储成本调。4.5 现象运算结果每次跑都不一样原因运算日期范围或数据快照不一致解决固定快照再运算同一个计划日期上午跑和下午跑结果不同往往是因为运算期间有人改了库存或 BOM。MRP 运算应该基于一个数据快照而不是实时表。常见做法是运算前把相关表数据复制到临时快照表运算全程读快照跑完再对比差异。这样至少保证同一次运算内部数据一致排查问题时也有据可查。5. 进阶技巧用差异对比验证 MRP 结果是否可信5.1 建立基线运算与差异表MRP 结果对不对单看一次运算很难判断。我一般会先跑一次基线运算把结果存到mrp_result_baseline表然后改动一个参数再跑一次把新结果存到mrp_result_current用差异对比看哪些物料的需求变了、变了多少。这个方法比逐条手工核对快得多也更容易发现参数之间的相互影响。-- 差异对比找出两次运算结果不一致的物料 SELECT COALESCE(b.item_code, c.item_code) AS item_code, b.plan_qty AS baseline_qty, c.plan_qty AS current_qty, c.plan_qty - b.plan_qty AS diff_qty FROM mrp_result_baseline b LEFT JOIN mrp_result_current c ON b.item_code c.item_code WHERE b.plan_qty c.plan_qty OR c.item_code IS NULL;COALESCE处理一边有一边没有的情况diff_qty是差异数量。如果只改了一个参数差异却扩散到几十个物料说明这个参数影响面很大需要重点确认。比如打开损耗率之后所有含损耗子件的父项需求都会变这是正常的但如果某个不相关物料也变了那就要查 BOM 关联是不是写错了。5.2 用关键路径抽查代替全量核对全量核对几千行 MRP 结果不现实我习惯抽三条关键路径一条最长 BOM 路径、一条通用件出现次数最多的路径、一条提前期最长的采购件路径。最长路径验证展开层数通用件路径验证低层码长提前期路径验证倒排日期。三条路径手工算一遍和结果表对上基本就能判断运算逻辑有没有大问题。抽查路径验证重点手工计算项最长 BOM 路径展开层数与数量逐层用量乘积通用件路径低层码与汇总各层需求累加长提前期路径倒排日期需求日期减提前期这张表可以照着建一个检查清单每次改完参数跑完运算都过一遍。三条路径都对了再去看全量结果心里才有底。5.3 把例外信息当成入口而不是噪音MRP 跑完会生成一堆例外信息提前期不足、物料编码不存在、BOM 循环引用、库存为负。很多人嫌烦直接忽略但这些例外信息恰恰是基础数据质量的入口。我一般会按例外类型分组统计数量最多的那类优先处理。比如“物料编码不存在”排第一说明 BOM 表里有废弃编码没清理“提前期不足”排第一说明主计划排得太紧或者供应商交期没维护准确。从那以后我每次跑 MRP 都强制走一遍“基线运算 → 差异对比 → 三条路径抽查 → 例外信息分组”这个流程不再只看程序有没有报错。这套习惯帮我省掉了大量上线后缺料停线的后悔药。希望帮到你。本文还有配套的精品资源点击获取