先说结论
在 Odoo 里:
- 收货校验 会触发库存 move 完成
- 库存 move 完成 可能立刻生成库存会计分录
- 供应商发票过账 又是另一条会计主线
- 但发票价格会影响库存估值取值依据或后续差异理解
所以它们是:
强关联,但不等于同一个时点、同一个动作。
这就是为什么你有时觉得“收货一做账就出了”,有时又觉得“发票过账后才更像最终成本”。
第一层:库存分录并不是等发票按钮来触发
在 stock_account/models/stock_move.py 里,最关键的入口是 stock.move._action_done()。
源码顺序很清楚:
- 先给 outgoing move 取当前成本
- 调用父类完成库存 move
- 对已完成的 incoming / dropship move 设值
_set_value() - 调用
_create_account_move() - 再做一些标准价 / analytic 的后续动作
这说明一个核心事实:
库存会计分录的直接触发点,是库存 move done,而不是供应商发票 action_post。
如果你把这两个入口混成一个,后面就会一直觉得系统行为“忽快忽慢”。
第二层:库存分录是怎么建出来的
同一个文件里的 _create_account_move() 更直接:
- 先判断哪些 move 需要创建会计分录
- 汇总会计分录行
aml_vals_list - 创建
account.move - 把新建的
account_move_id回绑给 stock move - 最后直接
account_move._post()
注意最后这一步很关键:
库存分录不是只创建草稿,它会直接过账。
这也是为什么很多公司在自动估值场景里,会觉得“仓库一校验,账上立刻有动作”。
因为源码就是这么设计的。
第三层:那供应商发票过账到底参与了什么
account/models/account_move.py 里的 action_post(),本质上是在走发票/会计凭证自己的过账流程。
它会调用 _post(soft=False),把发票从 draft 推进到 posted。
这条线和库存 move done 不是同一条线。
所以发票过账本身并不是“替库存补做一次 _create_account_move()”。
它解决的是:
- 供应商应付确认
- 发票会计凭证过账
- 发票价格正式进入已过账会计事实
它和库存估值有关,但不是同一个按钮干同一件事。
真正的连接点:估值取值会参考已过账发票
purchase_stock/models/stock_move.py 里最值得看的,是 _get_value_from_account_move()。
这段逻辑会遍历采购行关联的 invoice_lines,但只取:
move_id.state == 'posted'的发票行- 并按发票类型、数量、币种换算,计算可用于估值的 value
这意味着什么?
意味着在采购收货场景里,Odoo 对“这个库存 move 应该按什么价值理解”时,会优先参考 已经过账的供应商发票事实。
如果还没有足够的已过账发票数据,它又会退回 _get_value_from_quotation(),也就是按采购报价/采购单价格估值。
这才是“发票和库存估值连起来”的真正位置。
不是按钮共享,而是 估值依据共享。
为什么你会感觉“有时同步,有时分步”
因为系统面对的是两个不同问题:
问题 1:货物现在是否已经真实入库?
这个由收货校验回答。
问题 2:这批货现在该按什么会计价值理解?
这个要看:
- 当前成本方法
- 当前 move 类型
- 有没有已过账供应商发票
- 已过账发票覆盖了多少数量
所以在业务感觉上就会出现两种体验:
体验 A:看起来同步
收货一校验,库存分录立刻出来,且价格和你预期差不多。
体验 B:看起来分步
收货先按采购价/临时值入账,后来发票过账后你再看,就会发现解释口径、差异理解或后续估值来源更完整了。
这不是系统摇摆,而是它把 物流事实 和 财务事实 分层处理了。
最容易误解的 4 句话
1. “库存分录是发票过账时才做的”
不对。自动估值场景下,很多库存分录在 move done 时就做了。
2. “发票过账和库存估值完全无关”
也不对。已过账发票会参与估值依据。
3. “只要发票价格变了,收货时那张库存凭证一定同步重做”
别这么想。系统的联动点是估值逻辑与会计事实,不是简单的“删旧重建”。
4. “收货和发票必须同一天做,账才对”
系统设计本来就允许物流事实和财务事实分时出现。
实战理解:把它拆成三层最不容易乱
我会把这条链记成三层:
第一层:物流完成
- 收货单校验
stock.move._action_done()- 库存数量变化
第二层:库存会计入账
_set_value()_create_account_move()- 库存 journal entry 过账
第三层:采购发票事实
- vendor bill
action_post() - 价格与数量成为正式已过账会计事实
- 为估值解释和后续口径提供依据
三层分开看,整个系统就很清楚。
最后一句话
Odoo 不是把“收货”“库存估值”“发票过账”揉成一个按钮完成,而是故意把它们拆成:
- 物流执行
- 库存估值入账
- 发票会计确认
三条彼此关联的链。
所以看到它们有时同步、有时分步,不要先怀疑系统; 先问自己:
你现在看到的,到底是物流事实,还是财务事实,还是估值依据正在从采购价过渡到已过账发票价。
DISCUSSION
评论区