@@ -348,10 +348,126 @@ access to property records.
348
348
Separate company data
349
349
=====================
350
350
351
- tmp
351
+ In enterprise environments, organizations often need to manage multiple distinct entities within the
352
+ same system. This approach allows different branches, subsidiaries, franchises, or even completely
353
+ different companies to operate independently while sharing common resources and functionalities.
354
+
355
+ In Odoo, the **multi-company ** feature enables managing multiple companies within a single database.
356
+ Each company can have its own configuration and data, while still allowing users to access data from
357
+ multiple companies. This is implemented through several key mechanisms:
358
+
359
+ - **Company field **: Adding a `company_id ` many-to-one field to a model allows linking its records
360
+ to a specific company (represented by the generic `res.company ` model). Records can be:
361
+ - **Company-specific **: When `company_id ` has a value, making the record belong to one company.
362
+ - **Company-shared **: When `company_id ` is empty, making the record accessible across all
363
+ companies.
364
+ - **Company-dependent fields **: The `company_dependent=True ` attribute set on a field creates a
365
+ separate value for each company. The values are stored in a JSON object in the database and the
366
+ right value is automatically selected based on the current company of the user.
367
+ - **Company context **: The `with_company(company) ` model method temporarily changes the company
368
+ context when accessing company-dependent fields, allowing to retrieve values from a specific
369
+ company's perspective.
370
+ - **Context-aware dependencies **: The `@api.depends_context('company') ` decorator ensures that
371
+ computed fields are computed depending on the current company (`self.env.company `).
372
+ - **Company consistency checks **: The `_check_company=True ` attribute on a relational field ensures
373
+ that the linked records either belong to the same company, or are shared records. The check can be
374
+ made automatic by setting the `_check_company_auto=True ` class attribute. Otherwise, the check
375
+ must be implemented manually by calling the `_check_company ` model method.
376
+ - **Company rules **: Record rules can be defined to restrict access to records based on the company
377
+ they belong to. When their domain is evaluated, the `company_ids ` variable contains the companies
378
+ selected by the current user in the company switcher.
379
+
380
+ .. example ::
381
+ In the example below, we extend the product and product category models to support multi-company,
382
+ and define record rules to ensure proper data isolation between companies.
383
+
384
+ .. code-block :: python
385
+
386
+ class Product (models .Model ):
387
+ _name = ' product'
388
+ _check_company_auto = True
389
+
390
+ company_id = fields.Many2one(string = " Company" , comodel_name = ' res.company' )
391
+ price = fields.Float(string = " Sales Price" , required = True , default = 100 )
392
+ cost = fields.Float(string = " Manufacturing Cost" , company_dependent = True )
393
+ margin = fields.Float(
394
+ string = " Profit Margin" , compute = ' _compute_margin' , inverse = ' _inverse_margin' , store = True
395
+ )
396
+ category_id = fields.Many2one(
397
+ string = " Category" ,
398
+ comodel_name = ' product.category' ,
399
+ ondelete = ' restrict' ,
400
+ required = True ,
401
+ default = lambda self : self .env.ref(' product_tutorial.category_apparel' ),
402
+ check_company = True ,
403
+ )
404
+
405
+ @api.depends (' price' , ' cost' )
406
+ @api.depends_context (' company' )
407
+ def _compute_margin (self ):
408
+ for product in self :
409
+ product.margin = product.price - product.with_company(product.company_id).cost
410
+
411
+ class ProductCategory (models .Model ):
412
+ _name = ' product.category'
413
+ _check_company_auto = True
414
+
415
+ company_id = fields.Many2one(
416
+ string = " Company" ,
417
+ comodel_name = ' res.company' ,
418
+ required = True ,
419
+ default = lambda self : self .env.company.id,
420
+ )
421
+ product_ids = fields.One2many(
422
+ string = " Products" , comodel_name = ' product' , inverse_name = ' category_id' , check_company = True
423
+ )
424
+
425
+ .. note ::
426
+ - A `company_id ` field is added to the `product ` and `product.category ` models, allowing them
427
+ to be company-specific.
428
+ - The `company_id ` field is optional on the `product ` model, allowing products to be shared
429
+ between companies. It is however required for the `product.category ` model, making
430
+ categories company-specific.
431
+ - It's a good practice to provide a default value for the `company_id ` field, as it eases the
432
+ creation of new records, especially since the company can be hidden from view when the user
433
+ doesn't have access to multiple companies.
434
+ - The `cost ` field is company-dependent, giving each company its own cost value for the same
435
+ product.
436
+ - The `_compute_margin ` method is decorated with `@api.depends_context('company') ` to trigger
437
+ recomputation when switching companies. Although not strictly necessary in this case, it
438
+ also uses `with_company ` to ensure retrieving cost values from te correct company.
439
+ - The `_check_company_auto=True ` attribute is set on both models to ensure that relational
440
+ fields with the `check_company=True ` attribute are properly checked. This prevents linking a
441
+ product to a category belonging to a different company.
442
+
443
+ .. seealso ::
444
+ - For more details, see the :ref: `Multi-company guidelines <reference/howtos/company >`.
352
445
353
446
Let's adapt our real estate app to support multiple agencies while keeping their data separate.
354
447
448
+ .. exercise ::
449
+ #. Create a second company and assign it to the admin user.
450
+ #. In the company switcher, tick the checkbox of the new company to have access to both companies
451
+ at once. Then, switch from one company to another by clicking on the company name.
452
+ #. todo
453
+ #. Make properties and offers records company-specific, but allow property types and tags to be
454
+ shared between companies. Ensure cross-company consistency.
455
+ #. Add record rules to ensure proper data isolation between companies.
456
+
457
+ .. todo: require company_dependent field
458
+ .. todo: require with_company
459
+ .. todo: require context_depends
460
+
461
+ .. tip ::
462
+ - Reminder: The sources for generic models can be found in the
463
+ `base <{GITHUB_PATH}/odoo/addons/base/ >`_ module.
464
+ - For some models, you might prefer linking the company to the parent model's company, through
465
+ a related field, for example
466
+
467
+ .. spoiler :: Solution
468
+
469
+ tmp
470
+
355
471
.. _tutorials/server_framework_101/bypass_security :
356
4
97E3
72
357
473
Bypass security checks
0 commit comments