简介:这是一份面向生产管理、供应链及工业工程方向学习者的物料需求计划(MRP)教学课件,适合需要理解MRP原理、掌握库存控制与生产计划逻辑的高校学生及企业从业者。资源包内含1个PPT文件,压缩包约458KB,以幻灯片形式系统梳理MRP知识框架。课件从传统库存管理的盲目性、高库存与低服务水平矛盾切入,逐步展开MRP基本原理、三大关键输入(主生产计划MPS、物料清单BOM、库存记录)及逻辑流程图,并详细讲解粗需求量、预计入库量、现有库存量、净需求量等计算项目与计划订货量、计划投入量的推导过程。内容还涵盖BOM分层编码方法、方桌结构案例、电表零件需求计划案例及MRP逻辑处理流程,帮助读者建立从市场预测到采购生产指令的完整认知。目前已有665人学习,适合作为课程复习或企业内训的参考材料。
1. 物料需求计划(MRP):为什么你的生产计划总在救火
上周跟一位做电子组装的朋友吃饭,他说工厂最魔幻的场景是:仓库里堆着够用三个月的电容,产线却因为缺一颗两毛钱的连接器停了半天。采购追着供应商骂,供应商说上周才补过货,一查系统,原来那颗料被另一个订单的BOM吃掉了,而那个订单的交期还在两个月后。这不是人的问题,是物料需求计划(MRP)没跑对。MRP要解决的核心就一句话:在正确的时间,把正确数量的正确物料,推到正确的位置。它上承主生产计划(MPS),下接采购与车间作业,中间靠BOM、库存、提前期三根柱子撑着。适合谁看?如果你正在用Excel排料、被缺料和呆滞两头夹击,或者刚接手一套ERP但MRP运算结果总对不上账,这篇就是写给你的。我会从计算逻辑讲到跑批参数,再到那些只有踩过才知道的坑,尽量让你看完能自己搭一版可复现的MRP运算。
2. MRP的净需求计算:从毛需求到计划订单的完整推演
MRP不是玄学,它是一套确定性的时间轴推演。很多人跑出来的结果不对,不是软件坏了,是输入没对齐。这一章把计算链路拆开,每一步都给出可验证的算法和参数含义。
2.1 毛需求、净需求、计划订单接收的递推关系
MRP的核心递推发生在每个物料的时间桶(bucket)上。毛需求来自上层物料的计划订单投放,加上独立需求(备件、样品)。净需求 = 毛需求 + 安全库存 - 预计入库 - 可用库存。如果净需求大于零,就按批量规则生成计划订单接收,再按提前期往前推,变成计划订单投放,成为下一层物料的毛需求。
这个递推里最容易翻车的是「预计入库」的口径。已下达采购订单、已下达生产订单、在途调拨,这三类都算预计入库,但很多系统默认只抓采购在途,把车间已下达的工单漏了,结果净需求虚高,采购被重复建议。我一般会在跑批前先对一遍预计入库的取数范围,确认它覆盖了所有已确认的供应来源。
下面是一段用Python实现的单层净需求递推,逻辑清晰,方便你对照自己系统的结果排查。
# 单层MRP净需求递推,按时间桶逐期计算 def mrp_netting(gross_req, sched_receipts, on_hand, safety_stock, lot_size_rule, lead_time, periods): """ gross_req: 每期毛需求 list sched_receipts: 每期预计入库 list on_hand: 期初可用库存 safety_stock: 安全库存 lot_size_rule: 批量规则,'LFL'按需或固定倍数 lead_time: 提前期,单位期数 periods: 总期数 """ projected_available = on_hand planned_order_receipt = [0] * periods planned_order_release = [0] * periods for t in range(periods): # 预计可用 = 上期可用 + 预计入库 - 毛需求 projected_available = (projected_available + sched_receipts[t] - gross_req[t]) # 净需求:低于安全库存的部分需要补 net_req = safety_stock - projected_available if net_req > 0: if lot_size_rule == 'LFL': order_qty = net_req else: # 固定批量,向上取整到倍数 multiple = lot_size_rule order_qty = ((net_req + multiple - 1) // multiple) * multiple planned_order_receipt[t] = order_qty projected_available += order_qty # 按提前期前推投放期 release_period = t - lead_time if release_period >= 0: planned_order_release[release_period] += order_qty return planned_order_receipt, planned_order_release这段代码里,projected_available是逐期滚动的预计可用量,net_req为负说明库存够用,不生成订单。批量规则用LFL(Lot-for-Lot,按需批量)时,订多少补多少,库存最省但订单最碎;用固定倍数时,订单整齐但可能产生余量。提前期lead_time决定投放期,如果算出来是负数,说明交期已经来不及,系统一般会抛异常或建议提前,这个信号必须接住,不能忽略。
参数上,安全库存不是拍脑袋定的,它跟需求波动和供应波动挂钩。需求标准差大、供应商交期不稳,安全库存就得抬。但安全库存设太高,MRP会频繁触发补货,呆滞风险跟着涨。我一般建议先按历史消耗的变异系数估一版,跑三个月再调。
2.2 低层码与BOM展开顺序:为什么运算顺序错了全盘皆错
MRP展开BOM时,必须按低层码(low-level code)从低到高运算。低层码是某个物料在所有BOM中出现在的最低层级。比如一颗电阻既用在成品A(第1层)又用在半成品B(第2层),那它的低层码就是2。运算时必须等第1层和第2层的需求都汇总完,才能算这颗电阻的净需求。如果顺序反了,先算电阻再算半成品B,B产生的电阻需求就漏了,结果就是缺料。
常见做法是跑批前先重算低层码,尤其是工程变更(ECN)之后。BOM结构一改,低层码可能变,不重算就会用旧的展开顺序。我见过一次因为新增了一个共用件,低层码从3变成2,但系统没刷新,导致该物料的需求少算了一大截,产线停了两天。
低层码的计算可以用一遍BFS从成品往下遍历,记录每个物料出现的最大深度。下面是一个简化实现。
from collections import deque def compute_low_level_code(bom_dict, finished_goods): """ bom_dict: {父件: [子件列表]} finished_goods: 成品列表 返回 {物料: 低层码} """ llc = {} # BFS逐层展开,记录每个物料出现的最大层级 queue = deque([(fg, 0) for fg in finished_goods]) while queue: item, level = queue.popleft() # 低层码取出现过的最大层级 if item not in llc or level > llc[item]: llc[item] = level for child in bom_dict.get(item, []): queue.append((child, level + 1)) return llcbom_dict是父件到子件的映射,finished_goods是独立需求的成品。BFS保证按层级推进,llc记录每个物料出现过的最大深度。实际系统里还要处理环状BOM(自己引用自己),得加访问标记防死循环。算完低层码后,按llc升序排列物料,再逐个跑净需求,才能保证上层需求全部汇总到位。
2.3 提前期偏移与计划订单投放:时间轴对齐的实操
提前期偏移是MRP里最容易被低估的环节。采购提前期、生产提前期、检验提前期,每一段都要算进去。很多系统只填一个采购提前期,把收货检验、上架的时间漏了,结果物料到了但不能用,产线照样停。
我一般会把提前期拆成三段:下单到供应商发货、运输在途、到货检验入库。三段相加才是真正的可用提前期。生产件同理,要拆成备料、加工、检验。这些数据不一定要很精确,但必须完整,漏一段就是坑。
计划订单投放的日期 = 计划订单接收日期 - 总提前期。如果投放日期落在过去,说明已经来不及,系统应该给出例外信息。跑MRP时一定要看例外消息,不能只看计划订单列表。例外消息里的「提前期不足」「交期已过」才是真正要处理的。
时间桶的粒度也要注意。按天跑和按周跑,结果可能差很多。交期紧的行业建议按天,甚至按班次。桶太粗,提前期偏移会失真,该今天下的单被推到下周,缺料就来了。
3. 用Python搭一版可复现的MRP运算:数据结构与跑批流程
理解了计算逻辑,下一步是把它跑起来。这一章给出一套最小可用的MRP运算框架,包含数据结构定义、BOM展开、逐层净需求和例外信息输出。你可以直接拿去改,接自己的数据源。
3.1 物料主数据与BOM的数据结构设计
MRP的输入就三块:物料主数据、BOM、库存与在途。物料主数据里关键字段是提前期、安全库存、批量规则、低层码。BOM是父子关系加用量。库存分现有量和预计入库。
用Python的dataclass定义,清晰且好扩展。
from dataclasses import dataclass, field from typing import List, Dict @dataclass class Material: code: str lead_time: int # 总提前期,单位天 safety_stock: float lot_size_rule: str # 'LFL' 或固定倍数 low_level_code: int = 0 @dataclass class BomLine: parent: str child: str qty_per: float # 单位用量 @dataclass class Inventory: code: str on_hand: float scheduled_receipts: Dict[int, float] = field(default_factory=dict) # 键为期数,值为该期预计入库量Material里的lead_time是总提前期,建议在数据准备阶段就把采购、运输、检验加好,不要留到运算时再拼。BomLine的qty_per是单位用量,注意损耗率要提前乘进去,比如实际用量是1.02,就填1.02,不要等到运算时再算。Inventory的scheduled_receipts用字典按期间存,方便按桶取数。
这套结构的好处是跟数据库表能一一对应,物料主数据一张表,BOM一张表,库存一张表,跑批时全量加载到内存,运算快且好调试。
3.2 逐层展开与净需求运算的跑批脚本
有了数据结构,跑批流程分四步:算低层码、按低层码排序、逐层算净需求、汇总计划订单。下面是一个完整的最小实现。
def run_mrp(materials: Dict[str, Material], bom_lines: List[BomLine], inventory: Dict[str, Inventory], independent_demand: Dict[str, Dict[int, float]], periods: int): """ materials: 物料主数据字典 bom_lines: BOM行列表 inventory: 库存字典 independent_demand: {物料: {期数: 需求量}} periods: 总期数 """ # 构建BOM父子映射 bom_dict = {} for line in bom_lines: bom_dict.setdefault(line.parent, []).append(line) # 第一步:算低层码 finished = list(independent_demand.keys()) llc = compute_low_level_code( {p: [l.child for l in lines] for p, lines in bom_dict.items()}, finished ) for code, level in llc.items(): if code in materials: materials[code].low_level_code = level # 第二步:按低层码升序排列 sorted_codes = sorted(llc.keys(), key=lambda c: llc[c]) # 第三步:逐层算净需求 gross_req = {code: {} for code in sorted_codes} planned_release = {code: {} for code in sorted_codes} for code in sorted_codes: mat = materials[code] inv = inventory.get(code, Inventory(code, 0)) # 汇总毛需求:独立需求 + 上层投放 req = {} for t, qty in independent_demand.get(code, {}).items(): req[t] = req.get(t, 0) + qty for t, qty in gross_req.get(code, {}).items(): req[t] = req.get(t, 0) + qty gross_list = [req.get(t, 0) for t in range(periods)] receipt_list = [inv.scheduled_receipts.get(t, 0) for t in range(periods)] por, pol = mrp_netting( gross_list, receipt_list, inv.on_hand, mat.safety_stock, mat.lot_size_rule, mat.lead_time, periods ) planned_release[code] = {t: q for t, q in enumerate(pol) if q > 0} # 把本层投放传递给子件,作为子件毛需求 for line in bom_dict.get(code, []): child_req = gross_req.setdefault(line.child, {}) for t, qty in planned_release[code].items(): child_req[t] = child_req.get(t, 0) + qty * line.qty_per return planned_release跑批的核心在最后那个循环:算完当前物料的计划订单投放后,立刻按BOM用量乘给子件,累加到子件的毛需求里。因为已经按低层码排序,子件一定在父件之后被处理,需求不会漏。independent_demand是成品的独立需求,备件和样品也放这里。periods是总期数,按天跑就填天数。
这段代码没处理环状BOM和负库存,实际用的时候要加校验。另外mrp_netting里提前期为负的情况要单独抛出来,作为例外信息。
3.3 例外信息与计划订单的验证方法
跑完MRP,别急着把计划订单推给采购。先看例外信息。例外信息一般分几类:提前期不足、交期已过、负库存、BOM环状引用、无供应商。每一类都要有处理动作。
验证计划订单是否合理,我常用两个办法。一是反查:随便挑一个计划订单,手工从成品需求往下推,看能不能对上。二是总量校验:所有物料的计划订单投放,按BOM展开后的总需求,应该跟独立需求的总量在数量级上一致,差太多说明某层漏了或重了。
下面是一个简单的例外信息检查。
def check_exceptions(planned_release, materials, periods): exceptions = [] for code, releases in planned_release.items(): mat = materials[code] for t, qty in releases.items(): if t < 0: exceptions.append({ 'type': '提前期不足', 'material': code, 'period': t, 'qty': qty, 'msg': f'{code} 需在第{t}期投放,已过期' }) if t == 0 and mat.lead_time > 0: exceptions.append({ 'type': '交期紧张', 'material': code, 'period': t, 'qty': qty, 'msg': f'{code} 需立即投放,提前期{mat.lead_time}天' }) return exceptionsplanned_release里键为负数的,说明投放期已经过了,必须人工干预。键为0的,说明今天就得下单,也是紧急信号。这些例外信息要每天跑完MRP后第一时间看,不能等。
验证时还要注意单位一致。BOM用量、库存、需求,单位必须统一。我见过因为BOM用「千个」而库存用「个」,导致需求放大一千倍的翻车现场。跑批前先做单位校验,能省很多事。
4. MRP跑批的避坑清单:那些让计划对不上账的细节
MRP的坑大多不在算法,在数据和参数。这一章列几条我踩过的,每条按现象、原因、解决写,你对照排查。
4.1 预计入库口径不一致导致净需求虚高
现象:MRP建议的采购量比实际需要多出一大截,仓库明明有货,系统还在催采购。
原因:预计入库只取了采购在途,漏了已下达的生产工单和调拨在途。净需求 = 毛需求 + 安全库存 - 预计入库 - 可用库存,预计入库少算,净需求就虚高。
解决:跑批前核对预计入库的取数范围,确认覆盖采购在途、生产在途、调拨在途三类。最好在数据准备层就把它们合并成一张供应表,MRP只读这张表,避免口径分散。
4.2 低层码未刷新导致共用件需求漏算
现象:某个共用件总是缺料,但它的独立需求看起来不大,查BOM发现它被多个半成品使用。
原因:工程变更后BOM结构变了,低层码没重算,MRP按旧的展开顺序运算,共用件在低层级的需求没汇总进来。
解决:每次ECN生效后强制重算低层码,跑MRP前加一道校验,对比低层码是否有变化。有变化就重新排序再跑。
4.3 提前期只填采购段导致到货即缺料
现象:采购说货已经到了,产线还是停,一问还在检验。
原因:提前期只填了下单到供应商发货,运输和检验没算进去。计划订单投放日期偏晚,物料到了但不能用。
解决:提前期拆成下单、运输、检验三段,相加作为总提前期。检验时间不稳定的,按历史均值加安全余量。
4.4 批量规则选错导致库存越跑越高
现象:MRP跑着跑着,呆滞库存越来越多,但缺料也没少。
原因:批量规则用了固定大批量,需求小的时候也按大批量补,余量累积成呆滞。或者安全库存设太高,频繁触发补货。
解决:需求平稳的物料用LFL,需求碎但供应有最小起订量的,用固定倍数并定期复核倍数是否合理。安全库存按变异系数估,每季度调一次。
4.5 时间桶粒度太粗导致交期失真
现象:按周跑MRP,计划订单投放日期总是差几天,紧急单来不及。
原因:时间桶按周,提前期偏移以周为单位,精度不够。交期紧的物料被粗桶抹平了。
解决:交期紧的行业按天跑,甚至按班次。桶粒度至少要比最短提前期细一个数量级。
5. MRP运算结果的验证与调优:从能跑到跑得准
跑通MRP只是第一步,跑得准才是目标。这一章讲怎么验证结果、怎么调参数,以及一个我常用的对账技巧。
5.1 用反查法验证单条计划订单
挑一条计划订单,从成品需求手工往下推。比如成品A第10期需要100台,BOM里用到半成品B两件、外壳一件。半成品B又用到某芯片三颗。手工算一遍芯片在第10期的毛需求,再减去库存和在途,看净需求跟MRP结果是否一致。不一致就顺着BOM一层层查,看哪一层的用量、损耗率、提前期对不上。这个方法笨但有效,新上线MRP时我一般会抽十条不同层级的订单反查。
5.2 关键参数的敏感度测试
MRP结果对几个参数特别敏感:安全库存、批量倍数、提前期。调参前先做敏感度测试,固定其他参数,只动一个,看计划订单总量和缺料率怎么变。
| 参数 | 测试范围 | 观察指标 | 调优方向 |
|---|---|---|---|
| 安全库存 | 0.5倍到2倍历史均值 | 缺料次数、呆滞金额 | 缺料多则抬,呆滞多则降 |
| 批量倍数 | 最小起订量的1到3倍 | 订单数、库存周转 | 订单碎则抬,库存高则降 |
| 提前期 | 历史均值上下20% | 例外信息数量 | 例外多则复核实际提前期 |
测试时一次只动一个参数,跑完对比计划订单列表和例外信息。敏感度高的参数要重点管,敏感度低的可以粗放一点。
5.3 一个对账习惯:总量守恒校验
我每次跑完MRP,会做一个总量守恒校验:把所有成品的独立需求按BOM展开,算出每种物料的毛需求总量,再跟MRP输出的毛需求总量对比。理论上应该一致,差太多说明某层漏算或重算。这个校验能快速发现BOM错误、低层码错误、单位错误。具体做法是写一个独立的展开脚本,不依赖MRP的运算逻辑,纯按BOM递归展开,然后跟MRP结果对。两个独立实现的结果一致,才敢把计划订单推出去。
这个习惯帮我抓过好几次问题,有一次是BOM里某个物料的损耗率被误填成10而不是1.1,展开后需求差了近十倍,总量校验直接暴露了。MRP这东西,算法不难,难在数据和参数的一致性。把校验做在前面,比事后救火省心得多。希望帮到你。
本文还有配套的精品资源,点击获取