先说结论
在 Odoo 里,库存价值不是“等月底再统一算”。
对实时报价(real_time)产品来说,库存 move 在完成时就会往总账写分录。
所以你会看到一个很典型的链路:
- 库存 move 完成
- 价值被计算出来
- 会计分录生成
- journal entry 过账
这条链把库存和会计直接接在了一起。
入口:stock_account.StockMove._action_done()
在 addons/stock_account/models/stock_move.py 里,真正关键的逻辑顺序非常讲究:
moves_out = self.filtered(lambda m: m._is_out())
moves_out._set_value()
moves = super()._action_done(cancel_backorder=cancel_backorder)
moves_in = moves.filtered(lambda m: m.is_in or m.is_dropship)
moves_in._set_value()
moves._create_account_move()
moves_out.product_id.filtered(lambda p: p.cost_method == 'fifo')._update_standard_price()
这里最值得注意的是:
- 出库 move 先算值,因为 FIFO 需要当前栈
- 入库 move 在真正 done 之后再算值,因为实际成本此时更明确
- 最后统一创建会计分录
这不是随便写的顺序,而是为了保证 valuation 计算尽量贴近真实业务。
什么样的 move 才会生成分录
不是每一笔库存移动都会打总账。
_should_create_account_move() 只允许满足这些条件的 move 进入会计链:
- 产品是可库存的(storable)
- 这笔 move 是 valued 的
- 来源或目的地点有 valuation account
- 产品的 valuation 方式是
real_time
也就是说,会计分录不是 move 的默认副作用,而是 valuation 策略的结果。
这点很重要。
如果你的产品是 manual valuation,或者 location 没有对应 valuation account,链路就会被挡住。
_create_account_move() 如何拼出双向分录
创建会计分录时,Odoo 不是只写一边,而是写一对平衡分录。
核心逻辑在 _get_account_move_line_vals():
if self.location_id.valuation_account_id:
debit_acc = self.product_id._get_product_accounts()['stock_valuation']
credit_acc = self.location_id.valuation_account_id
else:
debit_acc = self.location_dest_id.valuation_account_id
credit_acc = self.product_id._get_product_accounts()['stock_valuation']
这段代码告诉你两件事:
- 库存价值账户永远是核心
- 另一边要看 source location 还是 destination location 有没有 valuation account
然后它会用 self.value 作为金额,生成一借一贷两条行。
这也是为什么库存会计不是“简单把数量乘单价记一下”,而是要把库位、产品和成本法一起考虑进去。
为什么这条链和 FIFO、标准价都有关
stock_account 还会在出库后更新 FIFO 产品的 standard price。
这意味着:
- 成本法不同,计算路线不同
- FIFO 更依赖当前堆栈与出库顺序
- real_time 让会计分录在业务发生时同步写入
如果你只盯着财务分录,容易忽略库存侧的价值来源; 如果你只盯着库存数量,又会漏掉总账已经过账这件事。
所以库存价值链的完整视角应该是:
数量在库存,价值在 move,总账分录在 account.move。
最容易踩坑的地方
1)把价值问题当成数量问题
数量对了,不代表 valuation 对了。先看产品成本法,再看 location valuation account。
2)只查 stock,不查 account
如果分录没出来,重点查 _should_create_account_move() 的条件有没有被拦住。
3)忽略退货和 dropship 分支
_is_out()、_is_in()、_is_dropship() 这些分支会影响值的计算时点。
实战建议
排查库存金额不对时,最有效的顺序通常是:
- 确认产品是 real_time valuation
- 确认 location 上有 valuation account
- 追
_action_done()的前后顺序 - 检查 move value 有没有在 FIFO/standard price 场景下被更新
- 再看 journal entry 的借贷方向
这样查,通常比“直接去改会计凭证”更快。
一句话总结
Odoo 的库存会计不是月底补账,而是 move 完成时就把价值写进总账。
stock_account._action_done() 负责价值时点,_create_account_move() 负责分录生成,成本法和 valuation account 则决定这条链能不能走通。
DISCUSSION
评论区