Skip to content

Commit

Permalink
[ADD] base_multi_company: Create new module
Browse files Browse the repository at this point in the history
* Create new module to provide base multi company logic and mixin
* Add deactivation by company mixin
* Add company_id/ids handling
* Add break after company is found
Squashed commits:
[854cc36] Increase test coverage
[770bd71] Revert hook view create back to model init
[40e803e] Fix apples and oranges
[7a4dfb4] Use registry correctly
[6e9f170] Switch company_id to computed & move company aliased view creation to post init hook
[faa4fc9] Remove active functionality
[fecfb59] Add explicit tests for active and inactive searches
  • Loading branch information
lasley authored and rousseldenis committed Mar 9, 2021
1 parent 35f74dc commit 97700d2
Show file tree
Hide file tree
Showing 12 changed files with 381 additions and 0 deletions.
71 changes: 71 additions & 0 deletions base_multi_company/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
.. image:: https://img.shields.io/badge/licence-lgpl--3-blue.svg
:target: http://www.gnu.org/licenses/LGPL-3.0-standalone.html
:alt: License: LGPL-3

==================
Multi Company Base
==================

This module provides an abstract model to be inherited by models that need to implement multi-company functionality.

Installation
============

To install this module, simply follow the standard install process.

Configuration
=============

No configuration is needed or possible.

Usage
=====

Todo

.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/133/10.0

Known Issues / Roadmap
======================

* Docs

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/OCA/multi-company/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smash it by providing detailed and welcomed
feedback.

Credits
=======

Images
------

* Odoo Community Association:
`Icon <https://github.com/OCA/maintainer-tools/blob/master/template/module/static/description/icon.svg>`_.

Contributors
------------

* Dave Lasley <dave@laslabs.com>
* Pedro M. Baeza <pedro.baeza@tecnativa.com>

Maintainer
----------

.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org

This module is maintained by the OCA.

OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.

To contribute to this module, please visit http://odoo-community.org.
5 changes: 5 additions & 0 deletions base_multi_company/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2017 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).

from . import models
21 changes: 21 additions & 0 deletions base_multi_company/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Copyright 2017 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).

{
'name': 'Multi Company Base',
'summary': 'Provides a base for adding multi-company support to models.',
'version': '10.0.1.0.0',
'author': "LasLabs, Tecnativa, Odoo Community Association (OCA)",
'category': 'base',
'website': 'https://laslabs.com',
'license': 'LGPL-3',
'installable': True,
'application': False,
'depends': [
'base',
],
'data': [
'security/ir.model.access.csv',
],
}
66 changes: 66 additions & 0 deletions base_multi_company/hooks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# -*- coding: utf-8 -*-
# Copyright 2015-2016 Pedro M. Baeza <pedro.baeza@tecnativa.com>
# Copyright 2017 LasLabs Inc.
# License LGPL-3 - See http://www.gnu.org/licenses/lgpl-3.0.html

from odoo import api, SUPERUSER_ID


__all__ = [
'post_init_hook',
'uninstall_hook',
]


def post_init_hook(cr, rule_ref, model_name):
""" Set the `domain_force` and default `company_ids` to `company_id`.
Args:
cr (Cursor): Database cursor to use for operation.
rule_ref (string): XML ID of security rule to write the
`domain_force` from.
model_name (string): Name of Odoo model object to search for
existing records.
"""
with api.Environment.manage():
env = api.Environment(cr, SUPERUSER_ID, {})
# Change access rule
rule = env.ref(rule_ref)
rule.write({
'active': True,
'domain_force': (
"['|', ('company_ids', 'in', user.company_id.ids),"
" ('company_id', '=', False)]"
),
})
# Copy company values
model = env[model_name]
groups = model.read_group([], ['company_id'], ['company_id'])
for group in groups:
if not group['company_id']:
continue
records = model.search(group['__domain'])
records.write({
'company_ids': [(6, 0, [group['company_id'][0]])],
})


def uninstall_hook(cr, rule_ref):
""" Restore product rule to base value.
Args:
cr (Cursor): Database cursor to use for operation.
rule_ref (string): XML ID of security rule to remove the
`domain_force` from.
"""
with api.Environment.manage():
env = api.Environment(cr, SUPERUSER_ID, {})
# Change access rule
rule = env.ref(rule_ref)
rule.write({
'active': False,
'domain_force': (
" ['|', ('company_id', '=', user.company_id.id),"
" ('company_id', '=', False)]"
),
})
6 changes: 6 additions & 0 deletions base_multi_company/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
# Copyright 2017 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).

from . import multi_company_abstract
from . import res_company_assignment
50 changes: 50 additions & 0 deletions base_multi_company/models/multi_company_abstract.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
# Copyright 2017 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).

from odoo import api, fields, models


class MultiCompanyAbstract(models.AbstractModel):

_name = 'multi.company.abstract'
_description = 'Multi-Company Abstract'

company_id = fields.Many2one(
string='Company',
comodel_name='res.company',
compute='_compute_company_id',
inverse='_inverse_company_id',
search='_search_company_id',
)
company_ids = fields.Many2many(
string='Companies',
comodel_name='res.company.assignment',
default=lambda s: s._default_company_ids(),
)

@api.model
def _default_company_ids(self):
Companies = self.env['res.company']
return [
(6, 0, Companies._company_default_get().ids),
]

@api.multi
@api.depends('company_ids')
def _compute_company_id(self):
for record in self:
for company in record.company_ids:
if company.id in self.env.user.company_ids.ids:
record.company_id = company.id
break

@api.multi
def _inverse_company_id(self):
for record in self:
if record.company_id.id not in record.company_ids.ids:
record.company_ids = [(4, record.company_id.id)]

@api.model
def _search_company_id(self, operator, value):
return [('company_ids', operator, value)]
22 changes: 22 additions & 0 deletions base_multi_company/models/res_company_assignment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Copyright 2017 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).

from odoo import api, fields, models, tools


class ResCompanyAssignment(models.Model):

_name = 'res.company.assignment'
_auto = False

name = fields.Char()

@api.model_cr
def init(self):
tools.drop_view_if_exists(self.env.cr, self._table)
self.env.cr.execute("""
CREATE OR REPLACE VIEW %s
AS SELECT id, name
FROM res_company;
""" % (self._table))
3 changes: 3 additions & 0 deletions base_multi_company/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_res_company_assignment_group_erp_manager,res_company_assignment group_erp_manager,model_res_company_assignment,base.group_erp_manager,1,1,1,1
access_res_company_assignment_group_user,res_company_assignment group_user,model_res_company_assignment,base.group_user,1,0,0,0
Binary file added base_multi_company/static/description/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions base_multi_company/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
# License LGPL-3 - See http://www.gnu.org/licenses/lgpl-3.0.html

from . import test_multi_company_abstract
from . import test_res_company_assignment
106 changes: 106 additions & 0 deletions base_multi_company/tests/test_multi_company_abstract.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# -*- coding: utf-8 -*-
# Copyright 2017 LasLabs Inc.
# License LGPL-3 - See http://www.gnu.org/licenses/lgpl-3.0.html

from odoo import fields, models
from odoo.tests import common


class MultiCompanyAbstractTester(models.TransientModel):
_name = 'multi.company.abstract.tester'
_inherit = 'multi.company.abstract'

name = fields.Char()


class TestMultiCompanyAbstract(common.SavepointCase):

@classmethod
def _init_test_model(cls, model_cls):
""" It builds a model from model_cls in order to test abstract models.
Note that this does not actually create a table in the database, so
there may be some unidentified edge cases.
Args:
model_cls (odoo.models.BaseModel): Class of model to initialize
Returns:
model_cls: Instance
"""
registry = cls.env.registry
cr = cls.env.cr
inst = model_cls._build_model(registry, cr)
model = cls.env[model_cls._name].with_context(todo=[])
model._prepare_setup()
model._setup_base(partial=False)
model._setup_fields(partial=False)
model._setup_complete()
model._auto_init()
model.init()
model._auto_end()
cls.test_model_record = cls.env['ir.model'].search([
('name', '=', model._name),
])
return inst

@classmethod
def setUpClass(cls):
super(TestMultiCompanyAbstract, cls).setUpClass()
cls.env.registry.enter_test_mode()
cls._init_test_model(MultiCompanyAbstractTester)
cls.test_model = cls.env[MultiCompanyAbstractTester._name]

@classmethod
def tearDownClass(cls):
cls.env.registry.leave_test_mode()
super(TestMultiCompanyAbstract, cls).tearDownClass()

def setUp(self):
super(TestMultiCompanyAbstract, self).setUp()
self.Model = self.env['multi.company.abstract.tester']
self.record = self.Model.create({
'name': 'test',
'active': True,
})
Companies = self.env['res.company']
self.company_1 = Companies._company_default_get()
self.company_2 = Companies.create({
'name': 'Test Co 2',
})

def add_company(self, company):
""" Add company to the test record. """
self.record.company_ids = [4, company.id]

def switch_user_company(self, user, company):
""" Add a company to the user's allowed & set to current. """
user.write({
'company_ids': [(6, 0, (company + user.company_ids).ids)],
'company_id': company.id,
})

def test_default_company_ids(self):
""" It should set company_ids to the default company. """
self.assertEqual(
self.record.company_ids.ids,
self.company_1.ids,
)

def test_compute_company_id(self):
""" It should set company_id to the top of the company_ids stack. """
self.add_company(self.company_2)
self.assertEqual(
self.record.company_id.id,
self.record.company_ids[0].id,
)

def test_inverse_company_id(self):
""" It should add the company using company_id. """
self.record.company_id = self.company_2
self.assertIn(self.company_2.id, self.record.company_ids.ids)

def test_search_company_id(self):
""" It should return correct record by searching company_id. """
record = self.env['multi.company.abstract.tester'].search([
('company_id', '=', self.company_1.id),
('id', '=', self.record.id),
])
self.assertEqual(record, self.record)
26 changes: 26 additions & 0 deletions base_multi_company/tests/test_res_company_assignment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# Copyright 2017 LasLabs Inc.
# License LGPL-3 - See http://www.gnu.org/licenses/lgpl-3.0.html

from odoo.tests import common


class TestResCompanyAssignment(common.TransactionCase):

def setUp(self):
super(TestResCompanyAssignment, self).setUp()
self.View = self.env['res.company.assignment']
self.Model = self.env['res.company']
self.views = self.View.search([])

def test_equality_len(self):
""" The record lengths should match between mirror and original. """
len_views = len(self.views)
len_records = len(self.Model.search([]))
self.assertEqual(len_views, len_records)

def test_equality_data(self):
""" The record data should match between mirror and original. """
view = self.views[0]
record = self.Model.browse(view.id)
self.assertEqual(view.name, record.name)

0 comments on commit 97700d2

Please sign in to comment.