项目深度

Odoo 项目落地成本为什么不是“运费进库存就结束”:landed cost、项目分析分摊与 picking 成本回流讲透

很多人理解 Odoo 落地成本,只停留在库存估值增加、会计分录生成这一步。但在项目化物流里,源码还会把 landed cost 沿着 picking 对应的 `project_id` 回写到分析分摊。本文把 `stock.landed.cost`、`stock.valuation.adjustment.lines` 与项目分析账户的接力链路讲透。

库存 项目
进阶 开发者 1 分钟阅读
0 评论 0 点赞 0 收藏 6 阅读

先说结论

在 Odoo 里,落地成本并不天然等于“库存价值增加了一点”

如果你看的只是 /home/ubuntu/odoo-temp/addons/stock_landed_costs/models/stock_landed_cost.py 主流程,那你会看到:

  • stock.landed.cost 选中若干 picking_ids
  • 计算 valuation_adjustment_lines
  • 校验分摊结果
  • 生成会计分录
  • 对相关 move 执行 _set_value()

但项目相关模块 project_stock_landed_costs 又往前加了一刀:

  • 当 landed cost 的 target_model == 'picking'
  • 并且当前 adjustment line 对应到某个 move_id.picking_id.project_id
  • _prepare_account_move_line_values() 会把 analytic_distribution 直接设为项目的 _get_analytic_distribution() 结果

这意味着:落地成本不只进库存,也可能继续落到项目分析维度。


1. 主干链路:标准 landed cost 本来只关心“把附加成本分到 move 上”

stock.landed.cost 标准模块的主流程很典型:

  1. target_model 决定附加成本应用在哪类对象上,目前核心选择是 picking
  2. _get_targeted_move_ids() 取到这些拣货下的 move_ids
  3. compute_landed_cost() 按数量、成本、重量、体积等方法把成本拆给每条 stock.valuation.adjustment.lines
  4. button_validate() 为这些 adjustment line 创建会计分录
  5. 最后把库存价值更新回 move / valuation 层

如果系统只停在这里,那 landed cost 只是库存估值和总账话题。

但项目场景里,问题并没有结束:

这笔额外运输费、关税、杂费,到底该不该算进某个项目?

Odoo 给出的答案是:如果这批拣货明确挂着项目,就不该丢失项目维度。


2. 项目扩展点:真正关键的是 stock.valuation.adjustment.lines

/home/ubuntu/odoo-temp/addons/project_stock_landed_costs/models/stock_landed_costs.py 的扩展非常短,但非常有力:

if self.cost_id.target_model == 'picking':
    res['analytic_distribution'] = self.move_id.picking_id.project_id._get_analytic_distribution()

注意它不是去改 landed cost 主单,也不是去改 cost line,而是直接卡在 valuation adjustment line 准备会计分录值 的时刻。

这意味着设计意图非常明确:

  • 落地成本先按库存逻辑完成分摊
  • 到要落账时,再判断这条成本行是否属于某个项目 picking
  • 如果属于,就把项目分析维度一并塞进分录

这样做的好处很明显:

  • 不破坏标准 landed cost 的拆分算法
  • 不需要重新发明成本分配模型
  • 只在“真正生成会计分录”这一刻补进项目分析维度

这是一个很典型的 Odoo 扩展方式:主链路不重写,落账节点精准插桩。


3. 为什么这里要依赖 picking.project_id

别忽略另一个前提:project_stock 模块先在 stock.picking 上补了 project_id 字段。

也就是说,项目落地成本能成立,不是因为 landed cost 自己知道项目,而是因为:

  • 拣货单先知道自己属于哪个项目
  • landed cost 再沿着 valuation line -> move -> picking -> project 这条链把项目找回来

这条路径的好处在于,它符合物流现实:

  • 运费、关税、清关费,本来就是围绕某批收发货发生
  • picking 是最自然的业务承载对象
  • 项目只是这批物流背后的经营维度

所以 Odoo 没有把 landed cost 直接硬绑到 project 上,而是让项目成为 picking 的上游业务上下文。


4. 为什么这不是“库存 + 项目”简单相加

很多实施会把这事理解成:

  • 库存模块记成本
  • 项目模块看利润
  • 两边顺手同步一下

但源码设计明显更严谨。

真正发生的是三层接力:

第一层:库存对象识别成本归属

stock.landed.cost 先确定成本应用在哪些 move 上。

第二层:项目维度通过 picking 传入

project_id 不直接定义在 landed cost 上,而是挂在 transfer 上。

第三层:分析分摊在分录准备阶段注入

只有到了 _prepare_account_move_line_values(),才真正把 analytic_distribution 注入到会计分录值里。

这三层拆开以后,你就能理解为什么它既不粗暴,也不容易把标准库存流程打坏。


5. 实施上最容易忽略的两个点

点一:没有项目 picking,就谈不上项目落地成本

如果 move 对应的 picking 根本没带 project_id,那扩展也无从回写项目分析维度。

点二:落地成本是否“进项目利润”取决于后续分析链路

project_stock_landed_costs 做的是把分析分摊写进分录值,不是直接在页面上神奇出现“项目运输成本”。 后续利润面板、分析报表、会计口径是否展示,还取决于更上层的 analytic 汇总逻辑。

也就是说,它解决的是 成本不丢项目维度,不是一口气包办所有展示问题。


6. 一句落地建议

如果你在做项目型采购、项目型物流或者交付制业务,最该检查的不是“landed cost 会不会过账”,而是:

  • picking 有没有挂项目
  • 项目的 analytic distribution 是否正确
  • landed cost 分录是不是沿着 adjustment line 回写了分析维度

只有这三件事串起来,落地成本才真的进入项目经营核算,而不是只停留在库存账上。


参考源码

  • /home/ubuntu/odoo-temp/addons/stock_landed_costs/models/stock_landed_cost.py
  • compute_landed_cost()
  • button_validate()
  • _get_targeted_move_ids()
  • /home/ubuntu/odoo-temp/addons/project_stock/models/stock_picking.py
  • project_id
  • /home/ubuntu/odoo-temp/addons/project_stock_landed_costs/models/stock_landed_costs.py
  • _prepare_account_move_line_values()

DISCUSSION

评论区

想参与讨论?先 登录 再发表评论。
还没有评论,你可以成为第一个留言的人。