很多人学 Odoo 寄售库存时,第一反应只有一层:
- 货在我仓里;
- 但 owner 不是我;
- 所以预留时要看 owner。
这当然没错,但只说到一半。
真正容易出事故的地方不是“数量看不看得见”,而是:
这批货明明在公司仓位里,为什么估值、会计分录、库存价值报表却不该把它当成本公司的货。
如果这个边界没看清,就会出现最危险的情况:
- 现场库存数量看起来正常;
- 财务价值却被错误带进资产。
一句话先讲透
Odoo 对寄售 / owner stock 的处理,至少分两层:
stock模块里,owner_id/restrict_partner_id决定这批货能不能被某次 move 合法占用;stock_account模块里,只要 owner 不是公司 partner,相关 move、move line、quant 就会触发_should_exclude_for_valuation(),从而被排除在估值之外。
所以寄售不是“库存里加个备注字段”,而是数量边界和价值边界同时变化。
为什么 owner stock 不能只停留在可用量层面
确实,在 stock.quant._get_available_quantity()、_get_reserve_quantity() 这类底层方法里,owner_id 已经进入 gather domain。
这意味着:
- 同一库位有货,不代表这次 move 就能随便取;
- 只要 owner 条件不匹配,可用量就会被切开。
但如果你只停在这里,会漏掉更关键的一件事:
- 即便数量在仓里,价值也未必属于你。
这就是 stock_account 继续接管的原因。
restrict_partner_id 和 owner_id 为什么要分两层表达
很多实施顾问会问:
- picking / move 上已经有
restrict_partner_id; - move line / quant 上又有
owner_id; - 为什么不统一成一个字段?
因为两者站位不同。
restrict_partner_id更像 move 级需求约束:这张动作单希望只从谁的库存里取;owner_id更像实际库存事实:这条明细、这条 quant 当前归谁。
在 button_validate() 里,如果 picking 有 owner_id,系统会把这个约束同步到:
- move 的
restrict_partner_id - move line 的
owner_id
这说明 Odoo 需要同时保留:
- 上游业务要求;
- 下游实际落地结果。
stock_account 的真正分界线:不是“在仓”,而是“是不是公司自己的 owner”
stock_account 里的 _should_exclude_for_valuation() 非常直接:
- 如果
restrict_partner_id存在,且不等于company.partner_id,这个 move 应排除估值; - 如果
owner_id存在,且不等于company.partner_id,这个 move line / quant 也应排除估值。
这条规则极其关键,因为它说明 Odoo 的估值逻辑不是按“仓位是不是 internal”单独决定,而是还要再过一层所有权校验。
所以寄售货哪怕躺在内部库位里,也不自动等于公司资产。
为什么这是会计边界,不只是仓库边界
想象一个典型寄售场景:
- 供应商货先放你仓里;
- 你负责保管、拣货、发货;
- 但在真正消耗 / 结算前,货权仍不属于你。
如果系统因为“货在 internal location”就直接把价值算进来,会导致:
- 库存资产虚高;
- 发货成本提前确认;
- 对账时数量与价值口径不一致。
Odoo 正是通过 owner 相关估值排除,把“物理在场”和“财务归属”拆开。
quant 的 value 为什么可能是 0
很多人第一次在库存报表里看到某条 quant 明明有数量却没有价值,会怀疑是成本法、币种或 lot valuation 出了问题。
其实先别急着看成本法。
stock_account.models.stock_quant._compute_value() 里,如果 quant:
- 库位不该估值;
- owner 应被排除估值;
- 数量本身为 0;
系统都会直接跳过。
也就是说,寄售 quant “有量无值” 往往不是 bug,而是正确表达。
move line 改 owner 为什么会牵动价值重算
stock_account 还把 owner_id 放进了 move line 的 valuation trigger 字段列表。
这意味着一旦 done 前后改动:
quantitylocation_idlocation_dest_idowner_idlot_id
系统就会尝试重算 move value。
这背后的业务含义是:
owner 不是展示字段,而是会改变“这部分数量是否参与公司库存价值”的核心条件。
所以随手修 owner,可能修的不只是仓库视图,而是财务口径。
实战最容易犯的 4 个错
1. 把 owner 当备注字段
结果数量分得开,估值却仍按公司库存算。
2. 只在 picking 上填 owner,没检查 move line / quant 是否正确落地
上游意图有了,下游事实没同步,后续追踪会断层。
3. 看到 internal location 就默认会估值
寄售是明显反例。internal 只说明货在内部空间,不说明价值归属。
4. 财务报表异常时只查 account move,不回头查 owner 边界
很多问题其实在库存层已经埋下了。
推荐的排查顺序
只要遇到“寄售货为什么数量有、价值没有”或反过来的问题,建议按这个顺序看:
- quant / move line 的
owner_id是谁; - move 的
restrict_partner_id是谁; - 公司
partner_id是谁; - 对应记录是否命中
_should_exclude_for_valuation(); - 再去看成本法、估值层与会计分录。
这样能先把边界判断做对,再看金额计算。
最后的结论
Odoo 对寄售库存最成熟的地方,不是“支持 owner 字段”,而是把 owner 真正接进了:
- 可用量匹配;
- 预留与拣货;
- move / move line 约束;
- quant 估值;
- 会计边界。
所以记住一句话就够了:
寄售库存可以在你的仓里,但不一定在你的账里。
DISCUSSION
评论区