Odoo 18:如何按预算过滤 analytic_distribution 中的分析账户?
结论
在 Odoo 18.0 的上游实现中,可以扩展 AnalyticDistribution.recordProps(line),将预算允许的账户 ID 加入内部 account.analytic.account 字段的 domain。该组件没有提供通过 XML options 直接设置内部账户 domain 的通用配置项,business_domain 也不用于此目的。参见 Odoo 18.0 组件源码。
以下示例适用于部署环境仍沿用该组件实现的情况。Enterprise 扩展或其他定制模块可能改变调用链,需要对照实际加载的代码确认。
为什么外层 context 没有达到预期效果
弹窗会创建独立的临时 Record,其内部账户字段由 recordProps(line) 动态生成。模板将这些字段各自的 domain 传给内部 Field,外层 analytic_distribution 的 context 因此不会自动成为内部账户选择器的自定义 context。参见弹窗模板。
账户候选搜索使用标准 Many2one 自动补全,调用 account.analytic.account.name_search;“搜索更多”也沿用该选择器的 domain。没有看到日志,不足以判断该 widget 绕过了 Python 搜索方法。相关调用见 Many2XAutocomplete 源码。
1. 将允许账户加载到采购订单行
下面假设采购订单上的 allowed_analytic_account_ids 已经是正确的 Many2many 字段,并且会随 overall_budget_id 更新。原问题没有提供预算模型上的账户字段名,示例因此复用已有字段。
在自定义模块中为订单行增加 related 字段。如果已有同名字段,直接复用并检查其计算逻辑:
from odoo import fields, models
class PurchaseOrderLine(models.Model):
_inherit = "purchase.order.line"
allowed_analytic_account_ids = fields.Many2many(
related="order_id.allowed_analytic_account_ids",
readonly=True,
)
在现有采购订单表单继承视图中加载这个行字段:
<xpath expr="//field[@name='order_line']/list" position="inside">
<field name="allowed_analytic_account_ids" column_invisible="True"/>
</xpath>
如果也允许通过订单行的独立表单编辑分配,需要在该表单中加载此字段。
2. 给内部账户字段追加 domain
将以下代码放入自定义模块的 static/src/js/analytic_distribution_budget.js:
/** @odoo-module **/
import { AnalyticDistribution } from "@analytic/components/analytic_distribution/analytic_distribution";
import { Domain } from "@web/core/domain";
import { patch } from "@web/core/utils/patch";
patch(AnalyticDistribution.prototype, {
recordProps(line) {
const result = super.recordProps(line);
const record = this.props.record;
if (record.resModel !== "purchase.order.line") {
return result;
}
const allowedIds =
record.data.allowed_analytic_account_ids?.currentIds || [];
for (const field of Object.values(result.fields)) {
if (field.relation === "account.analytic.account") {
field.domain = Domain.and([
field.domain || [],
[["id", "in", allowedIds]],
]).toList();
}
}
return result;
},
});
这段代码保留原有过滤条件,并与允许账户范围取交集。Domain.and() 的行为见 domain 工具源码;Many2many 前端集合的 currentIds 表示当前关联 ID,见 StaticList 源码。
示例采用“允许列表为空时不显示任何账户”的规则。如果业务要求未选预算时不限制,应增加明确条件,区分“未选择预算”和“预算中没有允许账户”。
3. 加载资源并检查请求
在现有 __manifest__.py 的 assets 中加入资源,将 your_module 替换成实际模块名:
"assets": {
"web.assets_backend": [
"your_module/static/src/js/analytic_distribution_budget.js",
],
},
同时确认模型文件已通过 __init__.py 导入,模块依赖包含所扩展的采购、分析功能及预算字段所属模块。更新模块后刷新前端资源。
在浏览器 Network 面板中输入账户名称,检查 account.analytic.account 的 name_search 请求。其 kwargs.args 应包含预算账户的 id in [...] 条件,同时保留原有计划和公司条件。
如果依然没有进入自定义 name_search,依次检查:
- Python 文件是否被导入,运行中的数据库是否已更新模块。
- 日志是否来自实际处理请求的进程;排查时使用服务端 logger 比
print更可靠。 - 实际加载的组件是否被其他模块替换。
- context 键名是否一致,尤其是
filter_budget_id与allowed_analytic_account_ids。
原示例展示的是 search(),不是 _search(),排查时应区分二者。
注意已有分配与保存校验
上述改动只过滤候选账户,不会自动清理已有的 analytic_distribution。切换预算后,应检查允许账户字段是否及时刷新,并明确如何处理已经选中、但不再属于新预算的账户。
如果“只能使用预算账户”是必须执行的业务规则,还需要在服务端保存或业务确认环节增加校验,覆盖订单行分配变化和订单预算变化两种情况。解析分配数据时要注意:一个 JSON 键可能包含多个以逗号分隔的账户 ID。
以上代码基于上游源码,尚未在你的 Enterprise 数据库及自定义预算模型中运行验证。
备注:内容仅供参考。
