diff --git a/pos_operating_unit/README.rst b/pos_operating_unit/README.rst new file mode 100644 index 0000000000..6441dce708 --- /dev/null +++ b/pos_operating_unit/README.rst @@ -0,0 +1,100 @@ +======================== +POS with Operating Units +======================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:ff2417b3a2178379e4a963b4b7fed6282bfb952b07931806cec94e7806d49d61 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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-LGPL--3-blue.png + :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html + :alt: License: LGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Foperating--unit-lightgray.png?logo=github + :target: https://github.com/OCA/operating-unit/tree/18.0/pos_operating_unit + :alt: OCA/operating-unit +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/operating-unit-18-0/operating-unit-18-0-pos_operating_unit + :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/operating-unit&target_branch=18.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module adds a field "operating unit" in POS with the only goal of +restricting visibility (therefore access) to users with same OU allowed. + +This also applies to sessions, orders and payments in POS. + +A "Point of sale" field is added to orders and payments for clarity. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +In POS config, set operating unit(s) allowed to see this POS. + +Known issues / Roadmap +====================== + +This module only manages visibility of POS and related orders. If a more +complete "pos_operating_unit" is proposed and merged, then it should +cover features of this module which can be discontinued. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub 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 `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Ilyas +* Ooops404 + +Contributors +------------ + +- Ooops404 https://ooops404.com + + - Ilyas irazor147@gmail.com + +- Nybble Group https://www.nybblegroup.com + + - Cristiam Carreño cristiamcarreno@gmail.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/operating-unit `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/pos_operating_unit/__init__.py b/pos_operating_unit/__init__.py new file mode 100644 index 0000000000..0650744f6b --- /dev/null +++ b/pos_operating_unit/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/pos_operating_unit/__manifest__.py b/pos_operating_unit/__manifest__.py new file mode 100644 index 0000000000..66ed46b605 --- /dev/null +++ b/pos_operating_unit/__manifest__.py @@ -0,0 +1,14 @@ +{ + "name": "POS with Operating Units", + "version": "18.0.1.0.0", + "author": "Ilyas, Ooops404, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/operating-unit", + "category": "Point of sale", + "depends": ["point_of_sale", "operating_unit"], + "license": "LGPL-3", + "data": [ + "security/pos_security.xml", + "views/pos_views.xml", + "views/res_config_settings_views.xml", + ], +} diff --git a/pos_operating_unit/i18n/es.po b/pos_operating_unit/i18n/es.po new file mode 100644 index 0000000000..0d0af125c3 --- /dev/null +++ b/pos_operating_unit/i18n/es.po @@ -0,0 +1,83 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * pos_operating_unit +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2024-01-11 19:34+0000\n" +"Last-Translator: Ivorra78 \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: pos_operating_unit +#: model:ir.model,name:pos_operating_unit.model_res_config_settings +msgid "Config Settings" +msgstr "" + +#. module: pos_operating_unit +#: model:ir.model.fields,field_description:pos_operating_unit.field_pos_config__operating_unit_ids +#: model:ir.model.fields,field_description:pos_operating_unit.field_pos_order__operating_unit_ids +#: model:ir.model.fields,field_description:pos_operating_unit.field_pos_order_line__operating_unit_ids +#: model:ir.model.fields,field_description:pos_operating_unit.field_pos_payment__operating_unit_ids +#: model:ir.model.fields,field_description:pos_operating_unit.field_pos_session__operating_unit_ids +#: model:ir.model.fields,field_description:pos_operating_unit.field_res_config_settings__pos_operating_unit_ids +#: model_terms:ir.ui.view,arch_db:pos_operating_unit.pos_config_view_form_inherit_ou +#: model_terms:ir.ui.view,arch_db:pos_operating_unit.res_config_settings_view_form +msgid "Operating Units" +msgstr "Unidades Operativas" + +#. module: pos_operating_unit +#: model_terms:ir.ui.view,arch_db:pos_operating_unit.pos_config_view_form_inherit_ou +#: model_terms:ir.ui.view,arch_db:pos_operating_unit.res_config_settings_view_form +msgid "Operating units used for this POS" +msgstr "" + +#. module: pos_operating_unit +#: model:ir.model.fields,field_description:pos_operating_unit.field_pos_order__config_id +#: model:ir.model.fields,field_description:pos_operating_unit.field_pos_payment__config_id +msgid "Point of Sale" +msgstr "Punto de Venta" + +#. module: pos_operating_unit +#: model:ir.model,name:pos_operating_unit.model_pos_config +msgid "Point of Sale Configuration" +msgstr "Configuración del Punto de Venta" + +#. module: pos_operating_unit +#: model:ir.model,name:pos_operating_unit.model_pos_order_line +msgid "Point of Sale Order Lines" +msgstr "Líneas de Pedidos en el Punto de Venta" + +#. module: pos_operating_unit +#: model:ir.model,name:pos_operating_unit.model_pos_order +msgid "Point of Sale Orders" +msgstr "Pedidos en el Punto de Venta" + +#. module: pos_operating_unit +#: model:ir.model,name:pos_operating_unit.model_pos_payment +msgid "Point of Sale Payments" +msgstr "Pagos en el Punto de Venta" + +#. module: pos_operating_unit +#: model:ir.model,name:pos_operating_unit.model_pos_session +msgid "Point of Sale Session" +msgstr "Sesión del Punto de Venta" + +#~ msgid "Display Name" +#~ msgstr "Mostrar Nombre" + +#~ msgid "ID" +#~ msgstr "ID" + +#~ msgid "Last Modified on" +#~ msgstr "Última Modificación el" + +#~ msgid "The physical point of sale you will use." +#~ msgstr "El punto de venta físico que usted utilizará." diff --git a/pos_operating_unit/i18n/it.po b/pos_operating_unit/i18n/it.po new file mode 100644 index 0000000000..dde87bde06 --- /dev/null +++ b/pos_operating_unit/i18n/it.po @@ -0,0 +1,83 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * pos_operating_unit +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2024-08-30 14:06+0000\n" +"Last-Translator: mymage \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 5.6.2\n" + +#. module: pos_operating_unit +#: model:ir.model,name:pos_operating_unit.model_res_config_settings +msgid "Config Settings" +msgstr "Impostazioni configurazione" + +#. module: pos_operating_unit +#: model:ir.model.fields,field_description:pos_operating_unit.field_pos_config__operating_unit_ids +#: model:ir.model.fields,field_description:pos_operating_unit.field_pos_order__operating_unit_ids +#: model:ir.model.fields,field_description:pos_operating_unit.field_pos_order_line__operating_unit_ids +#: model:ir.model.fields,field_description:pos_operating_unit.field_pos_payment__operating_unit_ids +#: model:ir.model.fields,field_description:pos_operating_unit.field_pos_session__operating_unit_ids +#: model:ir.model.fields,field_description:pos_operating_unit.field_res_config_settings__pos_operating_unit_ids +#: model_terms:ir.ui.view,arch_db:pos_operating_unit.pos_config_view_form_inherit_ou +#: model_terms:ir.ui.view,arch_db:pos_operating_unit.res_config_settings_view_form +msgid "Operating Units" +msgstr "Unità operative" + +#. module: pos_operating_unit +#: model_terms:ir.ui.view,arch_db:pos_operating_unit.pos_config_view_form_inherit_ou +#: model_terms:ir.ui.view,arch_db:pos_operating_unit.res_config_settings_view_form +msgid "Operating units used for this POS" +msgstr "Unità operative utilizzate per questo POS" + +#. module: pos_operating_unit +#: model:ir.model.fields,field_description:pos_operating_unit.field_pos_order__config_id +#: model:ir.model.fields,field_description:pos_operating_unit.field_pos_payment__config_id +msgid "Point of Sale" +msgstr "Punto vendita" + +#. module: pos_operating_unit +#: model:ir.model,name:pos_operating_unit.model_pos_config +msgid "Point of Sale Configuration" +msgstr "Configurazione punto vendita" + +#. module: pos_operating_unit +#: model:ir.model,name:pos_operating_unit.model_pos_order_line +msgid "Point of Sale Order Lines" +msgstr "Righe ordine punto vendita" + +#. module: pos_operating_unit +#: model:ir.model,name:pos_operating_unit.model_pos_order +msgid "Point of Sale Orders" +msgstr "Ordini punto vendita" + +#. module: pos_operating_unit +#: model:ir.model,name:pos_operating_unit.model_pos_payment +msgid "Point of Sale Payments" +msgstr "Pagamenti punto vendita" + +#. module: pos_operating_unit +#: model:ir.model,name:pos_operating_unit.model_pos_session +msgid "Point of Sale Session" +msgstr "Sessione punto vendita" + +#~ msgid "Display Name" +#~ msgstr "Nome visualizzato" + +#~ msgid "ID" +#~ msgstr "ID" + +#~ msgid "Last Modified on" +#~ msgstr "Ultima modifica il" + +#~ msgid "The physical point of sale you will use." +#~ msgstr "Il punto vendita fisico che userai." diff --git a/pos_operating_unit/i18n/pos_operating_unit.pot b/pos_operating_unit/i18n/pos_operating_unit.pot new file mode 100644 index 0000000000..ba0ddadd05 --- /dev/null +++ b/pos_operating_unit/i18n/pos_operating_unit.pot @@ -0,0 +1,68 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * pos_operating_unit +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.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: pos_operating_unit +#: model:ir.model,name:pos_operating_unit.model_res_config_settings +msgid "Config Settings" +msgstr "" + +#. module: pos_operating_unit +#: model:ir.model.fields,field_description:pos_operating_unit.field_pos_config__operating_unit_ids +#: model:ir.model.fields,field_description:pos_operating_unit.field_pos_order__operating_unit_ids +#: model:ir.model.fields,field_description:pos_operating_unit.field_pos_order_line__operating_unit_ids +#: model:ir.model.fields,field_description:pos_operating_unit.field_pos_payment__operating_unit_ids +#: model:ir.model.fields,field_description:pos_operating_unit.field_pos_session__operating_unit_ids +#: model:ir.model.fields,field_description:pos_operating_unit.field_res_config_settings__pos_operating_unit_ids +#: model_terms:ir.ui.view,arch_db:pos_operating_unit.pos_config_view_form_inherit_ou +#: model_terms:ir.ui.view,arch_db:pos_operating_unit.res_config_settings_view_form +msgid "Operating Units" +msgstr "" + +#. module: pos_operating_unit +#: model_terms:ir.ui.view,arch_db:pos_operating_unit.pos_config_view_form_inherit_ou +#: model_terms:ir.ui.view,arch_db:pos_operating_unit.res_config_settings_view_form +msgid "Operating units used for this POS" +msgstr "" + +#. module: pos_operating_unit +#: model:ir.model.fields,field_description:pos_operating_unit.field_pos_order__config_id +#: model:ir.model.fields,field_description:pos_operating_unit.field_pos_payment__config_id +msgid "Point of Sale" +msgstr "" + +#. module: pos_operating_unit +#: model:ir.model,name:pos_operating_unit.model_pos_config +msgid "Point of Sale Configuration" +msgstr "" + +#. module: pos_operating_unit +#: model:ir.model,name:pos_operating_unit.model_pos_order_line +msgid "Point of Sale Order Lines" +msgstr "" + +#. module: pos_operating_unit +#: model:ir.model,name:pos_operating_unit.model_pos_order +msgid "Point of Sale Orders" +msgstr "" + +#. module: pos_operating_unit +#: model:ir.model,name:pos_operating_unit.model_pos_payment +msgid "Point of Sale Payments" +msgstr "" + +#. module: pos_operating_unit +#: model:ir.model,name:pos_operating_unit.model_pos_session +msgid "Point of Sale Session" +msgstr "" diff --git a/pos_operating_unit/models/__init__.py b/pos_operating_unit/models/__init__.py new file mode 100644 index 0000000000..199e05d7ee --- /dev/null +++ b/pos_operating_unit/models/__init__.py @@ -0,0 +1,5 @@ +from . import pos_config +from . import pos_order +from . import pos_session +from . import pos_payment +from . import res_config_settings diff --git a/pos_operating_unit/models/pos_config.py b/pos_operating_unit/models/pos_config.py new file mode 100644 index 0000000000..3717d2665f --- /dev/null +++ b/pos_operating_unit/models/pos_config.py @@ -0,0 +1,11 @@ +from odoo import fields, models + + +class POSConfig(models.Model): + _inherit = "pos.config" + + operating_unit_ids = fields.Many2many( + "operating.unit", + "pos_config_operating_unit_rel", + string="Operating Units", + ) diff --git a/pos_operating_unit/models/pos_order.py b/pos_operating_unit/models/pos_order.py new file mode 100644 index 0000000000..1ec9085f02 --- /dev/null +++ b/pos_operating_unit/models/pos_order.py @@ -0,0 +1,42 @@ +from odoo import api, fields, models + + +class POSOrder(models.Model): + _inherit = "pos.order" + + operating_unit_ids = fields.Many2many( + "operating.unit", + "pos_order_operating_unit_rel", + string="Operating Units", + ) + config_id = fields.Many2one(related="session_id.config_id", readonly=True) + + @api.model_create_multi + def create(self, vals_list): + for vals in vals_list: + session_id = self.env["pos.session"].sudo().browse(vals.get("session_id")) + if session_id.config_id: + vals["operating_unit_ids"] = [ + (6, 0, session_id.config_id.operating_unit_ids.ids) + ] + return super().create(vals_list) + + +class POSOrderLine(models.Model): + _inherit = "pos.order.line" + + operating_unit_ids = fields.Many2many( + "operating.unit", + "pos_order_line_operating_unit_rel", + string="Operating Units", + ) + + @api.model_create_multi + def create(self, vals_list): + for vals in vals_list: + order_id = self.env["pos.order"].sudo().browse(vals.get("order_id")) + if order_id.config_id: + vals["operating_unit_ids"] = [ + (6, 0, order_id.config_id.operating_unit_ids.ids) + ] + return super().create(vals_list) diff --git a/pos_operating_unit/models/pos_payment.py b/pos_operating_unit/models/pos_payment.py new file mode 100644 index 0000000000..82be4ba6fb --- /dev/null +++ b/pos_operating_unit/models/pos_payment.py @@ -0,0 +1,22 @@ +from odoo import api, fields, models + + +class POSPayment(models.Model): + _inherit = "pos.payment" + + operating_unit_ids = fields.Many2many( + "operating.unit", + "pos_payment_operating_unit_rel", + string="Operating Units", + ) + config_id = fields.Many2one(related="session_id.config_id", readonly=True) + + @api.model_create_multi + def create(self, vals_list): + for vals in vals_list: + pos_order_id = self.env["pos.order"].sudo().browse(vals.get("pos_order_id")) + if pos_order_id.config_id: + vals["operating_unit_ids"] = [ + (6, 0, pos_order_id.config_id.operating_unit_ids.ids) + ] + return super().create(vals_list) diff --git a/pos_operating_unit/models/pos_session.py b/pos_operating_unit/models/pos_session.py new file mode 100644 index 0000000000..3a6b97ec40 --- /dev/null +++ b/pos_operating_unit/models/pos_session.py @@ -0,0 +1,19 @@ +from odoo import api, fields, models + + +class POSSession(models.Model): + _inherit = "pos.session" + + operating_unit_ids = fields.Many2many( + "operating.unit", + "pos_session_operating_unit_rel", + string="Operating Units", + ) + + @api.model_create_multi + def create(self, vals_list): + for vals in vals_list: + config_id = self.env["pos.config"].sudo().browse(vals.get("config_id")) + if config_id: + vals["operating_unit_ids"] = [(6, 0, config_id.operating_unit_ids.ids)] + return super().create(vals_list) diff --git a/pos_operating_unit/models/res_config_settings.py b/pos_operating_unit/models/res_config_settings.py new file mode 100644 index 0000000000..087acd3d83 --- /dev/null +++ b/pos_operating_unit/models/res_config_settings.py @@ -0,0 +1,10 @@ +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + pos_operating_unit_ids = fields.Many2many( + related="pos_config_id.operating_unit_ids", + readonly=False, + ) diff --git a/pos_operating_unit/pyproject.toml b/pos_operating_unit/pyproject.toml new file mode 100644 index 0000000000..4231d0cccb --- /dev/null +++ b/pos_operating_unit/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/pos_operating_unit/readme/CONTRIBUTORS.md b/pos_operating_unit/readme/CONTRIBUTORS.md new file mode 100644 index 0000000000..4f05caa05d --- /dev/null +++ b/pos_operating_unit/readme/CONTRIBUTORS.md @@ -0,0 +1,5 @@ +- Ooops404 + - Ilyas + +- Nybble Group + - Cristiam Carreño diff --git a/pos_operating_unit/readme/DESCRIPTION.md b/pos_operating_unit/readme/DESCRIPTION.md new file mode 100644 index 0000000000..2be36be907 --- /dev/null +++ b/pos_operating_unit/readme/DESCRIPTION.md @@ -0,0 +1,6 @@ +This module adds a field "operating unit" in POS with the only goal of +restricting visibility (therefore access) to users with same OU allowed. + +This also applies to sessions, orders and payments in POS. + +A "Point of sale" field is added to orders and payments for clarity. diff --git a/pos_operating_unit/readme/ROADMAP.md b/pos_operating_unit/readme/ROADMAP.md new file mode 100644 index 0000000000..5a89a280a4 --- /dev/null +++ b/pos_operating_unit/readme/ROADMAP.md @@ -0,0 +1,3 @@ +This module only manages visibility of POS and related orders. If a more +complete "pos_operating_unit" is proposed and merged, then it should +cover features of this module which can be discontinued. diff --git a/pos_operating_unit/readme/USAGE.md b/pos_operating_unit/readme/USAGE.md new file mode 100644 index 0000000000..67c5e78a22 --- /dev/null +++ b/pos_operating_unit/readme/USAGE.md @@ -0,0 +1 @@ +In POS config, set operating unit(s) allowed to see this POS. diff --git a/pos_operating_unit/security/pos_security.xml b/pos_operating_unit/security/pos_security.xml new file mode 100644 index 0000000000..d69cad9fc9 --- /dev/null +++ b/pos_operating_unit/security/pos_security.xml @@ -0,0 +1,73 @@ + + + + ['|', ('operating_unit_ids', '=', False), + ('operating_unit_ids', 'in', operating_unit_ids)] + POS Config allowed operating units + + + + + + + + + + ['|', ('operating_unit_ids', '=', False), + ('operating_unit_ids', 'in', operating_unit_ids)] + POS Session allowed operating units + + + + + + + + + + ['|', ('operating_unit_ids', '=', False), + ('operating_unit_ids', 'in', operating_unit_ids)] + POS Order allowed operating units + + + + + + + + + + ['|', ('operating_unit_ids', '=', False), + ('operating_unit_ids', 'in', operating_unit_ids)] + POS Order Line allowed operating units + + + + + + + + + + ['|', ('operating_unit_ids', '=', False), + ('operating_unit_ids', 'in', operating_unit_ids)] + POS Payment allowed operating units + + + + + + + + + + ['|', ('config_id.operating_unit_ids', '=', False), + ('config_id.operating_unit_ids', 'in', operating_unit_ids)] + POS Order Report allowed operating units + + + + + + + diff --git a/pos_operating_unit/static/description/icon.png b/pos_operating_unit/static/description/icon.png new file mode 100644 index 0000000000..3a0328b516 Binary files /dev/null and b/pos_operating_unit/static/description/icon.png differ diff --git a/pos_operating_unit/static/description/index.html b/pos_operating_unit/static/description/index.html new file mode 100644 index 0000000000..09c88aa79b --- /dev/null +++ b/pos_operating_unit/static/description/index.html @@ -0,0 +1,446 @@ + + + + + +POS with Operating Units + + + +
+

POS with Operating Units

+ + +

Beta License: LGPL-3 OCA/operating-unit Translate me on Weblate Try me on Runboat

+

This module adds a field “operating unit” in POS with the only goal of +restricting visibility (therefore access) to users with same OU allowed.

+

This also applies to sessions, orders and payments in POS.

+

A “Point of sale” field is added to orders and payments for clarity.

+

Table of contents

+ +
+

Usage

+

In POS config, set operating unit(s) allowed to see this POS.

+
+
+

Known issues / Roadmap

+

This module only manages visibility of POS and related orders. If a more +complete “pos_operating_unit” is proposed and merged, then it should +cover features of this module which can be discontinued.

+
+
+

Bug Tracker

+

Bugs are tracked on GitHub 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.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Ilyas
  • +
  • Ooops404
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

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/operating-unit project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/pos_operating_unit/tests/__init__.py b/pos_operating_unit/tests/__init__.py new file mode 100644 index 0000000000..f4c3bbf52d --- /dev/null +++ b/pos_operating_unit/tests/__init__.py @@ -0,0 +1,3 @@ +from . import test_pos_operating_unit_basic +from . import test_pos_operating_unit_rules +from . import test_pos_operating_unit diff --git a/pos_operating_unit/tests/test_pos_operating_unit.py b/pos_operating_unit/tests/test_pos_operating_unit.py new file mode 100644 index 0000000000..1c0e1f0e72 --- /dev/null +++ b/pos_operating_unit/tests/test_pos_operating_unit.py @@ -0,0 +1,177 @@ +# Copyright 2024 ForgeFlow, S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html) + +from odoo import fields +from odoo.exceptions import AccessError +from odoo.fields import Command +from odoo.tests import tagged + +from odoo.addons.operating_unit.tests.common import OperatingUnitCommon + + +@tagged("post_install", "-at_install") +class TestPOSOperatingUnit(OperatingUnitCommon): + """Test Point of Sale Operating Unit access controls and functionality.""" + + @classmethod + def setUpClass(cls): + super().setUpClass() + + # Setup models + cls.PosOrder = cls.env["pos.order"] + cls.PosConfig = cls.env["pos.config"] + cls.PosSession = cls.env["pos.session"] + + # Setup product for testing + cls.pos_product = cls.env["product.product"].create( + { + "name": "Test POS Product", + "available_in_pos": True, + "list_price": 1000.0, + } + ) + + # Setup pricelist + cls.pricelist = cls.env["product.pricelist"].create( + { + "name": "Test POS Pricelist", + "currency_id": cls.env.company.currency_id.id, + } + ) + + # Setup groups + cls.group_pos_manager = cls.env.ref("point_of_sale.group_pos_manager") + cls.group_account_invoice = cls.env.ref("account.group_account_invoice") + + # Create POS config with operating unit + cls.pos_config = cls.env["pos.config"].create( + { + "name": "Test POS Config", + "operating_unit_ids": [Command.set([cls.ou1.id])], + "available_pricelist_ids": [Command.set([cls.pricelist.id])], + "pricelist_id": cls.pricelist.id, + } + ) + + # Open session + cls.pos_config.open_ui() + + # Configure users with proper groups and operating units + cls.user1.write( + { + "groups_id": [ + Command.link(cls.group_pos_manager.id), + Command.link(cls.group_account_invoice.id), + ], + "operating_unit_ids": [Command.link(cls.ou1.id)], + } + ) + cls.user2.write( + { + "groups_id": [ + Command.link(cls.group_pos_manager.id), + Command.link(cls.group_account_invoice.id), + ], + "operating_unit_ids": [Command.set([cls.b2b.id])], + } + ) + + def test_operating_unit_access_config(self): + """Test that users can only access POS configs for their operating units.""" + # User1 has access to ou1 (same as pos_config) + config1_ids = self.PosConfig.with_user(self.user1).search([]) + self.assertIn(self.pos_config, config1_ids) + + # User2 has access to b2b (different from pos_config) + config2_ids = self.PosConfig.with_user(self.user2).search([]) + self.assertNotIn(self.pos_config, config2_ids) + + def test_operating_unit_access_session(self): + """Test that users can only access sessions for their operating units.""" + # User1 should be able to read the session + self.pos_config.current_session_id.with_user(self.user1).read() + + # User2 should not have access + with self.assertRaises(AccessError): + self.pos_config.current_session_id.with_user(self.user2).read() + + def test_operating_unit_access_order_and_line_and_payment(self): + """Test that users can only access orders for their operating units.""" + order = self._create_order() + + # User1 should have access to order and related records + order.with_user(self.user1).read() + order.lines.with_user(self.user1).read() + order.payment_ids.with_user(self.user1).read() + + # User2 should not have access + with self.assertRaises(AccessError): + order.with_user(self.user2).read() + with self.assertRaises(AccessError): + order.lines.with_user(self.user2).read() + with self.assertRaises(AccessError): + order.payment_ids.with_user(self.user2).read() + + def _create_order(self): + """Create a test POS order using the modern Odoo 18 approach.""" + # Create order using sync_from_ui method + order_data = { + "id": "0006-001-0010", + "to_invoice": False, + "session_id": self.pos_config.current_session_id.id, + "date_order": fields.Datetime.to_string(fields.Datetime.now()), + "pricelist_id": self.pricelist.id, + "user_id": self.env.user.id, + "name": "Order 0006-001-0010", + "partner_id": False, + "amount_paid": 1000.0, + "amount_total": 1000.0, + "amount_tax": 0.0, + "amount_return": 0.0, + "fiscal_position_id": False, + "sequence_number": 1, + "uuid": "00001-001-0001", + "lines": [ + [ + 0, + 0, + { + "product_id": self.pos_product.id, + "qty": 1.0, + "price_unit": 1000.0, + "price_subtotal": 1000.0, + "price_subtotal_incl": 1000.0, + "discount": 0.0, + }, + ] + ], + "payment_ids": [ + [ + 0, + 0, + { + "payment_method_id": self.pos_config.payment_method_ids[0].id, + "amount": 1000.0, + }, + ] + ], + } + + # Create the order + result = self.PosOrder.sync_from_ui([order_data]) + if not result or "pos.order" not in result: + raise ValueError("Failed to create POS order") + + # Get order data from result + pos_orders = result["pos.order"] + if not pos_orders: + raise ValueError("No order data in result") + + # Extract order ID from the first order data + order_data_dict = pos_orders[0] if pos_orders else {} + order_id = order_data_dict.get("id") + + if not order_id: + raise ValueError("Failed to get order ID from result") + + return self.PosOrder.browse(order_id) diff --git a/pos_operating_unit/tests/test_pos_operating_unit_basic.py b/pos_operating_unit/tests/test_pos_operating_unit_basic.py new file mode 100644 index 0000000000..a7ef5dabd6 --- /dev/null +++ b/pos_operating_unit/tests/test_pos_operating_unit_basic.py @@ -0,0 +1,80 @@ +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html) +from odoo.fields import Command +from odoo.tests import tagged + +from odoo.addons.operating_unit.tests.common import OperatingUnitCommon + + +@tagged("post_install", "-at_install") +class TestPOSOperatingUnitBasic(OperatingUnitCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.company = cls.env.ref("base.main_company") + # create partner and OU for the test to satisfy partner_id not-null + partner = cls.env["res.partner"].create({"name": "OU Test Partner"}) + cls.ou = cls.env["operating.unit"].create( + { + "name": "OU Test", + "code": "OUT", + "company_id": cls.company.id, + "partner_id": partner.id, + } + ) + + def test_fields_and_assign(self): + """Test POS configuration and session for operating units.""" + self.assertIn("operating_unit_ids", self.env["pos.config"]._fields) + self.assertIn("operating_unit_ids", self.env["pos.session"]._fields) + cfg = self.env["pos.config"].create( + { + "name": "POS Test", + "company_id": self.company.id, + "operating_unit_ids": [Command.set([self.ou.id])], + } + ) + self.assertIn(self.ou, cfg.operating_unit_ids) + + def test_operating_unit_assignment_via_write(self): + """Ensure operating_unit_ids can be set via write and read back correctly.""" + cfg = self.env["pos.config"].create( + {"name": "POS Write Test", "company_id": self.company.id} + ) + self.assertFalse(cfg.operating_unit_ids) + cfg.write({"operating_unit_ids": [Command.set([self.ou.id])]}) + self.assertIn(self.ou, cfg.operating_unit_ids) + + def test_operating_unit_unset_via_write(self): + """Ensure operating_unit_ids can be unset via write.""" + cfg = self.env["pos.config"].create( + { + "name": "POS Unset Test", + "company_id": self.company.id, + "operating_unit_ids": [Command.set([self.ou.id])], + } + ) + self.assertIn(self.ou, cfg.operating_unit_ids) + cfg.write({"operating_unit_ids": [Command.set([])]}) + self.assertFalse(cfg.operating_unit_ids) + + def test_assign_multiple_operating_units(self): + """Assign multiple operating units to a config and verify both are present.""" + partner2 = self.env["res.partner"].create({"name": "OU2 Partner"}) + ou2 = self.env["operating.unit"].create( + { + "name": "OU 2", + "code": "OU2", + "company_id": self.company.id, + "partner_id": partner2.id, + } + ) + cfg = self.env["pos.config"].create( + { + "name": "POS Multi OU", + "company_id": self.company.id, + "operating_unit_ids": [Command.set([self.ou.id, ou2.id])], + } + ) + self.assertIn(self.ou, cfg.operating_unit_ids) + self.assertIn(ou2, cfg.operating_unit_ids) + self.assertEqual(len(cfg.operating_unit_ids), 2) diff --git a/pos_operating_unit/tests/test_pos_operating_unit_rules.py b/pos_operating_unit/tests/test_pos_operating_unit_rules.py new file mode 100644 index 0000000000..e7e33c3e16 --- /dev/null +++ b/pos_operating_unit/tests/test_pos_operating_unit_rules.py @@ -0,0 +1,72 @@ +from odoo.fields import Command +from odoo.tests import tagged + +from odoo.addons.operating_unit.tests.common import OperatingUnitCommon + + +@tagged("post_install", "-at_install") +class TestPOSOperatingUnitRules(OperatingUnitCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + company = cls.env.ref("base.main_company") + # create partners for operating units to satisfy NOT NULL partner_id + partner_a = cls.env["res.partner"].create({"name": "OU A Partner"}) + partner_b = cls.env["res.partner"].create({"name": "OU B Partner"}) + cls.ou_a = cls.env["operating.unit"].create( + { + "name": "OU A", + "code": "OUA", + "company_id": company.id, + "partner_id": partner_a.id, + } + ) + cls.ou_b = cls.env["operating.unit"].create( + { + "name": "OU B", + "code": "OUB", + "company_id": company.id, + "partner_id": partner_b.id, + } + ) + cls.user = cls.env["res.users"].create( + { + "name": "User A", + "login": "user_a@example.com", + "email": "user_a@example.com", + "company_id": company.id, + "operating_unit_ids": [(6, 0, [cls.ou_a.id])], + "groups_id": [ + ( + 6, + 0, + [ + cls.env.ref("point_of_sale.group_pos_user").id, + ], + ) + ], + } + ) + # Sesión en OU_B (no autorizada para el user) + cfg_b = cls.env["pos.config"].create( + { + "name": "POS B", + "company_id": company.id, + "operating_unit_ids": [Command.set([cls.ou_b.id])], + } + ) + cls.session_b = cls.env["pos.session"].create( + { + "config_id": cfg_b.id, + "user_id": cls.env.ref("base.user_admin").id, + } + ) + + def test_record_rule_hides_other_ou_sessions(self): + """Test that the record rule hides POS sessions from other operating units.""" + sess = ( + self.env["pos.session"] + .with_user(self.user) + .search([("id", "=", self.session_b.id)]) + ) + self.assertFalse(sess, "User with OU_A must not see POS sessions from OU_B") diff --git a/pos_operating_unit/views/pos_views.xml b/pos_operating_unit/views/pos_views.xml new file mode 100644 index 0000000000..3d88829fd3 --- /dev/null +++ b/pos_operating_unit/views/pos_views.xml @@ -0,0 +1,94 @@ + + + pos.config.form.inherit.ou + pos.config + + + + + + + + + + + + pos.order.form.inherit.ou + pos.order + + + + + + + + + + + pos.order.tree.inherit.ou + pos.order + + + + + + + + + + + pos.payment.form.inherit.ou + pos.payment + + + + + + + + + + + pos.payment.tree.inherit.ou + pos.payment + + + + + + + + + diff --git a/pos_operating_unit/views/res_config_settings_views.xml b/pos_operating_unit/views/res_config_settings_views.xml new file mode 100644 index 0000000000..188c7a4b4e --- /dev/null +++ b/pos_operating_unit/views/res_config_settings_views.xml @@ -0,0 +1,27 @@ + + + + res.config.settings.view.form.inherit.point_of_sale + res.config.settings + + + + + + + + + + + +