企业项目深度

Odoo 企业版项目里的 Assets 按钮为什么不是“项目挂了固定资产”那么简单:analytic distribution、统计按钮与折旧归集链路讲透

很多人第一次看到 Odoo 企业版项目表单上的 Assets 统计按钮,会以为项目模型自己挂了一套固定资产关系。可 `project_account_asset` 源码恰好说明,官方根本没做“项目资产子表”,而是借 `analytic_distribution` 把资产、折旧与项目分析账户轻量桥接起来。本文把计数、打开动作、默认分摊与会计权限边界一次讲清。

企业 项目
进阶 开发者 2 分钟阅读
0 评论 0 点赞 0 收藏 5 阅读

先说结论

/home/ubuntu/odoo-temp/enterprise/project_account_asset/ 这个模块非常小,小到很多人会低估它。

表面上它只做了三件事:

  • project.project 上增加 assets_count
  • 在项目右侧 stat button 里追加一个 Assets 按钮
  • 点击后打开资产列表,并默认把新资产的 analytic_distribution 预填到当前项目分析账户

但它真正表达的设计思路其实很明确:

Odoo 企业版并没有把“项目资产”建成一套独立关系模型,而是把项目当成“分析账户入口”,再通过 analytic_distribution 去观察与承接资产及其折旧。

也就是说,项目页上的 Assets 按钮不是在说“这个项目拥有一批资产主数据”,而是在说:

  • 这些资产的分析分配里包含当前项目分析账户
  • 所以后续折旧、处置、账务追踪,也可能继续沿着这条分析口径落回项目

这和“项目里建个固定资产子表”是两种完全不同的产品思路。


第一层:这个模块为什么没有 Many2many,而只靠 account_id

project_account_asset/models/project_project.py 里最重要的前提,是它根本没有新增什么:

  • asset_ids
  • asset_line_ids
  • project_id on account.asset

它只依赖项目已有的 account_id,也就是项目分析账户。

接着 assets_count 的计算逻辑直接做:

self.env['account.asset']._read_group(
    [('analytic_distribution', 'in', self.account_id.ids)],
    ['analytic_distribution'],
    ['__count'],
)

这里非常值得停一下。

官方不是按“资产属于哪个项目”来查,而是按:

  • 资产的 analytic_distribution
  • 是否包含当前项目的分析账户 ID

换句话说,项目和资产的连接键不是业务主键,不是外键,而是分析分配口径

这意味着两件事。

1. 项目页看到的是“会计归属上的相关资产”

只要某个资产的分析分配中包含该项目分析账户,它就会被统计进来。

所以这个按钮表达的不是狭义“项目专属资产”,而是:

与该项目分析核算相关的资产。

2. 一项资产理论上可以同时服务多个分析维度

因为 analytic_distribution 本身允许分摊结构,所以从建模思路上说,资产并不是非得 100% 属于一个项目。

这比硬绑一个 project_id 灵活得多,也更符合财务真实场景:

  • 一台设备可能同时服务多个项目
  • 一笔资本化投入可能要按分析分配拆给不同项目 / 成本中心
  • 项目只是其中一个分析口径,不一定是唯一口径

所以 project_account_asset 的重点不是“项目资产卡片”,而是“项目可见的资产分析视图”。


第二层:为什么它用 _read_group,而不是一条条 search_count

_compute_assets_count() 没有在每个项目上单独 search_count(),而是一次性 _read_group() 后再映射回各个项目。

这背后反映出官方对这个字段的定位:

  • 它是项目页的辅助统计
  • 可能在列表、看板、右侧面板里被频繁读取
  • 所以必须尽量避免 N+1 查询

源码随后把结果做成:

data = {int(account_id): count for account_id, count in data}

再按项目的 account_id.id 回填 assets_count

这说明按钮背后的统计粒度其实很单纯:

  • 一个项目,对应一个分析账户
  • 一个分析账户,对应若干包含它的资产
  • 页面只关心这个计数,不追求在项目侧维护更复杂的桥表状态

这种实现很“轻”,但也正因为轻,它不容易把项目模块和资产模块耦得太死。


第三层:为什么点击 Assets 按钮后,重点不是过滤,而是默认值

action_open_project_assets() 做的事情比“打开一个 domain 过滤后的列表”稍微多一步。

它先找出相关资产:

assets = self.env['account.asset'].search(
    [('analytic_distribution', 'in', self.account_id.ids)],
)

然后读取资产模块自己的标准动作:

action = self.env["ir.actions.actions"]._for_xml_id("account_asset.action_account_asset_form")

接着更新成:

  • list / form / kanban 视图
  • domain = [('id', 'in', assets.ids)]
  • context = {'default_analytic_distribution': {self.account_id.id: 100}}

很多人会把注意力只放在 domain 上,但真正有产品味道的点在 default_analytic_distribution

这代表用户从项目进入资产页时,Odoo 默认假设:

如果你是从这个项目上下文新建资产,那最合理的默认分析分配,就是 100% 给当前项目分析账户。

这并不等于系统强制资产只能属于这个项目;它只是把“从项目入口创建资产”的默认口径先设好。

所以这个动作同时在做两件事:

  • 回看历史:看哪些资产已经和当前项目分析有关
  • 引导未来:新建资产时默认继续沿着当前项目分析账户走

这就是一个典型的“桥接动作”,而不是单纯导航按钮。


第四层:为什么它只给会计读权限用户显示

字段定义和 stat button 都加了同一个权限组:

  • groups='account.group_account_readonly'

_get_stat_buttons() 里还会判断:

if self.env.user.has_group('account.group_account_readonly'):

这件事特别能说明官方边界意识。

项目用户并不天然应该看到资产数据。

毕竟资产对象后面可能涉及:

  • 原值
  • 折旧
  • 资产处置
  • 会计科目
  • 凭证
  • 公司级账务口径

这些都不是普通项目协作者一定该看的东西。

所以官方没有把 Assets 按钮当成“项目管理增强功能”开放给所有项目成员,而是明确把它当成:

项目页里的会计观察入口。

也就是说,这个桥不是为了让项目模块吞掉资产模块,而是为了让有会计权限的人,在项目上下文里更快进入资产视角。


第五层:为什么按钮在 assets_count > 0 时才显示

_get_stat_buttons() 里的 show 条件是:

'show': self.assets_count > 0,

这意味着项目表单不会默认常驻一个空的 Assets 按钮。

这看起来像 UI 小事,其实对应的是产品定位:

  • 资产不是每个项目的必备对象
  • 它只是某些资本化 / 长周期项目会出现的视角
  • 没有资产时,不需要把项目页变成“什么都能点”的杂货铺

换句话说,官方这里不是在宣告“项目一定要管理资产”,而是在说:

只有当分析分配已经把资产拉进项目上下文时,项目页才值得出现这个入口。

这和那种“先放个按钮,点进去永远是空列表”的实现相比,克制得多。


第六层:这个按钮为什么能和折旧归集链路接上

如果只看 project_account_asset 本身,你可能会觉得它只是个统计入口,谈不上“深度”。

但把它和 /home/ubuntu/odoo-temp/enterprise/account_asset/ 放在一起看,逻辑就完整了。

account_move.py 里,系统从发票行自动创建资产时,会把原始凭证行的:

'analytic_distribution': move_line.analytic_distribution,

直接带到资产上。

而后续准备折旧分录 _prepare_move_for_asset_depreciation() 时,又会把资产自身的:

analytic_distribution = asset.analytic_distribution

继续写回折旧分录的两条 move line。

这意味着什么?

资产进入项目视角,不只是因为“项目页能看到它”

更关键的是:

资产如果最初按项目分析账户建起来,它后面的折旧分录也会沿着同一分析分配继续流动。

所以项目页上的 Assets 按钮,本质上看到的是一条更长的链:

  • 采购 / 发票行带分析分配
  • 资产继承分析分配
  • 项目通过分析账户统计资产
  • 折旧分录继续继承这套分析分配

它不是独立小挂件,而是分析会计和资产会计之间的一条轻量接缝。


第七层:为什么官方选择“分析分配桥接”,而不是“项目资产专属模型”

如果要做得更“重”,当然可以设计另一条路线:

  • account.asset 上加 project_id
  • 项目页维护 asset_ids
  • 资产复制、拆分、处置时再同步项目字段
  • 折旧凭证再想办法回填项目维度

但这样会立刻出现很多问题:

  • 一个资产能不能跨项目?
  • 多项目共同使用怎么分摊?
  • 会计要按成本中心 / 部门 / 项目多维分析时怎么办?
  • 资产调整、价值增加、处置凭证如何保持一致?

analytic_distribution 天生就是为“一个经济对象可落到多个分析维度”服务的。

所以官方这里其实做了一个很聪明的取舍:

  • 不在项目模块重造资产归属模型
  • 直接借分析分配当桥
  • 让项目看到自己关心的资产口径即可

这就是为什么 project_account_asset 代码极少,却并不浅。


新手最容易误解的 5 件事

1. 以为项目上有 Assets 按钮,就说明 account.asset 一定有 project_id

不对。这个桥接模块压根没走外键关系,而是走 analytic_distribution

2. 以为 assets_count 统计的是“项目独占资产”

不对。它统计的是分析分配包含当前项目分析账户的资产。

3. 以为点进列表只是为了看历史数据

不对。动作上下文还会把新建资产默认分析分配成当前项目 100%。

4. 以为所有项目成员都能看到这个按钮

不对。它明确受 account.group_account_readonly 控制。

5. 以为这个模块和折旧没关系

不对。真正的价值恰恰在于资产的分析分配还能继续进入折旧分录,项目因此能沿同一口径观察长期成本。


实战里最该注意什么

1. 想让项目看到资产,先看项目有没有分析账户

如果项目本身 account_id 没建好,这个桥接就无从谈起。

2. 资产看不到时,不要先怀疑 stat button

先查:

  • 资产的 analytic_distribution 有没有当前项目分析账户
  • 分摊比例是不是被写到别的分析账户上
  • 当前用户是否具备会计只读组权限

3. 从项目入口新建资产时,确认默认分摊是不是符合业务真实情况

源码默认给的是 {project.account_id: 100}

这对“单项目资本化”很合理,但如果资产本来要分摊给多个项目或成本中心,就不要无脑接受默认值。

4. 不要把这个按钮误当成项目成本全景

它只覆盖资产这条线,且前提是分析分配写对了。

项目真实成本口径通常还会同时涉及:

  • 工时
  • 采购
  • 费用
  • 材料
  • 订阅 / 收入

Assets 按钮只是其中一块长期资产视角,不是全部。


一句话记忆法

Odoo 企业版项目上的 Assets 按钮,不是在项目里塞进一套固定资产子表,而是让项目通过分析账户去观察“哪些资产及其后续折旧,正在沿这条分析分配为项目服务”。

DISCUSSION

评论区

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