From 6b40f52190256edbd5207b0a045e068bf52f37cd Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Tue, 4 Jul 2023 17:53:59 +0200 Subject: [PATCH 1/5] edi: refactor model rules w/ specific model Long awaited improvement. Allows to decouple rules by model giving much more flexibility. Migration steps included. --- edi_oca/__manifest__.py | 1 + .../migrations/14.0.1.19.0/post-migrate.py | 34 +++++ edi_oca/migrations/14.0.1.19.0/pre-migrate.py | 42 ++++++ edi_oca/models/__init__.py | 1 + edi_oca/models/edi_exchange_consumer_mixin.py | 32 +++-- edi_oca/models/edi_exchange_type.py | 22 +--- edi_oca/models/edi_exchange_type_rule.py | 54 ++++++++ edi_oca/readme/CONFIGURE.rst | 21 +++ edi_oca/security/ir_model_access.xml | 18 +++ edi_oca/static/src/xml/widget_edi.xml | 10 +- edi_oca/tests/test_consumer_mixin.py | 73 +++++++---- .../views/edi_exchange_type_rule_views.xml | 121 ++++++++++++++++++ edi_oca/views/edi_exchange_type_views.xml | 21 +-- edi_oca/views/menuitems.xml | 7 + 14 files changed, 385 insertions(+), 72 deletions(-) create mode 100644 edi_oca/migrations/14.0.1.19.0/post-migrate.py create mode 100644 edi_oca/migrations/14.0.1.19.0/pre-migrate.py create mode 100644 edi_oca/models/edi_exchange_type_rule.py create mode 100644 edi_oca/views/edi_exchange_type_rule_views.xml diff --git a/edi_oca/__manifest__.py b/edi_oca/__manifest__.py index 124eb84d80..ac6fcaac18 100644 --- a/edi_oca/__manifest__.py +++ b/edi_oca/__manifest__.py @@ -35,6 +35,7 @@ "views/edi_backend_type_views.xml", "views/edi_exchange_record_views.xml", "views/edi_exchange_type_views.xml", + "views/edi_exchange_type_rule_views.xml", "views/menuitems.xml", "templates/exchange_chatter_msg.xml", "templates/exchange_mixin_buttons.xml", diff --git a/edi_oca/migrations/14.0.1.19.0/post-migrate.py b/edi_oca/migrations/14.0.1.19.0/post-migrate.py new file mode 100644 index 0000000000..95a944823d --- /dev/null +++ b/edi_oca/migrations/14.0.1.19.0/post-migrate.py @@ -0,0 +1,34 @@ +# Copyright 2023 Camptocamp SA (http://www.camptocamp.com) +# @author Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +import logging + +from odoo import SUPERUSER_ID, api, tools + +_logger = logging.getLogger(__name__) + + +def migrate(cr, version): + if not version: + return + + bkp_table = "exc_type_model_rel_bkp" + if not tools.sql.table_exists(cr, bkp_table): + return + + # Use backup table (created by pre-migrate step) to create type rules + env = api.Environment(cr, SUPERUSER_ID, {}) + query = """ + SELECT * FROM exc_type_model_rel_bkp + """ + cr.execute(query) + res = cr.dictfetchall() + model = env["edi.exchange.type.rule"] + for item in res: + kind = "form_btn" if item.pop("form_btn", False) else "custom" + vals = dict(item, name="Default", kind=kind) + model.create(vals) + + cr.execute("DROP TABLE exc_type_model_rel_bkp") + _logger.info("edi.exchange.type.rule created") diff --git a/edi_oca/migrations/14.0.1.19.0/pre-migrate.py b/edi_oca/migrations/14.0.1.19.0/pre-migrate.py new file mode 100644 index 0000000000..61419f1d88 --- /dev/null +++ b/edi_oca/migrations/14.0.1.19.0/pre-migrate.py @@ -0,0 +1,42 @@ +# Copyright 2023 Camptocamp SA (http://www.camptocamp.com) +# @author Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +import logging + +from odoo import tools + +_logger = logging.getLogger(__name__) + + +def migrate(cr, version): + if not version: + return + + # Backup old style rules to be used later on post migrate + old_table = "edi_exchange_type_ir_model_rel" + if not tools.sql.table_exists(cr, old_table): + return + bkp_table = "exc_type_model_rel_bkp" + if tools.sql.table_exists(cr, bkp_table): + return + + bkp_query = """ + CREATE TABLE IF NOT EXISTS + exc_type_model_rel_bkp + AS + SELECT + rel.ir_model_id as model_id, + type.id as type_id, + type.enable_domain as enable_domain, + type.enable_snippet as enable_snippet, + type.model_manual_btn as form_btn + FROM + edi_exchange_type type, + edi_exchange_type_ir_model_rel rel + WHERE + rel.edi_exchange_type_id = type.id; + """ + cr.execute(bkp_query) + + _logger.info("edi.exchange.type old style rules backed up") diff --git a/edi_oca/models/__init__.py b/edi_oca/models/__init__.py index b3961f232f..f40b0abe1e 100644 --- a/edi_oca/models/__init__.py +++ b/edi_oca/models/__init__.py @@ -3,4 +3,5 @@ from . import edi_exchange_record from . import edi_exchange_consumer_mixin from . import edi_exchange_type +from . import edi_exchange_type_rule from . import edi_id_mixin diff --git a/edi_oca/models/edi_exchange_consumer_mixin.py b/edi_oca/models/edi_exchange_consumer_mixin.py index a5430e635b..7085d29d9b 100644 --- a/edi_oca/models/edi_exchange_consumer_mixin.py +++ b/edi_oca/models/edi_exchange_consumer_mixin.py @@ -58,34 +58,42 @@ def _compute_edi_config(self): record.edi_has_form_config = any([x.get("form") for x in config.values()]) def _edi_get_exchange_type_config(self): - exchange_types = ( - self.env["edi.exchange.type"] + # TODO: move this machinery to the rule model + rules = ( + self.env["edi.exchange.type.rule"] .sudo() - .search([("model_ids.model", "=", self._name)]) + .search([("model_id.model", "=", self._name)]) ) result = {} - for exchange_type in exchange_types: + for rule in rules: + exchange_type = rule.type_id eval_ctx = dict( self._get_eval_context(), record=self, exchange_type=exchange_type ) - domain = safe_eval.safe_eval(exchange_type.enable_domain or "[]", eval_ctx) + domain = safe_eval.safe_eval(rule.enable_domain or "[]", eval_ctx) if not self.filtered_domain(domain): continue - if exchange_type.enable_snippet: + if rule.enable_snippet: safe_eval.safe_eval( - exchange_type.enable_snippet, eval_ctx, mode="exec", nocopy=True + rule.enable_snippet, eval_ctx, mode="exec", nocopy=True ) if not eval_ctx.get("result", False): continue - result[exchange_type.id] = self._edi_get_exchange_type_conf(exchange_type) + result[rule.id] = self._edi_get_exchange_type_rule_conf(rule) return result @api.model - def _edi_get_exchange_type_conf(self, exchange_type): - conf = {"form": {}} - if exchange_type.model_manual_btn: - conf.update({"form": {"btn": {"label": exchange_type.name}}}) + def _edi_get_exchange_type_rule_conf(self, rule): + conf = { + "form": {}, + "type": { + "id": rule.type_id.id, + "name": rule.type_id.name, + }, + } + if rule.kind == "form_btn": + conf.update({"form": {"btn": {"label": rule.type_id.name}}}) return conf def _get_eval_context(self): diff --git a/edi_oca/models/edi_exchange_type.py b/edi_oca/models/edi_exchange_type.py index f5a8af86bc..aeaef7ed5a 100644 --- a/edi_oca/models/edi_exchange_type.py +++ b/edi_oca/models/edi_exchange_type.py @@ -111,24 +111,10 @@ class EDIExchangeType(models.Model): """, ) advanced_settings = Serialized(default={}, compute="_compute_advanced_settings") - model_ids = fields.Many2many( - "ir.model", - help="""Modules to be checked for manual EDI generation""", - ) - enable_domain = fields.Char( - string="Enable on domain", help="""Filter domain to be checked on Models""" - ) - enable_snippet = fields.Char( - string="Enable on snippet", - help="""Snippet of code to be checked on Models, - You can use `record` and `exchange_type` here. - It will be executed if variable result has been defined as True - """, - ) - model_manual_btn = fields.Boolean( - string="Manual button on form", - help="Automatically display a button on related models' form." - # TODO: "Button settings can be configured via advanced settings." + rule_ids = fields.One2many( + comodel_name="edi.exchange.type.rule", + inverse_name="type_id", + help="Rules to handle exchanges and UI automatically", ) quick_exec = fields.Boolean( string="Quick execution", diff --git a/edi_oca/models/edi_exchange_type_rule.py b/edi_oca/models/edi_exchange_type_rule.py new file mode 100644 index 0000000000..8f65b77729 --- /dev/null +++ b/edi_oca/models/edi_exchange_type_rule.py @@ -0,0 +1,54 @@ +# Copyright 2023 Camptocamp SA +# @author Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from odoo import fields, models + +KIND_HELP = """ +* Form button: show a button on the related model form + when conditions from domain and snippet are satisfied + +* Custom: let devs handle a custom behavior with specific developments +""" + + +class EDIExchangeTypeRule(models.Model): + """ + Define rules for exchange types. + """ + + _name = "edi.exchange.type.rule" + _description = "EDI Exchange type rule" + + active = fields.Boolean(default=True) + name = fields.Char(required=True) + type_id = fields.Many2one( + comodel_name="edi.exchange.type", + required=True, + ondelete="cascade", + ) + model_id = fields.Many2one( + comodel_name="ir.model", + help="Apply to this model", + ondelete="cascade", + ) + model = fields.Char(related="model_id.model") # Tech field + enable_domain = fields.Char( + string="Enable on domain", help="Filter domain to be checked on Models" + ) + enable_snippet = fields.Char( + string="Enable on snippet", + help="""Snippet of code to be checked on Models, + You can use `record` and `exchange_type` here. + It will be executed if variable result has been defined as True + """, + ) + kind = fields.Selection( + selection=[ + ("form_btn", "Form button"), + ("custom", "Custom"), + ], + required=True, + default="form_btn", + help=KIND_HELP, + ) diff --git a/edi_oca/readme/CONFIGURE.rst b/edi_oca/readme/CONFIGURE.rst index c65c8e743b..acf6a0d286 100644 --- a/edi_oca/readme/CONFIGURE.rst +++ b/edi_oca/readme/CONFIGURE.rst @@ -27,3 +27,24 @@ snippet of code. After defining this fields, we will automatically see buttons on the view to generate the exchange records. This configuration is useful to define a way of generation managed by user. + + +Exchange type rules configuration +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Exchange types can be further configured with rules. +You can use rules to: + +1. make buttons automatically appear in forms +2. define your own custom logic + +Go to an exchange type and go to the tab "Model rules". +There you can add one or more rule, one per model. +On each rule you can define a domain or a snippet to activate it. +In case of a "Form button" kind, if the domain and/ the snippet is/are satisfied, +a form btn will appear on the top of the form. +This button can be used by the end user to manually generate an exchange. +If there's more than a backend and the exchange type has not a backend set, +a wizard will appear asking to select a backend to be used for the exchange. + +In case of "Custom" kind, you'll have to define your own logic to do something. diff --git a/edi_oca/security/ir_model_access.xml b/edi_oca/security/ir_model_access.xml index 19dfacd511..83970fb3c5 100644 --- a/edi_oca/security/ir_model_access.xml +++ b/edi_oca/security/ir_model_access.xml @@ -27,6 +27,15 @@ + + access_edi_exchange_type_rule manager + + + + + + + access_edi_exchange_record manager @@ -63,6 +72,15 @@ + + access_edi_exchange_type_rule user + + + + + + + access_edi_exchange_record user diff --git a/edi_oca/static/src/xml/widget_edi.xml b/edi_oca/static/src/xml/widget_edi.xml index ebcf9ad3ff..5840c31ed0 100644 --- a/edi_oca/static/src/xml/widget_edi.xml +++ b/edi_oca/static/src/xml/widget_edi.xml @@ -2,13 +2,13 @@
- - - - + + + +