多公司指南

警告

本教程需要对 Odoo 有良好的了解。如需帮助,请先参考 服务器框架 101 教程。

从 13.0 版本开始,用户可以同时登录多个公司。这允许用户访问多个公司的信息,同时也能够在多公司环境中创建或编辑记录。

如果管理不当,这可能会成为许多多公司行为不一致的来源。例如,一个同时登录了公司 A 和公司 B 的用户,可以在公司 A 创建销售订单,并将其添加属于公司 B 的产品。只有当用户从公司 B 注销后,才会在该销售订单上出现访问错误。

为了正确管理多公司行为,Odoo 的 ORM 提供了多种功能:

公司相关字段

当一条记录来自多个公司时,我们必须预期某个字段的值会根据设置该值的公司不同而有所差异。

对于同一记录的字段支持多个值,必须将属性 company_dependent 设置为 True

from odoo import api, fields, models

class Record(models.Model):
    _name = 'record.public'

    info = fields.Text()
    company_info = fields.Text(company_dependent=True)
    display_info = fields.Text(string='Infos', compute='_compute_display_info')

    @api.depends_context('company')
    def _compute_display_info(self):
        for record in self:
            record.display_info = record.info + record.company_info

注解

_compute_display_info 方法使用 depends_context('company')`(参见 :attr:`~odoo.api.depends_context)进行装饰,以确保计算字段会根据当前公司(self.env.company)进行重新计算。

当读取一个与公司相关的字段时,会使用当前公司来获取其值。换句话说,如果用户同时登录了公司 A 和 B,且 A 作为主公司,然后为公司 B 创建一条记录,那么与公司相关的字段的值将为公司 A 的值。

要读取由其他公司设置的公司相关字段的值,我们需要确保我们使用的公司是正确的。这可以通过 with_company() 实现,该方法会更新当前公司。

# Accessed as the main company (self.env.company)
val = record.company_dependent_field

# Accessed as the desired company (company_B)
val = record.with_company(company_B).company_dependent_field
# record.with_company(company_B).env.company == company_B

警告

每当您在计算/创建可能在不同公司中行为不同的事物时,应确保您所做的操作是在正确的公司中进行的。始终使用 with_company 来避免以后出现问题,这并不会花费太多成本。

@api.onchange('field_name')
def _onchange_field_name(self):
 self = self.with_company(self.company_id)
 ...

@api.depends('field_2')
def _compute_field_3(self):
 for record in self:
   record = record.with_company(record.company_id)
   ...

多公司一致性

当一条记录通过 company_id 字段在多个公司之间共享时,我们必须确保它不能通过关系字段与另一家公司的记录相关联。例如,我们不希望销售订单及其发票属于不同的公司。

为确保多公司一致性,您必须:

  • 将类属性 _check_company_auto 设置为 True

  • 如果其模型包含 company_id 字段,则使用 check_company 属性设置为 True 来定义关系字段。

在每次调用 create()write() 时,将触发自动检查,以确保记录的多公司一致性。

from odoo import fields, models

class Record(models.Model):
    _name = 'record.shareable'
    _check_company_auto = True

    company_id = fields.Many2one('res.company')
    other_record_id = fields.Many2one('other.record', check_company=True)

注解

字段 company_id 不应定义为 check_company=True

Model._check_company(fnames=None)[源代码]

Check the companies of the values of the given field names.

参数

fnames (list) – names of relational fields to check

引发

UserError – if the company_id of the value of any field is not in [False, self.company_id] (or self if res_company).

For res_users relational fields, verifies record company is in company_ids fields.

User with main company A, having access to company A and B, could be assigned or linked to records in company B.

警告

check_company 功能执行严格的检查!这意味着如果一条记录没有 company_id`(即该字段不是必填项),则不能将其与设置了 `company_id 的记录关联。

注解

当字段上未定义域且 check_company 设置为 True 时,会添加一个默认域:['|', '('company_id', '=', False), ('company_id', '=', company_id)]

默认公司

当模型中的字段 company_id 被设置为必填项时,最佳实践是设置一个默认公司。这可以简化用户的设置流程,甚至在公司被隐藏时确保其有效性。事实上,如果用户没有访问多个公司的权限(即用户没有 base.group_multi_company 用户组),公司通常会被隐藏。

from odoo import api, fields, models

class Record(models.Model):
    _name = 'record.restricted'
    _check_company_auto = True

    company_id = fields.Many2one(
        'res.company', required=True, default=lambda self: self.env.company
    )
    other_record_id = fields.Many2one('other.record', check_company=True)

视图

如前所述 above,如果用户没有访问多个公司的权限,公司通常会隐藏。这是通过用户组 base.group_multi_company 进行评估的。

<record model="ir.ui.view" id="record_form_view">
    <field name="name">record.restricted.form</field>
    <field name="model">record.restricted</field>
    <field name="arch" type="xml">
        <form>
            <sheet>
                <group>
                    <group>
                        <field name="company_id" groups="base.group_multi_company"/>
                        <field name="other_record_id"/>
                    </group>
                </group>
            </sheet>
        </form>
    </field>
</record>

安全规则

在处理跨公司共享的记录或仅限于单个公司的记录时,我们必须确保用户无法访问其他公司的记录。

这是通过基于 company_ids 的安全规则实现的,其中包含用户的当前公司(用户在多公司小部件中选择的公司)。

<!-- Shareable Records -->
<record model="ir.rule" id="record_shared_company_rule">
    <field name="name">Shared Record: multi-company</field>
    <field name="model_id" ref="model_record_shared"/>
    <field name="global" eval="True"/>
    <field name="domain_force">
        ['|', ('company_id', '=', False), ('company_id', 'in', company_ids)]
    </field>
</record>
<!-- Company-restricted Records -->
<record model="ir.rule" id="record_restricted_company_rule">
    <field name="name">Restricted Record: multi-company</field>
    <field name="model_id" ref="model_record_restricted"/>
    <field name="global" eval="True"/>
    <field name="domain_force">
        [('company_id', 'in', company_ids)]
    </field>
</record>