项目与库存

Odoo 项目为什么能直接看到收发货:project_stock 的关联字段、嵌入动作与 picking 上下文边界讲透

很多人以为 project_stock 会自动帮项目生成领料单或把库存流程深度嵌进任务里,但官方源码做得其实很克制:它先在 stock.picking 上补一个 project_id,再给项目页挂上指向收货、发货和全部调拨的嵌入动作,并通过 restricted_picking_type_code、default_project_id 和 default_partner_id 把打开后的列表与新建行为限制在项目语义里。

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

不少人第一次看到 project_stock,会本能地把它理解成一句很重的话:

项目模块终于和库存模块打通了,项目里可以自动跑领料和收发货流程了。

但真正看 /home/ubuntu/odoo-temp/addons/project_stock,你会发现官方这次做得很克制。

它做的不是“自动制造一条库存主链路”,而是先做三件更基础、也更稳的事:

  1. stock.picking 增加一个 project_id
  2. 给项目页增加几个打开相关 picking 的动作入口;
  3. 用上下文把这些入口打开后的默认行为限制在“当前项目”语义里。

所以 project_stock 的核心价值不是自动化,而是:

把项目和库存之间原本很散的关系,变成可追溯、可导航、可过滤的显式关联。

一、最底层只有一个字段:stock.picking.project_id

models/stock_picking.py 里,这个模块对 stock.picking 的扩展非常轻:

  • 增加一个 Many2one('project.project') 字段 project_id
  • domain 限制 is_template = False

这说明它不是去重写库存业务规则,而是先把“这张调拨单属于哪个项目”这件事建成正式关系。

这个动作看起来简单,但意义很大。

因为只要有了显式关系,后面你才能自然地做这些事:

  • 在项目里看相关发货和收货;
  • 在 picking 上回看属于哪个项目;
  • 基于项目维度过滤库存动作;
  • 做后续追溯、报表或二次开发。

很多联动模块最容易犯的错,是一上来就想“自动生成一切”。

project_stock 反而先把关系建清楚,这个顺序是对的。

二、项目页之所以能看到库存动作,靠的是嵌入动作,不是写死按钮

views/project_project_views.xml 里注册了多条 ir.embedded.actions

  • From WH
  • To WH
  • Stock Moves

而且它们同时挂在:

  • 项目任务入口动作;
  • 项目更新/dashboard 动作。

这背后说明两件事:

1)它不是改一个视图按钮那么简单

官方用的是项目模块自己的 embedded actions 机制,把库存入口作为“可嵌入项目页面的动作卡片”来接入。

2)它天然继承项目页的可配置性

project/models/project_project.py 里,项目本身就有一套复制、读取、配置 embedded actions 的逻辑。

所以 project_stock 不是另开山头,而是顺着项目模块现成的交互骨架往里插库存入口。

这也是为什么这个模块代码量不大,但接入方式很“原生”。

三、真正的核心在 _get_picking_action():它决定你看到的是哪批单,以及新建时默认长什么样

models/project_project.py 里三个公开动作:

  • action_open_deliveries()
  • action_open_receipts()
  • action_open_all_pickings()

最后都汇总到 _get_picking_action()

这个方法做了几件特别关键的事。

1)domain 锁定当前项目

它先构造:

  • Domain('project_id', '=', self.id)

这保证打开的列表天然只看当前项目相关的 picking。

2)根据入口再补 picking type 语义

如果是 deliveries / receipts,它会继续加:

  • picking_type_id.code = outgoing
  • picking_type_id.code = incoming

这样“From WH”和“To WH”不是文字标签,而是真的把结果集分成发货与收货两类。

3)context 里塞默认值和限制值

它会给 action context 塞:

  • default_project_id
  • restricted_picking_type_code
  • outgoing 时还会加 default_partner_id

这三个键特别重要,因为它们会直接影响:

  • 新建 picking 时默认项目是谁;
  • 新建 picking 时默认 picking type 怎么选;
  • 发货场景下 partner 默认带哪个客户。

也就是说,这个模块不只是“帮你打开一个过滤后的列表”,它还在悄悄把后续新建单据的默认语义一起带过去。

四、restricted_picking_type_code 不是随便塞的,它会被 stock 核心逻辑真正消费

如果只看 project_stock 自己,很容易低估这个 context 键。

但在 stock/models/stock_picking.py 里,核心模型明确会读它:

  • _default_picking_type_id() 会根据 restricted_picking_type_code 选默认 picking type;
  • get_empty_list_help() 和部分 action/help 渲染也会用这个值来显示对应的说明文案。

这说明 project_stock 并不是自己发明了一套无用上下文,而是在复用 stock 核心已经支持的语义开关。

这就是官方模块常见的好味道:

不重复造轮子,只把项目语义准确地接进库存原有机制。

五、为什么 outgoing 还要额外塞 default_partner_id

_get_picking_action() 里,只有 outgoing 会补 default_partner_id = self.partner_id.id

这非常符合业务语义。

因为项目的对外发货,往往天然更接近“给这个项目关联客户送东西”;而 incoming 收货不一定对应项目客户,所以不强行带 partner。

这类小差异看起来不起眼,其实反映了一个很成熟的实现习惯:

  • 同样是项目相关调拨;
  • 发货和收货的默认业务上下文并不对称;
  • 所以默认值也不该机械复制。

六、视图层只做了最小曝光:在 picking 表单的 Other Info 里展示 project_id

views/stock_picking_views.xml 的继承也很克制:

  • 不是重做 picking 视图;
  • 只是把 project_id 插进 other_infos 分组;
  • 还加了 project.group_project_user 权限控制。

这意味着官方并不想把库存单据界面彻底“项目化”,而是:

  • 让懂项目的人能看到这个关联;
  • 让库存单据仍然以库存本身为中心。

这正是边界感比较好的做法。

七、它解决的是追溯和入口问题,不是自动生成库存业务

这是最重要的理解点。

从当前源码看,project_stock 没有 去做这些事情:

  • 不自动根据任务生成 picking;
  • 不自动根据项目预算生成领料;
  • 不改库存估值逻辑;
  • 不改 move / move line 的预留、过账、完成链路;
  • 不把任务和调拨做强绑定状态机。

它解决的是更基础的问题:

  • 让项目可以“看到”库存动作;
  • 让库存动作可以“挂回”项目;
  • 让新建入口默认保持项目语义。

所以如果你期待它一装上就自动跑项目物料履约,那大概率会失望;但如果你想要的是 项目维度的库存可见性与可追溯性,它就很对路。

八、实战里最容易误解的 4 件事

1)项目页能打开 picking,所以 picking 一定是项目自动生成的

不对。当前实现重点是关联与导航,不是自动生成。

2)有 project_id 就说明库存主流程改了

不对。库存核心过账、预留、完成逻辑仍然主要在 stock 模块自身。

3)From WH / To WH 只是界面名字不同

不对。它们带了不同 domain 和 context,后续新建默认值也会不同。

4)这是一个“大而全”的项目物料模块

不对。它是一个边界清晰、非常克制的“项目 ↔ picking 关系模块”。

总结

project_stock 的设计很值得学,因为它没有一上来就把项目和库存强行捏成一个大流程。

它先做的是更稳的三步:

  • stock.picking 上建立 project_id 显式关系;
  • ir.embedded.actions 把收货、发货、全部调拨接进项目页;
  • default_project_idrestricted_picking_type_codedefault_partner_id 把打开后的行为约束在项目语义里。

如果只记一句,可以记这句:

project_stock 不是“项目自动库存流程”模块,而是“项目与调拨之间的追溯、导航和默认上下文”模块。

DISCUSSION

评论区

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