summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVictor Feyens <vfe@odoo.com>2019-11-19 15:25:40 +0000
committerAntoine Vandevenne (anv) <anv@odoo.com>2019-11-28 10:16:48 +0000
commit5a1d603c5f437f9f05da3b6d5be70929fe8c7fca (patch)
treed86072b81f2405aa7b7002615c32f1131c60ab50
parent2e34d456e80ec6f8aed35cf7864ba7ce17f62f5f (diff)
[ADD] doc: multi-company howtos
closes odoo/odoo#41038 X-original-commit: eaabad20516a73a97b23343a22357659c29945f7 Signed-off-by: Antoine Vandevenne (anv) <AntoineVDV@users.noreply.github.com> Co-authored-by: Antoine Vandevenne (anv) <anv@odoo.com>
-rw-r--r--doc/_static/banners/company.jpgbin0 -> 215140 bytes
-rw-r--r--doc/howtos/backend.rst3
-rw-r--r--doc/howtos/company.rst210
-rw-r--r--doc/reference/orm.rst10
-rw-r--r--doc/tutorials.rst3
-rw-r--r--odoo/fields.py12
-rw-r--r--odoo/models.py14
7 files changed, 244 insertions, 8 deletions
diff --git a/doc/_static/banners/company.jpg b/doc/_static/banners/company.jpg
new file mode 100644
index 00000000000..cdecba72666
--- /dev/null
+++ b/doc/_static/banners/company.jpg
Binary files differ
diff --git a/doc/howtos/backend.rst b/doc/howtos/backend.rst
index 52c73a92b0c..fc2f41663df 100644
--- a/doc/howtos/backend.rst
+++ b/doc/howtos/backend.rst
@@ -2,6 +2,9 @@
.. queue:: backend/series
+.. _howto/base:
+.. _howto/module:
+
=================
Building a Module
=================
diff --git a/doc/howtos/company.rst b/doc/howtos/company.rst
new file mode 100644
index 00000000000..26c5762ca19
--- /dev/null
+++ b/doc/howtos/company.rst
@@ -0,0 +1,210 @@
+:banner: banners/company.jpg
+
+.. _reference/howtos/company:
+
+========================
+Multi-company Guidelines
+========================
+
+.. warning::
+
+ This tutorial requires good knowledge of Odoo.
+ Please refer to the :ref:`basic tutorial <howto/base>` first if needed.
+
+As of version 13.0, a user can be logged in multiple companies at once. This allows the user to
+access information from multiple companies but also to create/edit records in a multi-company
+environment.
+
+If not handled correctly, it may be the source of a lot of inconsistent multi-company behaviors.
+For instance, a user logged in both companies A and B could create a sales order in company A and
+add products belonging to company B to it. It is only when the user will log out from company B that
+access errors will occur for the sales order.
+
+To correctly manage multi-company behaviors, Odoo's ORM provides multiple features:
+
+- :ref:`Company-dependent fields <howto/company/company_dependent>`
+- :ref:`Multi-company consistency <howto/company/check_company>`
+- :ref:`Default company <howto/company/default_company>`
+- :ref:`Views <howto/company/views>`
+- :ref:`Security rules <howto/company/security>`
+
+.. _howto/company/company_dependent:
+
+Company-dependent fields
+------------------------
+
+When a record is available from multiple companies, we must expect that different values will be
+assigned to a given field depending on the company from which the value is set.
+
+For the field of a same record to support several values, it must be defined with the attribute
+`company_dependent` set to `True`.
+
+.. code-block:: python
+
+ 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('force_company')
+ def _compute_display_info(self):
+ for record in self:
+ record.display_info = record.info + record.company_info
+
+.. note:: The `_compute_display_info` method is decorated with `depends_context('force_company')`
+ (see :attr:`~odoo.api.depends_context`) to ensure that the computed field is recomputed
+ depending on the forced/current company (`force_company` context key set, or
+ `self.env.company`).
+
+When a company-dependent field is read, the current company is used to retrieve its value. In other
+words, if a user is logged in companies A and B with A as main company and creates a record for
+company B, the values of company-dependent fields will be that of company A.
+
+To read the values of company-dependent fields set from another company than the current one, the
+context key `force_company` must be set to the ID of the desired company.
+
+.. code-block:: python
+
+ # Accessed as main company (self.env.company)
+ val = record.company_dependent_field
+
+ # Accessed as desired company (company_B)
+ val = record.with_context(force_company=company_B.id).company_dependent_field
+
+
+.. _howto/company/check_company:
+
+Multi-company consistency
+-------------------------
+
+When a record is made shareable between several companies by the mean of a `company_id` field, we
+must take care that it cannot be linked to the record of another company through a relational field.
+For instance, we do not want to have a sales order and its invoice belonging to different companies.
+
+To ensure this multi-company consistency, you must:
+
+* Set the class attribute `_check_company_auto` to `True`.
+* Define relational fields with the attribute `check_company` set to `True` if their model has a
+ `company_id` field.
+
+On each :meth:`~odoo.models.Model.create` and :meth:`~odoo.models.Model.write`, automatic checks
+will be triggered to ensure the multi-company consistency of the record.
+
+.. code-block:: python
+
+ 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)
+
+.. note:: The field `company_id` must not be defined with `check_company=True`.
+
+.. currentmodule:: odoo.models
+.. automethod:: Model._check_company
+
+.. warning:: The `check_company` feature performs a strict check ! It means that if a record has no
+ `company_id` (i.e. the field is not required), it cannot be linked to a record whose
+ `company_id` is set.
+
+.. note::
+
+ When no domain is defined on the field and `check_company` is set to `True`, a default domain is
+ added: `['|', '('company_id', '=', False), ('company_id', '=', company_id)]`
+
+.. _howto/company/default_company:
+
+Default company
+---------------
+
+When the field `company_id` is made required on a model, a good practice is to set a default
+company. It eases the setup flow for the user or even guarantees its validity when the company is
+hidden from the view. Indeed, the company is usually hidden if the user does not have access to
+multiple companies (i.e. when the user does not have the group `base.group_multi_company`).
+
+.. code-block:: python
+
+ 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)
+
+
+.. _howto/company/views:
+
+Views
+-----
+
+As stated in :ref:`above <howto/company/default_company>`, the company is usually hidden
+from the view if the user does not have access to multiple companies. This is tested with the group
+`base.group_multi_company`.
+
+.. code-block:: xml
+
+ <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>
+
+
+.. _howto/company/security:
+
+Security rules
+--------------
+
+When working with records shared across companies or restricted to a single company, we must take
+care that a user does not have access to records belonging to other companies.
+
+This is achieved with security rules based on `company_ids`, which contains the current companies of
+the user (the companies the user checked in the multi-company widget).
+
+.. code-block:: xml
+
+ <!-- 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>
+
+.. code-block:: xml
+
+ <!-- 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>
+
+.. todo:: check_company on company_dependent fields.
diff --git a/doc/reference/orm.rst b/doc/reference/orm.rst
index d9e9a715a57..7075b3a782a 100644
--- a/doc/reference/orm.rst
+++ b/doc/reference/orm.rst
@@ -402,6 +402,16 @@ behavior is desired:
:class:`~odoo.fields.Char`
+.. attribute:: company_id
+
+ Main field name used for Odoo multi-company behavior.
+
+ Used by `:meth:~odoo.models._check_company` to check multi company consistency.
+ Defines whether a record is shared between companies (no value) or only
+ accessible by the users of a given company.
+
+ :class:`~odoo.fields.Many2one`
+ :type: :class:`~odoo.addons.base.models.res_company`
Recordsets
==========
diff --git a/doc/tutorials.rst b/doc/tutorials.rst
index ee30b3c681b..9cb865cf503 100644
--- a/doc/tutorials.rst
+++ b/doc/tutorials.rst
@@ -9,4 +9,5 @@ Tutorials
howtos/website
howtos/backend
howtos/web
- howtos/profilecode \ No newline at end of file
+ howtos/profilecode
+ howtos/company
diff --git a/odoo/fields.py b/odoo/fields.py
index eb26110414e..e6a6d1df1ee 100644
--- a/odoo/fields.py
+++ b/odoo/fields.py
@@ -2329,9 +2329,9 @@ class Many2one(_Relational):
:param bool delegate: set it to ``True`` to make fields of the target model
accessible from the current model (corresponds to ``_inherits``)
- :param check_company: add default domain ``['|', ('company_id', '=', False),
- ('company_id', '=', company_id)]``. Mark the field to be verified in
- ``_check_company``.
+ :param bool check_company: Mark the field to be verified in
+ :meth:`~odoo.models.Model._check_company`. Add a default company
+ domain depending on the field attributes.
"""
type = 'many2one'
column_type = ('int4', 'int4')
@@ -3099,9 +3099,9 @@ class Many2many(_RelationalMulti):
:param dict context: an optional context to use on the client side when
handling that field
- :param check_company: add default domain ``['|', ('company_id', '=', False),
- ('company_id', '=', company_id)]``. Mark the field to be verified in
- ``_check_company``.
+ :param bool check_company: Mark the field to be verified in
+ :meth:`~odoo.models.Model._check_company`. Add a default company
+ domain depending on the field attributes.
:param int limit: optional limit to use upon read
"""
diff --git a/odoo/models.py b/odoo/models.py
index b9b2e7d8380..bb1c305daa2 100644
--- a/odoo/models.py
+++ b/odoo/models.py
@@ -3106,7 +3106,19 @@ Fields:
raise ValidationError(_('A document was modified since you last viewed it (%s:%d)') % (self._description, res[0]))
def _check_company(self, fnames=None):
- """ Check the companies of the values of the given field names. """
+ """ Check the companies of the values of the given field names.
+
+ :param list fnames: names of relational fields to check
+ :raises UserError: if the `company_id` of the value of any field is not
+ in `[False, self.company_id]` (or `self` if
+ :class:`~odoo.addons.base.models.res_company`).
+
+ For :class:`~odoo.addons.base.models.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.
+ """
if fnames is None:
fnames = self._fields