一、概述
ERPNext 的存货模块实现了完整的库存管理功能,包括采购入库、销售出库、库存调拨、生产领料、库存盘点等业务流程。核心设计理念是通过 Stock Ledger Entry(库存流水)记录所有库存变动,Bin 表缓存实时库存余额,支持多种估值方法和序列号/批次管理。
模块定位
┌─────────────────────────────────────────────────────────────┐│ ERPNext 存货模块 │├─────────────────────────────────────────────────────────────┤│ 基础数据层:Item(物料)、Warehouse(仓库)、UOM(计量单位) ││ 库存流水层:Stock Ledger Entry(库存变动记录) ││ 库存余额层:Bin(实时库存快照)、Stock Closing Balance ││ 业务单据层:Purchase Receipt、Delivery Note、Stock Entry ││ 特殊管理层:Serial No(序列号)、Batch(批次) ││ 盘点层:Stock Reconciliation(库存盘点) ││ 生产集成层:Work Order、Material Request、BOM │└─────────────────────────────────────────────────────────────┘
二、核心数据模型
2.1 Item(物料)
文件:item.py
valuation_method: DF.Literal[”FIFO”,# 先进先出”Moving Average”,# 移动平均”Standard”,# 标准成本”Last Purchase Rate”,# 最后采购价”Specific”,# 个别认定法]
2.2 Warehouse(仓库)
文件:warehouse.py
Locations├── 北京仓库│ ├── 原材料仓│ └── 成品仓└── 上海仓库├── 原材料仓└── 成品仓
2.3 Stock Ledger Entry(库存流水)
文件:stock_ledger_entry.py
def make_sl_entry(args):”””创建库存流水记录”””sle = frappe.new_doc(”Stock Ledger Entry”)sle.item_code = args.get(”item_code”)sle.warehouse = args.get(”warehouse”)sle.actual_qty = args.get(”actual_qty”)sle.valuation_rate = args.get(”valuation_rate”)sle.voucher_type = args.get(”voucher_type”)sle.voucher_no = args.get(”voucher_no”)# 计算交易后数量和价值sle.qty_after_transaction = calculate_qty_after_transaction(sle)sle.stock_value = calculate_stock_value(sle)sle.stock_value_difference = calculate_value_difference(sle)sle.insert()sle.submit()# 更新 Bin 表update_bin(sle)
2.4 Bin(库存余额快照)
文件:bin.py
def set_projected_qty(self):self.projected_qty = (flt(self.actual_qty)+ flt(self.ordered_qty)+ flt(self.indented_qty)+ flt(self.planned_qty)- flt(self.reserved_qty)- flt(self.reserved_qty_for_production)- flt(self.reserved_qty_for_sub_contract)- flt(self.reserved_qty_for_production_plan))
┌─────────────────────────────────────────────────────────┐│ Stock Ledger Entry(流水) ││ - 记录每一笔库存变动 ││ - 历史可追溯 ││ - 数据量大 │├─────────────────────────────────────────────────────────┤│ Bin(快照) ││ - 当前时刻的库存余额 ││ - 实时更新 ││ - 查询性能好 │├─────────────────────────────────────────────────────────┤│ 关系: ││ - SLE 是 Bin 的数据源 ││ - 每次 SLE 创建后更新 Bin ││ - Bin 可以通过 SLE 重新计算(recalculate_qty) │└─────────────────────────────────────────────────────────┘
2.5 Serial No(序列号)
文件:serial_no.py
# 入库时创建序列号serial_no = frappe.new_doc(”Serial No”)serial_no.item_code = ”手机-A1”serial_no.name = ”SN001”serial_no.warehouse = ”成品仓”serial_no.purchase_rate = 2000serial_no.insert()# 出库时验证序列号# 确保序列号存在且在指定仓库validate_serial_nos(serial_nos, item_code, warehouse)
2.6 Batch(批次)
文件:batch.py
# 创建批次batch = frappe.new_doc(”Batch”)batch.item_code = ”原材料-A”batch.name = ”B20260701”batch.batch_qty = 1000batch.manufacturing_date = ”2026-07-01”batch.expiry_date = ”2027-07-01”batch.insert()# 入库时关联批次# 出库时选择批次(FIFO/LIFO)
三、核心业务流程
3.1 采购入库流程
Purchase Order → Purchase Receipt(PO) (PR)1. 创建 Purchase Receipt├── 选择供应商和物料├── 指定入库仓库├── 输入数量和单价2. 提交 Purchase Receipt├── 创建 Stock Ledger Entry:│ ├── actual_qty = +qty(入库)│ ├── valuation_rate = incoming_rate│ └── stock_value_difference = qty * valuation_rate├── 更新 Bin 表:│ ├── actual_qty += qty│ ├── stock_value += value│ └── valuation_rate = 新的平均单价├── 创建 GL Entry(如果启用永续盘存):│ ├── 借: Stock In Hand(库存科目)│ └── 贷: Stock Received But Not Billed(暂估负债)└── 更新序列号/批次
3.2 销售出库流程
Sales Order → Delivery Note(SO) (DN)1. 创建 Delivery Note├── 选择客户和物料├── 指定出库仓库├── 输入数量2. 提交 Delivery Note├── 创建 Stock Ledger Entry:│ ├── actual_qty = -qty(出库)│ ├── valuation_rate = 当前库存单价│ └── stock_value_difference = -qty * valuation_rate├── 更新 Bin 表:│ ├── actual_qty -= qty│ ├── stock_value -= value│ └── valuation_rate = 新的平均单价├── 创建 GL Entry(如果启用永续盘存):│ ├── 借: Cost of Goods Sold(或 Stock Delivered But Not Billed)│ └── 贷: Stock In Hand(库存科目)└── 更新序列号/批次状态(Sold/Delivered)
3.3 Stock Entry(库存变动)
文件:stock_entry.py
# 领料单# 业务场景:生产领料、部门领用# Stock Entry 创建流程:1. 选择业务类型 = Material Issue2. 选择源仓库(from_warehouse)3. 添加物料行(item_code, qty)4. 指定费用科目(expense_account)和成本中心(cost_center)# 提交时:- 创建 SLE(出库)- 更新 Bin(库存减少)- 创建 GL Entry:├── 借: expense_account(费用科目)└── 贷: Stock In Hand(库存科目)
# 调拨单# 业务场景:仓库间转移# Stock Entry 创建流程:1. 选择业务类型 = Material Transfer2. 选择源仓库和目标仓库3. 添加物料行# 提交时:- 创建 SLE(源仓库出库,目标仓库入库)- 更新两个仓库的 Bin- 创建 GL Entry(如果两个仓库关联不同科目):├── 借: 目标仓库库存科目└── 贷: 源仓库库存科目
# 生产完工单# 业务场景:从原材料生产成品# Stock Entry 创建流程:1. 选择业务类型 = Manufacture2. 关联 Work Order(可选)3. 从 BOM 获取原材料清单4. 指定成品和数量5. 选择原材料仓库和成品仓库# 提交时:- 创建 SLE(原材料出库,成品入库)- 更新 Bin- 创建 GL Entry:├── 借: Stock In Hand(成品)├── 贷: Stock In Hand(原材料)└── 贷: WIP(在制品差异)
3.4 库存盘点流程
文件:stock_reconciliation.py
1. 创建 Stock Reconciliation├── 选择盘点日期和仓库├── 添加物料行├── 系统自动获取当前库存(current_qty)├── 输入实际盘点数量(qty)和单价(valuation_rate)2. 提交盘点单├── 计算差异:│ ├── difference_qty = qty - current_qty│ └── difference_amount = difference_qty * valuation_rate├── 创建 SLE(冲销当前库存,录入新库存):│ ├── SLE1: actual_qty = -current_qty(冲销)│ └── SLE2: actual_qty = qty(新库存)├── 更新 Bin 表├── 创建 GL Entry:│ ├── 如果盘亏:借 Stock Adjustment,贷 Stock In Hand│ └── 如果盘盈:借 Stock In Hand,贷 Stock Adjustment└── 重算后续 SLE 和 GLE(repost_future_sle_and_gle)
def set_standard_cost_from_reconciliation(self):”””如果物料使用标准成本法,且输入的估值单价与当前标准成本不同,自动创建新的 Item Standard Cost 记录”””for item in self.items:if is_standard_cost_item(item.item_code, self.company) and flt(item.valuation_rate):isc = frappe.new_doc(”Item Standard Cost”)isc.item_code = item.item_codeisc.effective_date = self.posting_dateisc.standard_rate = item.valuation_rateisc.revaluation_entry = self.nameisc.insert()isc.submit()
3.5 序列号/批次处理
# 序列号/批次 Bundle 用于管理出库时的序列号/批次分配# 创建 Bundle(出库时):bundle = frappe.new_doc(”Serial and Batch Bundle”)bundle.item_code = ”手机-A1”bundle.warehouse = ”成品仓”bundle.type_of_transaction = ”Outward”bundle.append(”entries”, {”serial_no”: ”SN001”,”qty”: -1,”warehouse”: ”成品仓”,})bundle.insert()# 使用 Bundle 创建 SLE:sle.serial_and_batch_bundle = bundle.name
四、估值方法实现
4.1 Moving Average(移动平均)
文件:stock_ledger.py
# 新平均单价 = (原有库存价值 + 新入库价值) / (原有库存数量 + 新入库数量)# 即:new_valuation_rate = (old_stock_value + incoming_value) / (old_qty + incoming_qty)# 代码实现:def get_valuation_rate(args):”””计算移动平均单价”””item_code = args.get(”item_code”)warehouse = args.get(”warehouse”)# 获取当前库存bin = frappe.get_doc(”Bin”, {”item_code”: item_code, ”warehouse”: warehouse})old_qty = flt(bin.actual_qty)old_value = flt(bin.stock_value)incoming_qty = flt(args.get(”actual_qty”))incoming_value = flt(args.get(”incoming_value”))if old_qty + incoming_qty == 0:return 0return (old_value + incoming_value) / (old_qty + incoming_qty)
# 出库时使用当前的 valuation_rate# 不改变平均单价# 只减少库存数量和价值# SLE 记录:sle.actual_qty = -qtysle.valuation_rate = bin.valuation_ratesle.stock_value_difference = -qty * bin.valuation_rate
4.2 FIFO(先进先出)
# 使用 stock_queue 字段记录批次队列# 入库时:# 将新批次加入队列stock_queue = [{”qty”: 100, ”rate”: 10},{”qty”: 200, ”rate”: 12},]# 出库时:# 从队列头部开始消耗# 先消耗最早入库的批次# 示例:出库 150 件# 从第一批消耗 100 件(rate=10),价值 1000# 从第二批消耗 50 件(rate=12),价值 600# 总价值 = 1600# 平均出库单价 = 10.67
4.3 Standard Cost(标准成本)
# 使用 Item Standard Cost 记录标准成本class ItemStandardCost:item_code: DF.Linkcompany: DF.Linkeffective_date: DF.Datestandard_rate: DF.Currencyrevaluation_entry: DF.Link# 出库时使用标准成本# 不考虑实际入库成本# 差异计入差异科目# 会计分录:# 借: Cost of Goods Sold(标准成本)# 贷: Stock In Hand(标准成本)## 如果实际成本 != 标准成本:# 借/贷: Purchase Price Variance(差异科目)
五、库存与会计集成
5.1 永续盘存制
# 启用永续盘存后,所有库存变动自动生成 GL Entry# 启用配置:# Company DocType → enable_perpetual_inventory = Yes# 触发点:# - Purchase Receipt(采购入库)# - Delivery Note(销售出库)# - Stock Entry(库存调整)# - Stock Reconciliation(库存盘点)
5.2 GL Entry 生成规则
# Purchase Receipt# 借: Stock In Hand(仓库关联科目) xxx# 贷: Stock Received But Not Billed(公司配置科目) xxx
# Delivery Note# 如果未启用发出商品:# 借: Cost of Goods Sold(物料配置科目) xxx# 贷: Stock In Hand(仓库关联科目) xxx# 如果启用发出商品:# 借: Stock Delivered But Not Billed(公司配置科目) xxx# 贷: Stock In Hand(仓库关联科目) xxx# 开票时(Sales Invoice):# 借: Cost of Goods Sold# 贷: Stock Delivered But Not Billed
# Stock Entry (Material Issue)# 借: expense_account(物料/物料组/公司配置) xxx# 贷: Stock In Hand(仓库关联科目) xxx
# Stock Entry (Material Transfer)# 如果两个仓库关联不同科目:# 借: 目标仓库库存科目 xxx# 贷: 源仓库库存科目 xxx# 如果两个仓库关联同一科目:# 不生成 GL Entry(只更新 SLE 和 Bin)
# Stock Reconciliation# 盘亏:# 借: Stock Adjustment(公司配置科目) xxx# 贷: Stock In Hand(仓库关联科目) xxx# 盘盈:# 借: Stock In Hand(仓库关联科目) xxx# 贷: Stock Adjustment(公司配置科目) xxx
六、库存查询与报表
6.1 库存查询
# 获取库存余额from erpnext.stock.utils import get_stock_balancestock_balance = get_stock_balance(item_code=”原材料-A”,warehouse=”原材料仓”,posting_date=”2026-07-10”)# 获取库存价值from erpnext.stock.utils import get_stock_value_onstock_value = get_stock_value_on(item_code=”原材料-A”,warehouse=”原材料仓”,posting_date=”2026-07-10”)# 获取可用库存from erpnext.stock.stock_balance import get_bin_detailsbin_details = get_bin_details(item_code=”原材料-A”,warehouse=”原材料仓”)# 返回:actual_qty, projected_qty, reserved_qty 等
6.2 主要报表
七、与其他模块的集成
7.1 采购模块
# Purchase Order → Purchase Receipt# 三单匹配:PO → PR → PI# 集成点:# - PR 可以从 PO 创建(复制物料和数量)# - PR 更新 PO 的已收货数量# - PR 创建 SLE(库存增加)# - PR 创建 GL Entry(暂估负债)
7.2 销售模块
# Sales Order → Delivery Note# 三单匹配:SO → DN → SI# 集成点:# - DN 可以从 SO 创建(复制物料和数量)# - DN 更新 SO 的已发货数量# - DN 创建 SLE(库存减少)# - DN 创建 GL Entry(成本结转)
7.3 生产模块
# Work Order → Stock Entry(Manufacture)# 集成点:# - Stock Entry 可以从 Work Order 创建# - 自动从 BOM 获取原材料清单# - 更新 Work Order 的已完成数量# - 创建 SLE(原材料出库,成品入库)
7.4 会计模块
# 库存变动自动生成 GL Entry# 集成点:# - 启用永续盘存后自动生成会计分录# - 库存科目与仓库关联# - 费用科目与物料/物料组关联# - 差异科目与公司配置关联
八、代码文件结构
erpnext/stock/├── doctype/│ ├── item/│ │ └── item.py# 物料定义│ ├── warehouse/│ │ └── warehouse.py# 仓库定义│ ├── stock_ledger_entry/│ │ └── stock_ledger_entry.py# 库存流水│ ├── bin/│ │ └── bin.py# 库存余额快照│ ├── serial_no/│ │ └── serial_no.py# 序列号管理│ ├── batch/│ │ └── batch.py# 批次管理│ ├── purchase_receipt/│ │ ├── purchase_receipt.py# 采购收货│ │ └── services/gl_composer.py# PR 的 GL Entry 生成│ ├── delivery_note/│ │ ├── delivery_note.py# 销售发货│ │ └── services/gl_composer.py# DN 的 GL Entry 生成│ ├── stock_entry/│ │ ├── stock_entry.py# 库存变动│ │ └── services/│ │ ├── gl_composer.py# SE 的 GL Entry 生成│ │ └── material_receipt_issue.py# 领料/收货逻辑│ ├── stock_reconciliation/│ │ ├── stock_reconciliation.py# 库存盘点│ │ └── services/gl_composer.py# SR 的 GL Entry 生成│ └── ...├── services/│ └── base_stock_gl_composer.py# 库存 GL Entry 生成基类├── stock_ledger.py# 库存流水管理(估值计算)├── stock_balance.py# 库存余额计算├── serial_batch_bundle.py# 序列号/批次 Bundle 管理└── utils.py# 工具函数
九、核心设计原则
9.1 永续盘存制
库存变动实时记录库存价值与财务数据同步支持实时查询和分析
9.2 流水与快照分离
Stock Ledger Entry:记录历史变动,可追溯Bin:缓存当前余额,高性能查询两者通过更新机制保持同步
9.3 多估值方法支持
支持 FIFO、Moving Average、Standard Cost 等不同物料可使用不同估值方法估值逻辑集中在 stock_ledger.py
9.4 序列号/批次精细化管理
支持单件追踪(Serial No)和批量追踪(Batch)过期日期管理保质期预警
9.5 三单匹配
采购:PO → PR → PI销售:SO → DN → SI确保业务、库存、财务数据一致性
9.6 实时库存预警
通过 Bin 的 projected_qty 实现库存预警支持最小库存、安全库存设置自动生成补货建议
夜雨聆风