Merge PR #270 into 17.0

Signed-off-by dreispt
This commit is contained in:
OCA-git-bot
2024-03-09 14:24:42 +00:00
24 changed files with 1175 additions and 0 deletions

View File

@@ -0,0 +1,131 @@
=====================
User roles by company
=====================
..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:412b244590f18253b21a2f15b15c2cbbc8ec3decfc64dbf12973646123cb3193
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--backend-lightgray.png?logo=github
:target: https://github.com/OCA/server-backend/tree/17.0/base_user_role_company
:alt: OCA/server-backend
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/server-backend-17-0/server-backend-17-0-base_user_role_company
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/server-backend&target_branch=17.0
:alt: Try me on Runboat
|badge1| |badge2| |badge3| |badge4| |badge5|
Enable User Roles depending on the Companies selected.
A company specific Role will only be enabled if it is set for **all**
the currently selected companies.
For example, if a user is "Sales Manager" only for Company A, it will
see that role enabled only if Company A is selected. If the user selects
Company A and Company B, then the "Sales Manager" role won't be enabled.
**Table of contents**
.. contents::
:local:
Configuration
=============
Roles are set on the User form.
The "Company" additional column allows to set a Role as only valid for
specific companies.
There is also a "Active Role" techincal field, only visible in developer
mode. It shows what roles are active, after applying the company
selection rules.
Usage
=====
Select the active companies from the web client widget, near the top
right corner. When doing so, the User's security Groups are recomputed,
based on the Roles.
When the user changes the company selection, only the groups available
to all active companies will be activated.
For example:
- A "SALES PERSON" and a "SALES MANAGER" roles are created.
- A user is assigned to the roles:
- "SALES PERSON", with no specific company assigned (meaning all)
- "SALES MANAGER" only to "My Company (Chicago)"
- When selecting active companies from the UI widget:
- If only "My Company (San Francisco)" is active, "SALES PERSON"
will be active.
- If only "My Company (Chicago)" is active, "SALES PERSON" and
"SALES MANAGER" will be active.
- If both "My Company (San Francisco)" and "My Company (Chicago)" is
active, "SALES PERSON" will be active.
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/server-backend/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/server-backend/issues/new?body=module:%20base_user_role_company%0Aversion:%2017.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits
=======
Authors
-------
* Open Source Integrators
Contributors
------------
`Open Source Integrators <http://opensourceintegrators.com>`__
- Daniel Reis <dreis@opensourceintegrators.com>
- Chandresh Thakkar <cthakkr@opensourceintegrators.com>
- Urvisha Desai <udesai@opensourceintegrators.com>
`WeSolved <http://wesolved.com>`__
- Robin Conjour <rconjour@wesolved.com>
Maintainers
-----------
This module is maintained by the OCA.
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
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.
This module is part of the `OCA/server-backend <https://github.com/OCA/server-backend/tree/17.0/base_user_role_company>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View File

@@ -0,0 +1,5 @@
# Copyright (C) 2021 Open Source Integrators
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import controllers
from . import models

View File

@@ -0,0 +1,20 @@
# Copyright (C) 2021 Open Source Integrators
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
"name": "User roles by company",
"version": "17.0.1.0.0",
"category": "Tools",
"author": "Open Source Integrators, Odoo Community Association (OCA)",
"license": "AGPL-3",
"website": "https://github.com/OCA/server-backend",
"depends": ["base_user_role"],
"data": [
"views/role.xml",
"views/user.xml",
],
"installable": True,
"auto_install": True,
"maintainer": "dreispt",
"development_status": "Beta",
}

View File

@@ -0,0 +1 @@
from . import main

View File

@@ -0,0 +1,16 @@
# Copyright (C) 2022 Open Source Integrators
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import http
from odoo.addons.web.controllers.home import Home
class HomeExtended(Home):
@http.route()
def web_load_menus(self, unique):
response = super().web_load_menus(unique)
# On logout & re-login we could see wrong menus being rendered
# To avoid this, menu http cache must be disabled
response.headers.remove("Cache-Control")
return response

View File

@@ -0,0 +1,58 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * base_user_role_company
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: base_user_role_company
#: model:ir.model.fields,field_description:base_user_role_company.field_res_users_role_line__allowed_company_ids
msgid "Companies"
msgstr ""
#. module: base_user_role_company
#: model:ir.model.fields,field_description:base_user_role_company.field_res_users_role_line__company_id
msgid "Company"
msgstr ""
#. module: base_user_role_company
#: model:ir.model,name:base_user_role_company.model_ir_http
msgid "HTTP Routing"
msgstr ""
#. module: base_user_role_company
#: model:ir.model.fields,help:base_user_role_company.field_res_users_role_line__company_id
msgid ""
"If set, this role only applies when this is the main company selected. "
"Otherwise it applies to all companies."
msgstr ""
#. module: base_user_role_company
#: model:ir.model.constraint,message:base_user_role_company.constraint_res_users_role_line_user_role_uniq
msgid "Roles can be assigned to a user only once at a time"
msgstr ""
#. module: base_user_role_company
#: model:ir.model,name:base_user_role_company.model_res_users
msgid "User"
msgstr ""
#. module: base_user_role_company
#. odoo-python
#: code:addons/base_user_role_company/models/role.py:0
#, python-format
msgid "User \"%(user)s\" does not have access to the company \"%(company)s\""
msgstr ""
#. module: base_user_role_company
#: model:ir.model,name:base_user_role_company.model_res_users_role_line
msgid "Users associated to a role"
msgstr ""

View File

@@ -0,0 +1,67 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * base_user_role_company
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2023-11-08 14:36+0000\n"
"Last-Translator: Ivorra78 <informatica@totmaterial.es>\n"
"Language-Team: none\n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.17\n"
#. module: base_user_role_company
#: model:ir.model.fields,field_description:base_user_role_company.field_res_users_role_line__allowed_company_ids
msgid "Companies"
msgstr "Compañías"
#. module: base_user_role_company
#: model:ir.model.fields,field_description:base_user_role_company.field_res_users_role_line__company_id
msgid "Company"
msgstr "Compañía"
#. module: base_user_role_company
#: model:ir.model,name:base_user_role_company.model_ir_http
msgid "HTTP Routing"
msgstr "Enrutamiento HTTP"
#. module: base_user_role_company
#: model:ir.model.fields,help:base_user_role_company.field_res_users_role_line__company_id
msgid ""
"If set, this role only applies when this is the main company selected. "
"Otherwise it applies to all companies."
msgstr ""
"Si se establece, este rol sólo se aplica cuando ésta es la compañía "
"principal seleccionada. De lo contrario, se aplica a todas las compañías."
#. module: base_user_role_company
#: model:ir.model.constraint,message:base_user_role_company.constraint_res_users_role_line_user_role_uniq
msgid "Roles can be assigned to a user only once at a time"
msgstr "Las funciones sólo pueden asignarse a un usuario una vez cada vez"
#. module: base_user_role_company
#: model:ir.model,name:base_user_role_company.model_res_users
msgid "User"
msgstr "Usuario"
#. module: base_user_role_company
#. odoo-python
#: code:addons/base_user_role_company/models/role.py:0
#, python-format
msgid "User \"%(user)s\" does not have access to the company \"%(company)s\""
msgstr "El usuario \"%(user)s\" no tiene acceso a la empresa \"%(company)s\""
#. module: base_user_role_company
#: model:ir.model,name:base_user_role_company.model_res_users_role_line
msgid "Users associated to a role"
msgstr "Usuarios asociados a un papel"
#, python-format
#~ msgid "User \"{}\" does not have access to the company \"{}\""
#~ msgstr "Usuario \"{}\" no tiene acceso a la compañía \"{}\""

View File

@@ -0,0 +1,82 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * base_user_role_company
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 14.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2024-01-03 14:33+0000\n"
"Last-Translator: mymage <stefano.consolaro@mymage.it>\n"
"Language-Team: none\n"
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.17\n"
#. module: base_user_role_company
#: model:ir.model.fields,field_description:base_user_role_company.field_res_users_role_line__allowed_company_ids
msgid "Companies"
msgstr "Aziende"
#. module: base_user_role_company
#: model:ir.model.fields,field_description:base_user_role_company.field_res_users_role_line__company_id
msgid "Company"
msgstr "Azienda"
#. module: base_user_role_company
#: model:ir.model,name:base_user_role_company.model_ir_http
msgid "HTTP Routing"
msgstr "Instradamento HTTP"
#. module: base_user_role_company
#: model:ir.model.fields,help:base_user_role_company.field_res_users_role_line__company_id
msgid ""
"If set, this role only applies when this is the main company selected. "
"Otherwise it applies to all companies."
msgstr ""
"Se impostato, questo ruolo si applica solo quando questa è l'azienda "
"principale selezionata. Altrimenti vale per tutte le aziende."
#. module: base_user_role_company
#: model:ir.model.constraint,message:base_user_role_company.constraint_res_users_role_line_user_role_uniq
msgid "Roles can be assigned to a user only once at a time"
msgstr "I ruoli possono essere assegnati all'utente solo uno alla volta"
#. module: base_user_role_company
#: model:ir.model,name:base_user_role_company.model_res_users
msgid "User"
msgstr "Utente"
#. module: base_user_role_company
#. odoo-python
#: code:addons/base_user_role_company/models/role.py:0
#, python-format
msgid "User \"%(user)s\" does not have access to the company \"%(company)s\""
msgstr "L'utente \"%(user)s\" non ha accesso all'azienda \"%(company)s\""
#. module: base_user_role_company
#: model:ir.model,name:base_user_role_company.model_res_users_role_line
msgid "Users associated to a role"
msgstr "Utenti associati al ruolo"
#, python-format
#~ msgid "User \"{}\" does not have access to the company \"{}\""
#~ msgstr "L'utente \"{}\" non ha l'accesso all'azienda \"{}\""
#~ msgid "Display Name"
#~ msgstr "Nome visualizzato"
#~ msgid "ID"
#~ msgstr "ID"
#~ msgid "Last Modified on"
#~ msgstr "Ultima modifica il"
#~ msgid "Users"
#~ msgstr "Utenti"
#~ msgid "Active Role"
#~ msgstr "Ruolo attivo"

View File

@@ -0,0 +1,67 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * base_user_role_company
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2023-08-31 10:35+0000\n"
"Last-Translator: Pedro Castro Silva <pedrocs@exo.pt>\n"
"Language-Team: none\n"
"Language: pt\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 4.17\n"
#. module: base_user_role_company
#: model:ir.model.fields,field_description:base_user_role_company.field_res_users_role_line__allowed_company_ids
msgid "Companies"
msgstr "Empresas"
#. module: base_user_role_company
#: model:ir.model.fields,field_description:base_user_role_company.field_res_users_role_line__company_id
msgid "Company"
msgstr "Empresa"
#. module: base_user_role_company
#: model:ir.model,name:base_user_role_company.model_ir_http
msgid "HTTP Routing"
msgstr "Encaminhamento HTTP"
#. module: base_user_role_company
#: model:ir.model.fields,help:base_user_role_company.field_res_users_role_line__company_id
msgid ""
"If set, this role only applies when this is the main company selected. "
"Otherwise it applies to all companies."
msgstr ""
"Se atribuída, esta função será aplicada apenas quando esta é a empresa "
"principal selecionada. Caso contrário, aplicar-se-á a todas as empresas."
#. module: base_user_role_company
#: model:ir.model.constraint,message:base_user_role_company.constraint_res_users_role_line_user_role_uniq
msgid "Roles can be assigned to a user only once at a time"
msgstr "As funções podem ser atribuídas a um utilizador apenas uma vez"
#. module: base_user_role_company
#: model:ir.model,name:base_user_role_company.model_res_users
msgid "User"
msgstr "Utilizador"
#. module: base_user_role_company
#. odoo-python
#: code:addons/base_user_role_company/models/role.py:0
#, python-format
msgid "User \"%(user)s\" does not have access to the company \"%(company)s\""
msgstr ""
#. module: base_user_role_company
#: model:ir.model,name:base_user_role_company.model_res_users_role_line
msgid "Users associated to a role"
msgstr "Utilizadores associados a uma função"
#, python-format
#~ msgid "User \"{}\" does not have access to the company \"{}\""
#~ msgstr "O utilizador \"{}\" não tem acesso à empresa \"{}\""

View File

@@ -0,0 +1,6 @@
# Copyright (C) 2021 Open Source Integrators
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import role
from . import user
from . import ir_http

View File

@@ -0,0 +1,23 @@
# Copyright (C) 2021 Open Source Integrators
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models
from odoo.http import request
class IrHttp(models.AbstractModel):
_inherit = "ir.http"
def session_info(self):
"""
Based on the selected companies (cids),
calculate the roles to enable.
A role should be enabled only when it applies to all selected companies.
"""
result = super().session_info()
if self.env.user.role_line_ids:
cids_str = request.httprequest.cookies.get("cids", str(self.env.company.id))
cids = [int(cid) for cid in cids_str.split(",")]
# The first element of cids is the currently selected company
self.env.user.set_groups_from_roles(company_id=cids[0])
return result

View File

@@ -0,0 +1,41 @@
# Copyright (C) 2021 Open Source Integrators
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
class ResUsersRoleLine(models.Model):
_inherit = "res.users.role.line"
allowed_company_ids = fields.Many2many(related="user_id.company_ids")
company_id = fields.Many2one(
"res.company",
"Company",
domain="[('id', 'in', allowed_company_ids)]",
help="If set, this role only applies when this is the main company selected."
" Otherwise it applies to all companies.",
)
@api.constrains("user_id", "company_id")
def _check_company(self):
for record in self:
if (
record.company_id
and record.company_id != record.user_id.company_id
and record.company_id not in record.user_id.company_ids
):
raise ValidationError(
_(
'User "%(user)s" does not have access to the company "%(company)s"'
)
% {"user": record.user_id.name, "company": record.company_id.name}
)
_sql_constraints = [
(
"user_role_uniq",
"unique (user_id,role_id,company_id)",
"Roles can be assigned to a user only once at a time",
)
]

View File

@@ -0,0 +1,35 @@
# Copyright (C) 2021 Open Source Integrators
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, models
class ResUsers(models.Model):
_inherit = "res.users"
@classmethod
def authenticate(cls, db, login, password, user_agent_env):
uid = super().authenticate(db, login, password, user_agent_env)
# On login, ensure the proper roles are applied
# The last Role applied may not be the correct one,
# sonce the new session current company can be different
with cls.pool.cursor() as cr:
env = api.Environment(cr, uid, {})
if env.user.role_line_ids:
env.user.set_groups_from_roles()
return uid
def _get_enabled_roles(self):
res = super()._get_enabled_roles()
# Enable only the Roles corresponing to the currently selected company
if self.role_line_ids:
res = res.filtered(
lambda x: not x.company_id or x.company_id == self.env.company
)
return res
def set_groups_from_roles(self, force=False, company_id=False):
# When using the Company Switcher widget, the self.env.company is not yet set
if company_id:
self = self.with_company(company_id)
return super().set_groups_from_roles(force=force)

View File

@@ -0,0 +1,3 @@
[build-system]
requires = ["whool"]
build-backend = "whool.buildapi"

View File

@@ -0,0 +1,8 @@
Roles are set on the User form.
The "Company" additional column allows to set a Role as only valid for
specific companies.
There is also a "Active Role" techincal field, only visible in developer
mode. It shows what roles are active, after applying the company
selection rules.

View File

@@ -0,0 +1,9 @@
[Open Source Integrators](http://opensourceintegrators.com)
> - Daniel Reis \<<dreis@opensourceintegrators.com>\>
> - Chandresh Thakkar \<<cthakkr@opensourceintegrators.com>\>
> - Urvisha Desai \<<udesai@opensourceintegrators.com>\>
[WeSolved](http://wesolved.com)
> - Robin Conjour \<<rconjour@wesolved.com>\>

View File

@@ -0,0 +1,8 @@
Enable User Roles depending on the Companies selected.
A company specific Role will only be enabled if it is set for **all**
the currently selected companies.
For example, if a user is "Sales Manager" only for Company A, it will
see that role enabled only if Company A is selected. If the user selects
Company A and Company B, then the "Sales Manager" role won't be enabled.

View File

@@ -0,0 +1,22 @@
Select the active companies from the web client widget, near the top
right corner. When doing so, the User's security Groups are recomputed,
based on the Roles.
When the user changes the company selection, only the groups available
to all active companies will be activated.
For example:
- A "SALES PERSON" and a "SALES MANAGER" roles are created.
- A user is assigned to the roles:
- "SALES PERSON", with no specific company assigned (meaning all)
- "SALES MANAGER" only to "My Company (Chicago)"
- When selecting active companies from the UI widget:
- If only "My Company (San Francisco)" is active, "SALES PERSON" will
be active.
- If only "My Company (Chicago)" is active, "SALES PERSON" and "SALES
MANAGER" will be active.
- If both "My Company (San Francisco)" and "My Company (Chicago)" is
active, "SALES PERSON" will be active.

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@@ -0,0 +1,474 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
<title>User roles by company</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: grey; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="user-roles-by-company">
<h1 class="title">User roles by company</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:412b244590f18253b21a2f15b15c2cbbc8ec3decfc64dbf12973646123cb3193
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/server-backend/tree/17.0/base_user_role_company"><img alt="OCA/server-backend" src="https://img.shields.io/badge/github-OCA%2Fserver--backend-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/server-backend-17-0/server-backend-17-0-base_user_role_company"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/server-backend&amp;target_branch=17.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>Enable User Roles depending on the Companies selected.</p>
<p>A company specific Role will only be enabled if it is set for <strong>all</strong>
the currently selected companies.</p>
<p>For example, if a user is “Sales Manager” only for Company A, it will
see that role enabled only if Company A is selected. If the user selects
Company A and Company B, then the “Sales Manager” role wont be enabled.</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#configuration" id="toc-entry-1">Configuration</a></li>
<li><a class="reference internal" href="#usage" id="toc-entry-2">Usage</a></li>
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-3">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-4">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-5">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="toc-entry-6">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-7">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="configuration">
<h1><a class="toc-backref" href="#toc-entry-1">Configuration</a></h1>
<p>Roles are set on the User form.</p>
<p>The “Company” additional column allows to set a Role as only valid for
specific companies.</p>
<p>There is also a “Active Role” techincal field, only visible in developer
mode. It shows what roles are active, after applying the company
selection rules.</p>
</div>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#toc-entry-2">Usage</a></h1>
<p>Select the active companies from the web client widget, near the top
right corner. When doing so, the Users security Groups are recomputed,
based on the Roles.</p>
<p>When the user changes the company selection, only the groups available
to all active companies will be activated.</p>
<p>For example:</p>
<ul class="simple">
<li>A “SALES PERSON” and a “SALES MANAGER” roles are created.</li>
<li>A user is assigned to the roles:<ul>
<li>“SALES PERSON”, with no specific company assigned (meaning all)</li>
<li>“SALES MANAGER” only to “My Company (Chicago)”</li>
</ul>
</li>
<li>When selecting active companies from the UI widget:<ul>
<li>If only “My Company (San Francisco)” is active, “SALES PERSON”
will be active.</li>
<li>If only “My Company (Chicago)” is active, “SALES PERSON” and
“SALES MANAGER” will be active.</li>
<li>If both “My Company (San Francisco)” and “My Company (Chicago)” is
active, “SALES PERSON” will be active.</li>
</ul>
</li>
</ul>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#toc-entry-3">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/server-backend/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/server-backend/issues/new?body=module:%20base_user_role_company%0Aversion:%2017.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#toc-entry-4">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#toc-entry-5">Authors</a></h2>
<ul class="simple">
<li>Open Source Integrators</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#toc-entry-6">Contributors</a></h2>
<p><a class="reference external" href="http://opensourceintegrators.com">Open Source Integrators</a></p>
<blockquote>
<ul class="simple">
<li>Daniel Reis &lt;<a class="reference external" href="mailto:dreis&#64;opensourceintegrators.com">dreis&#64;opensourceintegrators.com</a>&gt;</li>
<li>Chandresh Thakkar &lt;<a class="reference external" href="mailto:cthakkr&#64;opensourceintegrators.com">cthakkr&#64;opensourceintegrators.com</a>&gt;</li>
<li>Urvisha Desai &lt;<a class="reference external" href="mailto:udesai&#64;opensourceintegrators.com">udesai&#64;opensourceintegrators.com</a>&gt;</li>
</ul>
</blockquote>
<p><a class="reference external" href="http://wesolved.com">WeSolved</a></p>
<blockquote>
<ul class="simple">
<li>Robin Conjour &lt;<a class="reference external" href="mailto:rconjour&#64;wesolved.com">rconjour&#64;wesolved.com</a>&gt;</li>
</ul>
</blockquote>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#toc-entry-7">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
<p>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.</p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/server-backend/tree/17.0/base_user_role_company">OCA/server-backend</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1 @@
from . import test_role_per_company

View File

@@ -0,0 +1,63 @@
# Copyright 2021 Open Source Integrators
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
from odoo.tests.common import TransactionCase
class TestUserRoleCompany(TransactionCase):
def setUp(self):
super().setUp()
# COMPANIES
self.Company = self.env["res.company"]
self.company1 = self.env.ref("base.main_company")
self.company2 = self.Company.create({"name": "company2"})
# GROUPS for roles
self.groupA = self.env.ref("base.group_user")
self.groupB = self.env.ref("base.group_system")
self.groupC = self.env.ref("base.group_partner_manager")
# ROLES
self.Role = self.env["res.users.role"]
self.roleA = self.Role.create({"name": "ROLE All Companies"})
self.roleA.implied_ids |= self.groupA
self.roleB = self.Role.create({"name": "ROLE Company 1"})
self.roleB.implied_ids |= self.groupB
self.roleC = self.Role.create({"name": "ROLE Company 1 and 2"})
self.roleC.implied_ids |= self.groupC
# USER
# ==Role=== ==Company== C1 C2 C1+C2
# Role A Yes Yes Yes
# Role B Company1 Yes
# Role C Company1 Yes Yes
# Role C Company2 Yes Yes
self.User = self.env["res.users"]
user_vals = {
"name": "ROLES TEST USER",
"login": "test_user",
"company_ids": [(6, 0, [self.company1.id, self.company2.id])],
"role_line_ids": [
(0, 0, {"role_id": self.roleA.id}),
(0, 0, {"role_id": self.roleB.id, "company_id": self.company1.id}),
(0, 0, {"role_id": self.roleC.id, "company_id": self.company1.id}),
(0, 0, {"role_id": self.roleC.id, "company_id": self.company2.id}),
],
}
self.test_user = self.User.create(user_vals)
def test_110_company_1(self):
"Company 1 selected: Roles A, B and C are enabled"
self.test_user.set_groups_from_roles(company_id=self.company1.id)
expected = self.groupA | self.groupB | self.groupC
found = self.test_user.groups_id.filtered(lambda x: x in expected)
self.assertEqual(expected, found)
def test_120_company_2(self):
"Company 2 selected: Roles A and C are enabled"
self.test_user.set_groups_from_roles(company_id=self.company2.id)
enabled = self.test_user.groups_id
expected = self.groupA | self.groupC
found = enabled.filtered(lambda x: x in expected)
self.assertEqual(expected, found)
not_expected = self.groupB
found = enabled.filtered(lambda x: x in not_expected)
self.assertFalse(found)

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="view_res_users_form_inherit_company" model="ir.ui.view">
<field name="name">res.users.form.inherit.company</field>
<field name="model">res.users</field>
<field name="inherit_id" ref="base_user_role.view_res_users_form_inherit" />
<field name="arch" type="xml">
<xpath
expr="//field[@name='role_line_ids']//field[@name='role_id']"
position="after"
>
<field name="allowed_company_ids" invisible="1" />
<field name="company_id" groups="base.group_multi_company" />
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2024 Camptocamp SA
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<record id="view_res_users_form_inherit" model="ir.ui.view">
<field name="name">res.users.form.inherit</field>
<field name="model">res.users</field>
<field name="inherit_id" ref="base_user_role.view_res_users_form_inherit" />
<field name="arch" type="xml">
<field name="role_id" position="attributes">
<!-- Remove the domain on existing roles. This allows to set
the same role multiple times on a user as long as it is set on a
different company. -->
<attribute name="domain">[]</attribute>
</field>
</field>
</record>
</odoo>