先说结论
很多人第一次看到 Odoo 项目和库存联动时,会把它理解成:
- 出库单上多了个项目字段
- 项目页能看到发货和收货
- 材料成本于是就“归到项目里了”
但如果你去看 /home/ubuntu/odoo-temp/addons/project_stock/ 和 project_stock_account/,会发现官方做的是一整条成本链:
stock.picking可以直接挂项目- 项目页面能按类型打开 deliveries / receipts / all pickings
- 库存移动在启用分析成本时,会拿项目的分析分摊
- 生成的分析行被标记为
picking_entry - 项目 profitability 再把这类分析行识别成
Materials
所以真正的设计不是“库存单属于哪个项目”,而是:
库存移动产生的成本,如何以可追踪、可汇总、可经营分析的方式进入项目。
第一层:项目上的库存按钮为什么只是入口,不是重点
project_stock/models/project_project.py 给项目加了几个动作:
action_open_deliveries()action_open_receipts()action_open_all_pickings()
从界面上看,这像是在项目里补了库存导航。
但这几个动作真正说明的是:
- 项目可以成为库存业务的过滤视角
- 不同类型拣货会继承项目上下文
- 项目和库存之间建立了正式的业务关联,而不只是备注字段
换句话说,按钮是为了让用户能回看,真正重要的是后面的成本归集逻辑。
第二层:材料成本为什么不是看拣货单头,而是看 move 的分析行
在 project_stock/models/stock_picking.py 里,stock.picking 的确新增了:
project_id
但项目材料成本的关键不在 picking 头,而在 project_stock_account/models/stock_move.py。
因为真正产生成本归属的是库存移动 stock.move:
_get_analytic_distribution()会优先拿picking_id.project_id的分析分摊_prepare_analytic_line_values()会把分析行名称和 category 一起准备好_prepare_analytic_lines()还会校验项目是否满足必填分析计划
这说明官方的思路是:
项目字段先挂在 picking 上,但真正落地到成本系统的是 move 级别的分析行。
这非常合理,因为会计和分析成本最终记的不是“有一张拣货单”,而是“有哪些库存移动形成了成本”。
第三层:为什么还要校验 mandatory analytic plans
在 _prepare_analytic_lines() 里,源码会检查:
- 当前公司在
stock_picking业务域下要求哪些 mandatory plans - 这些计划列在项目上是否都已填好
如果没填,就直接抛 ValidationError。
这点特别有 Odoo 味道:
- 不是先放行,等利润报表出错再补救
- 而是在生成分析行时就拦住
因为一旦库存成本已过账、分析维度又缺失,后面项目盈利里看到的材料成本就会不完整甚至归错项目。
所以这里的校验本质上是在保护:
项目材料成本一旦入账,就必须具备可核算的分析维度。
第四层:为什么分析行 category 要专门标成 picking_entry
project_stock_account/models/account_analytic_line.py 给分析行 category 增加了一个新值:
picking_entry
很多人会忽略这个字段,但它其实是后面 profitability 识别材料成本的关键标签。
因为如果所有分析行都混在一起,项目盈利就很难区分:
- 工时成本
- 外包采购成本
- 库存材料成本
而 picking_entry 恰好把“由库存移动生成的项目分析成本”单独打了标。
这就是一个典型的报表前置设计:
- 先在记账/分析行层面把来源分清
- 后面经营面板才能稳定按来源分类汇总
第五层:项目 profitability 为什么把它显示成 Materials
在 project_stock_account/models/project_project.py 里,官方扩展了盈利标签和成本抓取逻辑:
- 把对应 section 命名为
Materials - 从 category 为
picking_entry的分析行汇总成本 - 可选地提供动作跳到相关分析行
这说明项目盈利页面对材料成本的理解是:
- 它不是采购单成本
- 也不是工时成本
- 它是因为项目驱动了库存领用/调拨,从而形成的材料性成本
所以如果一个实施顾问跟客户说“项目利润里的材料成本来自库存移动”,这句话是源码级成立的,而不是业务猜测。
第六层:这套设计真正解决了什么问题
这套链路解决的是一个很现实的问题:
- 项目做交付要用料
- 用料往往发生在库存模块
- 但项目经理最终想在项目盈利里看到材料花了多少钱
如果没有这条链,就会出现两种糟糕局面:
1. 库存做了,项目看不到
材料真实领用了,但项目利润还像没发生过一样。
2. 看得见,但不可核算
项目里能看到一个“材料金额”,却追不到底层 move、分析行和分析计划。
Odoo 这里走的是第三条路:
- 库存流程照常做
- 分析成本在 move 层形成
- 项目盈利只做分类呈现与汇总
这让流程系统和经营系统能够对齐。
新手最容易误解的 4 件事
1. 以为项目材料成本就是拣货单上填了项目
不对,真正进入盈利的是库存移动生成的分析行。
2. 以为出库后材料成本会自动进项目,不需要分析配置
不对,mandatory analytic plans 缺失会直接拦截。
3. 以为材料成本和采购成本是一回事
不对,源码里是两个不同来源、不同 section。
4. 以为 profitability 里的 Materials 是手工统计值
不对,它是基于 picking_entry 类别分析行汇总出来的。
实战里最该注意什么
1. 项目想吃到材料成本,先确认库存操作类型是否启用了 analytic costs
否则 move 根本不会生成期望的分析分摊。
2. 客户一说“材料成本没进项目”,优先查三层
- picking 是否挂项目
- move 是否生成分析行
- 分析行 category 是否正确进入
picking_entry
3. 多分析计划环境里,不要把项目分析字段配成“可选但其实业务必填”
因为库存成本一旦进错维度,后续利润面板再漂亮也只是错误汇总。
一句话记忆法
Odoo 项目材料成本不是从拣货单头直接进利润面板,而是先由挂项目的库存移动生成带
picking_entry标签的分析行,再被 profitability 识别为Materials成本。
DISCUSSION
评论区