From e6c9d20d2278bf0258a261671799b6f1e95b3e20 Mon Sep 17 00:00:00 2001 From: Mourad Date: Thu, 3 Oct 2019 17:17:49 +0200 Subject: [PATCH 01/17] [ADD] base_substate --- base_substate/README.rst | 89 ++++ base_substate/__init__.py | 1 + base_substate/__manifest__.py | 20 + base_substate/i18n/base_substate.pot | 211 +++++++++ base_substate/models/__init__.py | 2 + base_substate/models/base_substate.py | 89 ++++ base_substate/models/base_substate_mixin.py | 89 ++++ base_substate/readme/CONTRIBUTORS.rst | 1 + base_substate/readme/DESCRIPTION.rst | 12 + base_substate/readme/USAGE.rst | 1 + .../security/base_substate_security.xml | 9 + base_substate/security/ir.model.access.csv | 7 + base_substate/static/description/icon.png | Bin 0 -> 9455 bytes base_substate/static/description/index.html | 437 ++++++++++++++++++ base_substate/tests/__init__.py | 4 + base_substate/tests/models_mixin.py | 128 +++++ base_substate/tests/sale_test.py | 57 +++ base_substate/tests/test_base_substate.py | 104 +++++ .../views/base_substate_type_views.xml | 79 ++++ .../views/base_substate_value_views.xml | 76 +++ base_substate/views/base_substate_views.xml | 84 ++++ 21 files changed, 1500 insertions(+) create mode 100644 base_substate/README.rst create mode 100644 base_substate/__init__.py create mode 100644 base_substate/__manifest__.py create mode 100644 base_substate/i18n/base_substate.pot create mode 100644 base_substate/models/__init__.py create mode 100644 base_substate/models/base_substate.py create mode 100644 base_substate/models/base_substate_mixin.py create mode 100644 base_substate/readme/CONTRIBUTORS.rst create mode 100644 base_substate/readme/DESCRIPTION.rst create mode 100644 base_substate/readme/USAGE.rst create mode 100644 base_substate/security/base_substate_security.xml create mode 100644 base_substate/security/ir.model.access.csv create mode 100644 base_substate/static/description/icon.png create mode 100644 base_substate/static/description/index.html create mode 100644 base_substate/tests/__init__.py create mode 100644 base_substate/tests/models_mixin.py create mode 100644 base_substate/tests/sale_test.py create mode 100644 base_substate/tests/test_base_substate.py create mode 100644 base_substate/views/base_substate_type_views.xml create mode 100644 base_substate/views/base_substate_value_views.xml create mode 100644 base_substate/views/base_substate_views.xml diff --git a/base_substate/README.rst b/base_substate/README.rst new file mode 100644 index 0000000000..e9772bdacf --- /dev/null +++ b/base_substate/README.rst @@ -0,0 +1,89 @@ +============== +Base Sub State +============== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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--ux-lightgray.png?logo=github + :target: https://github.com/OCA/server-ux/tree/12.0/base_substate + :alt: OCA/server-ux +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/server-ux-12-0/server-ux-12-0-base_substate + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/250/12.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module provide abstract models to manage customizable +substates to be applied on different models (sale order, purchase, ...). + +example: +-------- + +* for the quotation state of a sale order we can define 3 substates "In negotiation", + "Won" and "Lost". +* We can also send mail when the susbstate is reached. + +It is not useful for itself. You can see an example of implementation +in the 'sale_substate' module. (sale-workflow repository). + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +#. You must install an application module depending this one (for example sale_substate) + +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 smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Akretion + +Contributors +~~~~~~~~~~~~ + +* Mourad EL HADJ MIMOUNE + +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-ux `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/base_substate/__init__.py b/base_substate/__init__.py new file mode 100644 index 0000000000..0650744f6b --- /dev/null +++ b/base_substate/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/base_substate/__manifest__.py b/base_substate/__manifest__.py new file mode 100644 index 0000000000..59e08fb2e2 --- /dev/null +++ b/base_substate/__manifest__.py @@ -0,0 +1,20 @@ +# Copyright 2020 Akretion () +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Base Sub State", + "version": "12.0.1.0.0", + "category": "Tools", + "author": "Akretion, " "Odoo Community Association (OCA)", + "website": "https://github.com/OCA/sale-workflow/", + "license": "AGPL-3", + "depends": ["base"], + "data": [ + 'security/base_substate_security.xml', + 'security/ir.model.access.csv', + "views/base_substate_type_views.xml", + "views/base_substate_value_views.xml", + "views/base_substate_views.xml", + ], + "installable": True, +} diff --git a/base_substate/i18n/base_substate.pot b/base_substate/i18n/base_substate.pot new file mode 100644 index 0000000000..218c1a4cc7 --- /dev/null +++ b/base_substate/i18n/base_substate.pot @@ -0,0 +1,211 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * base_substate +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.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_substate +#: model:ir.model.fields,field_description:base_substate.field_base_substate__active +msgid "Active" +msgstr "" + +#. module: base_substate +#: model:ir.model.fields,field_description:base_substate.field_base_substate__model +#: model:ir.model.fields,field_description:base_substate.field_base_substate_type__model +#: model:ir.model.fields,field_description:base_substate.field_target_state_value__model +msgid "Apply on" +msgstr "" + +#. module: base_substate +#: model:ir.actions.act_window,name:base_substate.act_open_base_substate_view +#: model:ir.model,name:base_substate.model_base_substate +#: model:ir.ui.menu,name:base_substate.menu_base_substate +#: model_terms:ir.ui.view,arch_db:base_substate.base_substate_view_form +#: model_terms:ir.ui.view,arch_db:base_substate.base_substate_view_search +msgid "Base Substate" +msgstr "" + +#. module: base_substate +#: model:ir.model,name:base_substate.model_base_substate_type +msgid "Base Substate Type" +msgstr "" + +#. module: base_substate +#: model:ir.model,name:base_substate.model_base_substate_mixin +msgid "BaseSubstate Mixin" +msgstr "" + +#. module: base_substate +#: model:ir.model.fields,field_description:base_substate.field_base_substate__create_uid +#: model:ir.model.fields,field_description:base_substate.field_base_substate_type__create_uid +#: model:ir.model.fields,field_description:base_substate.field_target_state_value__create_uid +msgid "Created by" +msgstr "" + +#. module: base_substate +#: model:ir.model.fields,field_description:base_substate.field_base_substate__create_date +#: model:ir.model.fields,field_description:base_substate.field_base_substate_type__create_date +#: model:ir.model.fields,field_description:base_substate.field_target_state_value__create_date +msgid "Created on" +msgstr "" + +#. module: base_substate +#: model:ir.model.fields,field_description:base_substate.field_base_substate__description +msgid "Description" +msgstr "" + +#. module: base_substate +#: model:ir.model.fields,field_description:base_substate.field_base_substate__display_name +#: model:ir.model.fields,field_description:base_substate.field_base_substate_mixin__display_name +#: model:ir.model.fields,field_description:base_substate.field_base_substate_type__display_name +#: model:ir.model.fields,field_description:base_substate.field_target_state_value__display_name +msgid "Display Name" +msgstr "" + +#. module: base_substate +#: model:ir.model.fields,field_description:base_substate.field_base_substate__mail_template_id +msgid "Email Template" +msgstr "" + +#. module: base_substate +#: model:ir.model.fields,help:base_substate.field_base_substate__sequence +msgid "Gives the sequence order when applying the default substate" +msgstr "" + +#. module: base_substate +#: model:ir.model.fields,field_description:base_substate.field_base_substate__id +#: model:ir.model.fields,field_description:base_substate.field_base_substate_mixin__id +#: model:ir.model.fields,field_description:base_substate.field_base_substate_type__id +#: model:ir.model.fields,field_description:base_substate.field_target_state_value__id +msgid "ID" +msgstr "" + +#. module: base_substate +#: model:ir.model.fields,help:base_substate.field_base_substate__mail_template_id +msgid "If set, an email will be sent to the partner when the object reaches this substate." +msgstr "" + +#. module: base_substate +#: model:ir.model.fields,field_description:base_substate.field_base_substate____last_update +#: model:ir.model.fields,field_description:base_substate.field_base_substate_mixin____last_update +#: model:ir.model.fields,field_description:base_substate.field_base_substate_type____last_update +#: model:ir.model.fields,field_description:base_substate.field_target_state_value____last_update +msgid "Last Modified on" +msgstr "" + +#. module: base_substate +#: model:ir.model.fields,field_description:base_substate.field_base_substate__write_uid +#: model:ir.model.fields,field_description:base_substate.field_base_substate_type__write_uid +#: model:ir.model.fields,field_description:base_substate.field_target_state_value__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: base_substate +#: model:ir.model.fields,field_description:base_substate.field_base_substate__write_date +#: model:ir.model.fields,field_description:base_substate.field_base_substate_type__write_date +#: model:ir.model.fields,field_description:base_substate.field_target_state_value__write_date +msgid "Last Updated on" +msgstr "" + +#. module: base_substate +#: model:ir.model.fields,help:base_substate.field_base_substate__model +#: model:ir.model.fields,help:base_substate.field_target_state_value__model +msgid "Model for technical use" +msgstr "" + +#. module: base_substate +#: model:ir.model.fields,field_description:base_substate.field_base_substate_type__name +msgid "Name" +msgstr "" + +#. module: base_substate +#: model:ir.model.fields,field_description:base_substate.field_base_substate__sequence +msgid "Sequence" +msgstr "" + +#. module: base_substate +#: model:ir.model.fields,field_description:base_substate.field_base_substate_mixin__substate_id +msgid "Sub State" +msgstr "" + +#. module: base_substate +#: model:ir.ui.menu,name:base_substate.menu_substate_config +msgid "Sub State Configuration" +msgstr "" + +#. module: base_substate +#: model:ir.actions.act_window,name:base_substate.act_open_base_substate_type_view +#: model:ir.ui.menu,name:base_substate.menu_base_substate_type +#: model_terms:ir.ui.view,arch_db:base_substate.base_substate_type_view_form +#: model_terms:ir.ui.view,arch_db:base_substate.base_substate_type_view_search +msgid "Sub State Type" +msgstr "" + +#. module: base_substate +#: model:ir.model.fields,field_description:base_substate.field_base_substate__name +msgid "Substate Name" +msgstr "" + +#. module: base_substate +#: model:ir.model.fields,field_description:base_substate.field_target_state_value__base_substate_type_id +msgid "Substate Type" +msgstr "" + +#. module: base_substate +#: model:res.groups,name:base_substate.group_substate_manager +msgid "Substate manager" +msgstr "" + +#. module: base_substate +#: model:ir.model.fields,field_description:base_substate.field_base_substate_type__target_state_field +msgid "Target State Field" +msgstr "" + +#. module: base_substate +#: model:ir.actions.act_window,name:base_substate.act_open_target_state_value_view +#: model:ir.model,name:base_substate.model_target_state_value +#: model:ir.model.fields,field_description:base_substate.field_base_substate__target_state_value_id +#: model:ir.model.fields,field_description:base_substate.field_target_state_value__target_state_value +#: model:ir.ui.menu,name:base_substate.menu_target_state_value +#: model_terms:ir.ui.view,arch_db:base_substate.target_state_value_view_form +#: model_terms:ir.ui.view,arch_db:base_substate.target_state_value_view_search +msgid "Target State Value" +msgstr "" + +#. module: base_substate +#: model:ir.model.fields,field_description:base_substate.field_target_state_value__name +msgid "Target state Name" +msgstr "" + +#. module: base_substate +#: model:ir.model.fields,help:base_substate.field_target_state_value__name +msgid "Target state translateble name.\n" +"Ex: for sale order \"Quotation\", \"Sale order\", \"Locked\"..." +msgstr "" + +#. module: base_substate +#: model:ir.model.fields,help:base_substate.field_base_substate_type__target_state_field +msgid "Technical target state field name. Ex for sale order \"state\" for other \"status\" ... " +msgstr "" + +#. module: base_substate +#: model:ir.model.fields,help:base_substate.field_target_state_value__target_state_value +msgid "Technical target state value.\n" +"Ex: for sale order \"draft\", \"sale\", \"done\", ..." +msgstr "" + +#. module: base_substate +#: code:addons/base_substate/models/base_substate_mixin.py:67 +#, python-format +msgid "This substate is not define for this object but for %s" +msgstr "" + diff --git a/base_substate/models/__init__.py b/base_substate/models/__init__.py new file mode 100644 index 0000000000..6ac2cf86a3 --- /dev/null +++ b/base_substate/models/__init__.py @@ -0,0 +1,2 @@ +from . import base_substate +from . import base_substate_mixin diff --git a/base_substate/models/base_substate.py b/base_substate/models/base_substate.py new file mode 100644 index 0000000000..5c14994ec5 --- /dev/null +++ b/base_substate/models/base_substate.py @@ -0,0 +1,89 @@ +# Copyright 2020 Akretion +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class BaseSubstateType(models.Model): + """This model define technical data which precises + for each target model concerned by substate, + the technical "state" field name. + Data in this model should be created by import as technical data + in the specific module. For exemple in sale_subsatate we can define: + base.substate.type: + - name: Sale order Substate + - model: sale.order + - target_state_field: state + """ + _name = "base.substate.type" + _description = 'Base Substate Type' + _order = 'name asc, model asc' + + name = fields.Char(required=True, translate=True) + model = fields.Selection(selection=[], string='Apply on', required=True) + target_state_field = fields.Char( + required=True, help='Technical target state field name.' + ' Ex for sale order "state" for other "status" ... ') + + +class TargetStateValue(models.Model): + """This model define technical data that precise the translatable name + of the target model state (ex:Quotation for 'draft' State) + Data in this model should be created by import as technical data + in specific module ex : sale_subsatate + """ + _name = "target.state.value" + _description = 'Target State Value' + _order = 'name asc' + + name = fields.Char( + 'Target state Name', + required=True, + translate=True, + help='Target state translateble name.\n' + 'Ex: for sale order "Quotation", "Sale order", "Locked"...') + base_substate_type_id = fields.Many2one( + 'base.substate.type', + string='Substate Type', + ondelete='restrict', + ) + target_state_value = fields.Char( + required=True, help='Technical target state value.\n' + 'Ex: for sale order "draft", "sale", "done", ...') + model = fields.Selection(related='base_substate_type_id.model', + store=True, readonly=True, + help="Model for technical use") + + +class BaseSubstate(models.Model): + """This model define substates that will be applied on the target model. + for each state we can define one or more substate. + ex: + for the quotation state of a sale order we can define + 3 substates "In negotiation", + "Won" and "Lost". + We can also send mail when the susbstate is reached. + """ + _name = "base.substate" + _description = 'Base Substate' + _order = 'active desc, sequence asc' + + name = fields.Char('Substate Name', required=True, translate=True) + description = fields.Text(translate=True) + sequence = fields.Integer( + index=True, + help="Gives the sequence order when applying the default substate", + ) + target_state_value_id = fields.Many2one( + 'target.state.value', + string='Target State Value', + ondelete='restrict') + active = fields.Boolean(default=True) + mail_template_id = fields.Many2one( + 'mail.template', + string='Email Template', + help="If set, an email will be sent to the partner " + "when the object reaches this substate.") + model = fields.Selection(related='target_state_value_id.model', + store=True, readonly=True, + help="Model for technical use") diff --git a/base_substate/models/base_substate_mixin.py b/base_substate/models/base_substate_mixin.py new file mode 100644 index 0000000000..42cb60f987 --- /dev/null +++ b/base_substate/models/base_substate_mixin.py @@ -0,0 +1,89 @@ +# Copyright 2020 Akretion +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models, _ +from odoo.exceptions import ValidationError + + +class BaseSubstateMixin(models.AbstractModel): + _name = "base.substate.mixin" + _description = 'BaseSubstate Mixin' + + def _get_default_substate_id(self, state_val=False): + """ Gives default substate_id """ + search_domain = self._get_default_substate_domain(state_val) + # perform search, return the first found + return self.env['base.substate'].search(search_domain, + order='sequence', limit=1).id + + def _get_default_substate_domain(self, state_val=False): + """ Override this method + to change domain values + """ + if not state_val: + state_val = self._get_default_state_value() + substate_type = self._get_substate_type() + state_field = substate_type.target_state_field + if self and not state_val and state_field in self._fields: + state_val = self[state_field] + + domain = [('target_state_value_id.target_state_value', '=', state_val)] + domain += [('target_state_value_id.base_substate_type_id', + '=', substate_type.id)] + return domain + + def _get_default_state_value(self,): + """ Override this method + to change state_value + """ + return 'draft' + + def _get_substate_type(self,): + """ Override this method + to change substate_type (get by xml id for example) + """ + return self.env['base.substate.type'].search( + [('model', '=', self._name)], limit=1) + + substate_id = fields.Many2one( + 'base.substate', + string='Sub State', + ondelete='restrict', + default=lambda self: self._get_default_substate_id(), + track_visibility='onchange', + index=True, + domain=lambda self: [ + ('model', + '=', + self._name)], + copy=False) + + @api.constrains('substate_id') + def check_substate_id_consistency(self): + for mixin_obj in self: + if (mixin_obj.substate_id and + mixin_obj.substate_id.model != self._name): + raise ValidationError( + _("This substate is not define for this object but for %s" + ) % + mixin_obj.substate_id.model) + + def _update_before_write_create(self, values): + substate_type = self._get_substate_type() + state_field = substate_type.target_state_field + if values.get(state_field) and not values.get('substate_id'): + state_val = values.get(state_field) + values['substate_id'] = self._get_default_substate_id(state_val) + return values + + @api.multi + def write(self, values): + values = self._update_before_write_create(values) + res = super().write(values) + return res + + @api.model + def create(self, values): + values = self._update_before_write_create(values) + res = super().create(values) + return res diff --git a/base_substate/readme/CONTRIBUTORS.rst b/base_substate/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000000..f55657f6b9 --- /dev/null +++ b/base_substate/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Mourad EL HADJ MIMOUNE diff --git a/base_substate/readme/DESCRIPTION.rst b/base_substate/readme/DESCRIPTION.rst new file mode 100644 index 0000000000..290681551d --- /dev/null +++ b/base_substate/readme/DESCRIPTION.rst @@ -0,0 +1,12 @@ +This module provide abstract models to manage customizable +substates to be applied on different models (sale order, purchase, ...). + +example: +-------- + +* for the quotation state of a sale order we can define 3 substates "In negotiation", + "Won" and "Lost". +* We can also send mail when the susbstate is reached. + +It is not useful for itself. You can see an example of implementation +in the 'sale_substate' module. (sale-workflow repository). diff --git a/base_substate/readme/USAGE.rst b/base_substate/readme/USAGE.rst new file mode 100644 index 0000000000..e4bfecbcbb --- /dev/null +++ b/base_substate/readme/USAGE.rst @@ -0,0 +1 @@ +#. You must install an application module depending this one (for example sale_substate) diff --git a/base_substate/security/base_substate_security.xml b/base_substate/security/base_substate_security.xml new file mode 100644 index 0000000000..793ef434d2 --- /dev/null +++ b/base_substate/security/base_substate_security.xml @@ -0,0 +1,9 @@ + + + + + Substate manager + + + + diff --git a/base_substate/security/ir.model.access.csv b/base_substate/security/ir.model.access.csv new file mode 100644 index 0000000000..4cdb1a9ebd --- /dev/null +++ b/base_substate/security/ir.model.access.csv @@ -0,0 +1,7 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_base_substate_type,base.substate.type user,model_base_substate_type,base.group_user,1,0,0,0 +access_base_substate_type_manager,base.substate.type manager,model_base_substate_type,base_substate.group_substate_manager,1,1,1,1 +access_target_state_value,base.substate.value user,model_target_state_value,base.group_user,1,0,0,0 +access_target_state_value_manager,base.substate.value manager,model_target_state_value,base_substate.group_substate_manager,1,1,1,1 +access_base_substate,base.substate user,model_base_substate,base.group_user,1,0,0,0 +access_base_substate_manager,base.substate manager,model_base_substate,base_substate.group_substate_manager,1,1,1,1 diff --git a/base_substate/static/description/icon.png b/base_substate/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3a0328b516c4980e8e44cdb63fd945757ddd132d GIT binary patch literal 9455 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 z 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 Xviia!U7SGha1wx#SCgwmn*{w2TRX*I literal 0 HcmV?d00001 diff --git a/base_substate/static/description/index.html b/base_substate/static/description/index.html new file mode 100644 index 0000000000..6511a08abd --- /dev/null +++ b/base_substate/static/description/index.html @@ -0,0 +1,437 @@ + + + + + + +Base Sub State + + + +
+

Base Sub State

+ + +

Beta License: AGPL-3 OCA/server-ux Translate me on Weblate Try me on Runbot

+

This module provide abstract models to manage customizable +substates to be applied on different models (sale order, purchase, …).

+
+

example:

+
    +
  • for the quotation state of a sale order we can define 3 substates “In negotiation”, +“Won” and “Lost”.
  • +
  • We can also send mail when the susbstate is reached.
  • +
+

It is not useful for itself. You can see an example of implementation +in the ‘sale_substate’ module. (sale-workflow repository).

+

Table of contents

+ +
+

Usage

+
    +
  1. You must install an application module depending this one (for example sale_substate)
  2. +
+
+
+

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 smashing it by providing a detailed and welcomed +feedback.

+

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

+
+
+

Credits

+
+

Authors

+
    +
  • Akretion
  • +
+
+
+

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/server-ux project on GitHub.

+

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

+
+
+
+
+ + diff --git a/base_substate/tests/__init__.py b/base_substate/tests/__init__.py new file mode 100644 index 0000000000..bc0f1b9e60 --- /dev/null +++ b/base_substate/tests/__init__.py @@ -0,0 +1,4 @@ + +from . import models_mixin +from . import sale_test +from . import test_base_substate diff --git a/base_substate/tests/models_mixin.py b/base_substate/tests/models_mixin.py new file mode 100644 index 0000000000..80d4fb8391 --- /dev/null +++ b/base_substate/tests/models_mixin.py @@ -0,0 +1,128 @@ +# Copyright 2018 Simone Orsi - Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from operator import attrgetter + + +class TestMixin(object): + """Mixin to setup fake models for tests. + + Usage - the model: + + class FakeModel(models.Model, TestMixin): + _name = 'fake.model' + + name = fields.Char() + + Usage - the test klass: + + @classmethod + def setUpClass(cls): + super().setUpClass() + FakeModel._test_setup_model(cls.env) + + @classmethod + def tearDownClass(cls): + FakeModel._test_teardown_model(cls.env) + super().tearDownClass() + """ + + # Generate xmlids + # This is needed if you want to load data tied to a test model via xid. + _test_setup_gen_xid = False + # If you extend a real model (ie: res.partner) you must enable this + # to not delete the model on tear down. + _test_teardown_no_delete = False + # You can add custom fields to real models (eg: res.partner). + # In this case you must delete them to leave registry and model clean. + # This is mandatory for relational fields that link a fake model. + _test_purge_fields = [] + + @classmethod + def _test_setup_models(cls, env, model_clses): + """ + Setup models at the same time + if one fake model ref to another in relational + field. + ex : many2one fields + in this case we should don't use manual=True as an option in field. + """ + for model_cls in model_clses: + model_cls._build_model(env.registry, env.cr) + + env.registry.setup_models(env.cr) + ctx = dict(env.context, update_custom_fields=True) + if cls._test_setup_gen_xid: + ctx["module"] = cls._module + env.registry.init_models( + env.cr, [model_cls._name for model_cls in model_clses], ctx + ) + + @classmethod + def _test_setup_model(cls, env): + """Initialize it.""" + cls._build_model(env.registry, env.cr) + env.registry.setup_models(env.cr) + ctx = dict(env.context, update_custom_fields=True) + if cls._test_setup_gen_xid: + ctx["module"] = cls._module + env.registry.init_models(env.cr, [cls._name], ctx) + + @classmethod + def _test_teardown_model(cls, env): + """Cleanup registry and real models.""" + + for fname in cls._test_purge_fields: + model = env[cls._name] + if fname in model: + model._pop_field(fname) + + if not getattr(cls, "_test_teardown_no_delete", False): + del env.registry.models[cls._name] + # here we must remove the model from list of children of inherited + # models + parents = cls._inherit + parents = [ + parents] if isinstance(parents, str) else (parents or []) + # keep a copy to be sure to not modify the original _inherit + parents = list(parents) + parents.extend(cls._inherits.keys()) + parents.append("base") + funcs = [ + attrgetter(kind + "_children") for kind in + ["_inherits", "_inherit"] + ] + for parent in parents: + for func in funcs: + children = func(env.registry[parent]) + if cls._name in children: + # at this stage our cls is referenced as children of + # parent -> must un reference it + children.remove(cls._name) + + def _test_get_model_id(self): + self.env.cr.execute( + "SELECT id FROM ir_model WHERE model = %s", (self._name,)) + res = self.env.cr.fetchone() + return res[0] if res else None + + def _test_create_ACL(self, **kw): + model_id = self._test_get_model_id() + if not model_id: + self._reflect() + model_id = self._test_get_model_id() + if model_id: + vals = self._test_ACL_values(model_id) + vals.update(kw) + self.env["ir.model.access"].create(vals) + + def _test_ACL_values(self, model_id): + values = { + "name": "Fake ACL for %s" % self._name, + "model_id": model_id, + "perm_read": 1, + "perm_create": 1, + "perm_write": 1, + "perm_unlink": 1, + "active": True, + } + return values diff --git a/base_substate/tests/sale_test.py b/base_substate/tests/sale_test.py new file mode 100644 index 0000000000..618bcf68ed --- /dev/null +++ b/base_substate/tests/sale_test.py @@ -0,0 +1,57 @@ +# Copyright 2020 Akretion Mourad EL HADJ MIMOUNE +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from odoo import api, fields, models + +from .models_mixin import TestMixin + + +class SaleTest(models.Model, TestMixin): + _inherit = 'base.substate.mixin' + _name = "base.substate.test.sale" + _description = "Base substate Test Model" + + name = fields.Char(required=True) + user_id = fields.Many2one('res.users', string='Responsible') + state = fields.Selection( + [('draft', 'New'), ('cancel', 'Cancelled'), + ('sale', 'Sale'), + ('done', 'Done')], + string="Status", readonly=True, default='draft') + active = fields.Boolean(default=True) + partner_id = fields.Many2one('res.partner', string='Partner') + line_ids = fields.One2many( + comodel_name='base.substate.test.sale.line', + inverse_name='sale_id', + context={"active_test": False}, + ) + amount_total = fields.Float( + compute='_compute_amount_total', store=True) + + @api.depends('line_ids') + def _compute_amount_total(cls): + for record in cls: + for line in record.line_ids: + record.amount_total += line.amount * line.qty + + @api.multi + def button_confirm(cls): + cls.write({'state': 'sale'}) + return True + + @api.multi + def button_cancel(cls): + cls.write({'state': 'cancel'}) + + +class LineTest(models.Model, TestMixin): + _name = "base.substate.test.sale.line" + _description = "Base substate Test Model Line" + + name = fields.Char() + sale_id = fields.Many2one( + comodel_name='base.substate.test.sale', + ondelete='cascade', + context={"active_test": False}, + ) + qty = fields.Float() + amount = fields.Float() diff --git a/base_substate/tests/test_base_substate.py b/base_substate/tests/test_base_substate.py new file mode 100644 index 0000000000..ec8b07f4b9 --- /dev/null +++ b/base_substate/tests/test_base_substate.py @@ -0,0 +1,104 @@ +# Copyright 2020 Akretion Mourad EL HADJ MIMOUNE +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo.tests import common +from .sale_test import SaleTest, LineTest + + +@common.at_install(False) +@common.post_install(True) +class TestBaseSubstate(common.SavepointCase): + + @classmethod + def setUpClass(cls): + super(TestBaseSubstate, cls).setUpClass() + SaleTest._test_setup_models(cls.env, [SaleTest, LineTest]) + LineTest._test_setup_model(cls.env) + + cls.substate_test_sale = cls.env['base.substate.test.sale'] + cls.substate_test_sale_line = cls.env['base.substate.test.sale.line'] + + cls.base_substate = cls.env['base.substate.mixin'] + cls.substate_type = cls.env['base.substate.type'] + + cls.substate_type._fields['model'].selection.append( + ('base.substate.test.sale', 'Sale Order')) + + cls.substate_type = cls.env['base.substate.type'].create({ + 'name': "Sale", + 'model': "base.substate.test.sale", + 'target_state_field': "state", + }) + + cls.substate_val_quotation = cls.env['target.state.value'].create({ + 'name': "Quotation", + 'base_substate_type_id': cls.substate_type.id, + 'target_state_value': "draft", + }) + + cls.substate_val_sale = cls.env['target.state.value'].create({ + 'name': "Sale order", + 'base_substate_type_id': cls.substate_type.id, + 'target_state_value': "sale", + }) + cls.substate_under_negotiation = cls.env['base.substate'].create({ + 'name': "Under negotiation", + 'sequence': 1, + 'target_state_value_id': cls.substate_val_quotation.id, + }) + + cls.substate_won = cls.env['base.substate'].create({ + 'name': "Won", + 'sequence': 1, + 'target_state_value_id': cls.substate_val_quotation.id, + }) + + cls.substate_wait_docs = cls.env['base.substate'].create({ + 'name': "Waiting for legal documents", + 'sequence': 2, + 'target_state_value_id': cls.substate_val_sale.id, + }) + + cls.substate_valid_docs = cls.env['base.substate'].create({ + 'name': "To validate legal documents", + 'sequence': 3, + 'target_state_value_id': cls.substate_val_sale.id, + }) + + cls.substate_in_delivering = cls.env['base.substate'].create({ + 'name': "In delivering", + 'sequence': 4, + 'target_state_value_id': cls.substate_val_sale.id, + }) + + @classmethod + def tearDownClass(cls): + SaleTest._test_teardown_model(cls.env) + LineTest._test_teardown_model(cls.env) + super().tearDownClass() + + def test_sale_order_substate(self): + partner = self.env.ref('base.res_partner_1') + so_test1 = self.substate_test_sale.create({ + 'name': 'Test base substate to basic sale', + 'partner_id': partner.id, + 'line_ids': [(0, 0, { + 'name': "line test", + 'amount': 120.0, + 'qty': 1.5, + })], + }) + self.assertTrue(so_test1.state == 'draft') + self.assertTrue(so_test1.substate_id == + self.substate_under_negotiation) + + # Test that validation of sale order change substate_id + so_test1.button_confirm() + self.assertTrue(so_test1.state == 'sale') + self.assertTrue(so_test1.substate_id == self.substate_wait_docs) + + # Test that substate_id is set to false if + # there is not substate corresponding to state + so_test1.button_cancel() + self.assertTrue(so_test1.state == 'cancel') + self.assertTrue(not so_test1.substate_id) diff --git a/base_substate/views/base_substate_type_views.xml b/base_substate/views/base_substate_type_views.xml new file mode 100644 index 0000000000..ef8bfbffa9 --- /dev/null +++ b/base_substate/views/base_substate_type_views.xml @@ -0,0 +1,79 @@ + + + + + base.substate.type + + + + + + + + + + + base.substate.type + +
+ +
+
+ + + + + + +
+
+
+
+ + + base.substate.type + + + + + + + + + + + Sub State Type + ir.actions.act_window + base.substate.type + form + tree,form + + [] + {} + + + + + + form + + + + + + + tree + + + + +
diff --git a/base_substate/views/base_substate_value_views.xml b/base_substate/views/base_substate_value_views.xml new file mode 100644 index 0000000000..be3dad089b --- /dev/null +++ b/base_substate/views/base_substate_value_views.xml @@ -0,0 +1,76 @@ + + + + + target.state.value + + + + + + + + + + + target.state.value + +
+ +
+
+ + + + + + +
+
+
+
+ + + target.state.value + + + + + + + + + + + Target State Value + ir.actions.act_window + target.state.value + form + tree,form + + [] + {} + + + + + + form + + + + + + + tree + + + + +
diff --git a/base_substate/views/base_substate_views.xml b/base_substate/views/base_substate_views.xml new file mode 100644 index 0000000000..f52d10969b --- /dev/null +++ b/base_substate/views/base_substate_views.xml @@ -0,0 +1,84 @@ + + + + + base.substate + + + + + + + + + + + + + base.substate + +
+ +
+ +
+
+
+ + + + + + + + + +
+
+
+
+ + + base.substate + + + + + + + + + Base Substate + ir.actions.act_window + base.substate + form + tree,form + + [] + {} + + + + + + form + + + + + + + tree + + + + +
From 5089bfa93bff67c6deda5c70d85f93555a8a4d2f Mon Sep 17 00:00:00 2001 From: Kitti U Date: Sun, 30 Aug 2020 10:45:46 +0700 Subject: [PATCH 02/17] [IMP] base_substate: black, isort, prettier --- base_substate/__manifest__.py | 4 +- base_substate/models/base_substate.py | 75 ++++---- base_substate/models/base_substate_mixin.py | 56 +++--- base_substate/readme/USAGE.rst | 2 +- .../security/base_substate_security.xml | 6 +- base_substate/tests/__init__.py | 1 - base_substate/tests/models_mixin.py | 9 +- base_substate/tests/sale_test.py | 32 ++-- base_substate/tests/test_base_substate.py | 160 +++++++++-------- .../views/base_substate_type_views.xml | 155 +++++++++-------- .../views/base_substate_value_views.xml | 150 ++++++++-------- base_substate/views/base_substate_views.xml | 164 +++++++++--------- 12 files changed, 429 insertions(+), 385 deletions(-) diff --git a/base_substate/__manifest__.py b/base_substate/__manifest__.py index 59e08fb2e2..ddc8c4ce96 100644 --- a/base_substate/__manifest__.py +++ b/base_substate/__manifest__.py @@ -10,8 +10,8 @@ "license": "AGPL-3", "depends": ["base"], "data": [ - 'security/base_substate_security.xml', - 'security/ir.model.access.csv', + "security/base_substate_security.xml", + "security/ir.model.access.csv", "views/base_substate_type_views.xml", "views/base_substate_value_views.xml", "views/base_substate_views.xml", diff --git a/base_substate/models/base_substate.py b/base_substate/models/base_substate.py index 5c14994ec5..a312488942 100644 --- a/base_substate/models/base_substate.py +++ b/base_substate/models/base_substate.py @@ -15,15 +15,18 @@ class BaseSubstateType(models.Model): - model: sale.order - target_state_field: state """ + _name = "base.substate.type" - _description = 'Base Substate Type' - _order = 'name asc, model asc' + _description = "Base Substate Type" + _order = "name asc, model asc" name = fields.Char(required=True, translate=True) - model = fields.Selection(selection=[], string='Apply on', required=True) + model = fields.Selection(selection=[], string="Apply on", required=True) target_state_field = fields.Char( - required=True, help='Technical target state field name.' - ' Ex for sale order "state" for other "status" ... ') + required=True, + help="Technical target state field name." + ' Ex for sale order "state" for other "status" ... ', + ) class TargetStateValue(models.Model): @@ -32,27 +35,32 @@ class TargetStateValue(models.Model): Data in this model should be created by import as technical data in specific module ex : sale_subsatate """ + _name = "target.state.value" - _description = 'Target State Value' - _order = 'name asc' + _description = "Target State Value" + _order = "name asc" name = fields.Char( - 'Target state Name', + "Target state Name", required=True, translate=True, - help='Target state translateble name.\n' - 'Ex: for sale order "Quotation", "Sale order", "Locked"...') + help="Target state translateble name.\n" + 'Ex: for sale order "Quotation", "Sale order", "Locked"...', + ) base_substate_type_id = fields.Many2one( - 'base.substate.type', - string='Substate Type', - ondelete='restrict', + "base.substate.type", string="Substate Type", ondelete="restrict", ) target_state_value = fields.Char( - required=True, help='Technical target state value.\n' - 'Ex: for sale order "draft", "sale", "done", ...') - model = fields.Selection(related='base_substate_type_id.model', - store=True, readonly=True, - help="Model for technical use") + required=True, + help="Technical target state value.\n" + 'Ex: for sale order "draft", "sale", "done", ...', + ) + model = fields.Selection( + related="base_substate_type_id.model", + store=True, + readonly=True, + help="Model for technical use", + ) class BaseSubstate(models.Model): @@ -64,26 +72,29 @@ class BaseSubstate(models.Model): "Won" and "Lost". We can also send mail when the susbstate is reached. """ + _name = "base.substate" - _description = 'Base Substate' - _order = 'active desc, sequence asc' + _description = "Base Substate" + _order = "active desc, sequence asc" - name = fields.Char('Substate Name', required=True, translate=True) + name = fields.Char("Substate Name", required=True, translate=True) description = fields.Text(translate=True) sequence = fields.Integer( - index=True, - help="Gives the sequence order when applying the default substate", + index=True, help="Gives the sequence order when applying the default substate", ) target_state_value_id = fields.Many2one( - 'target.state.value', - string='Target State Value', - ondelete='restrict') + "target.state.value", string="Target State Value", ondelete="restrict" + ) active = fields.Boolean(default=True) mail_template_id = fields.Many2one( - 'mail.template', - string='Email Template', + "mail.template", + string="Email Template", help="If set, an email will be sent to the partner " - "when the object reaches this substate.") - model = fields.Selection(related='target_state_value_id.model', - store=True, readonly=True, - help="Model for technical use") + "when the object reaches this substate.", + ) + model = fields.Selection( + related="target_state_value_id.model", + store=True, + readonly=True, + help="Model for technical use", + ) diff --git a/base_substate/models/base_substate_mixin.py b/base_substate/models/base_substate_mixin.py index 42cb60f987..66961006c8 100644 --- a/base_substate/models/base_substate_mixin.py +++ b/base_substate/models/base_substate_mixin.py @@ -1,20 +1,23 @@ # Copyright 2020 Akretion # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import api, fields, models, _ +from odoo import _, api, fields, models from odoo.exceptions import ValidationError class BaseSubstateMixin(models.AbstractModel): _name = "base.substate.mixin" - _description = 'BaseSubstate Mixin' + _description = "BaseSubstate Mixin" def _get_default_substate_id(self, state_val=False): """ Gives default substate_id """ search_domain = self._get_default_substate_domain(state_val) # perform search, return the first found - return self.env['base.substate'].search(search_domain, - order='sequence', limit=1).id + return ( + self.env["base.substate"] + .search(search_domain, order="sequence", limit=1) + .id + ) def _get_default_substate_domain(self, state_val=False): """ Override this method @@ -27,53 +30,52 @@ def _get_default_substate_domain(self, state_val=False): if self and not state_val and state_field in self._fields: state_val = self[state_field] - domain = [('target_state_value_id.target_state_value', '=', state_val)] - domain += [('target_state_value_id.base_substate_type_id', - '=', substate_type.id)] + domain = [("target_state_value_id.target_state_value", "=", state_val)] + domain += [ + ("target_state_value_id.base_substate_type_id", "=", substate_type.id) + ] return domain def _get_default_state_value(self,): """ Override this method to change state_value """ - return 'draft' + return "draft" def _get_substate_type(self,): """ Override this method to change substate_type (get by xml id for example) """ - return self.env['base.substate.type'].search( - [('model', '=', self._name)], limit=1) + return self.env["base.substate.type"].search( + [("model", "=", self._name)], limit=1 + ) substate_id = fields.Many2one( - 'base.substate', - string='Sub State', - ondelete='restrict', + "base.substate", + string="Sub State", + ondelete="restrict", default=lambda self: self._get_default_substate_id(), - track_visibility='onchange', + track_visibility="onchange", index=True, - domain=lambda self: [ - ('model', - '=', - self._name)], - copy=False) + domain=lambda self: [("model", "=", self._name)], + copy=False, + ) - @api.constrains('substate_id') + @api.constrains("substate_id") def check_substate_id_consistency(self): for mixin_obj in self: - if (mixin_obj.substate_id and - mixin_obj.substate_id.model != self._name): + if mixin_obj.substate_id and mixin_obj.substate_id.model != self._name: raise ValidationError( - _("This substate is not define for this object but for %s" - ) % - mixin_obj.substate_id.model) + _("This substate is not define for this object but for %s") + % mixin_obj.substate_id.model + ) def _update_before_write_create(self, values): substate_type = self._get_substate_type() state_field = substate_type.target_state_field - if values.get(state_field) and not values.get('substate_id'): + if values.get(state_field) and not values.get("substate_id"): state_val = values.get(state_field) - values['substate_id'] = self._get_default_substate_id(state_val) + values["substate_id"] = self._get_default_substate_id(state_val) return values @api.multi diff --git a/base_substate/readme/USAGE.rst b/base_substate/readme/USAGE.rst index e4bfecbcbb..1b8849ea93 100644 --- a/base_substate/readme/USAGE.rst +++ b/base_substate/readme/USAGE.rst @@ -1 +1 @@ -#. You must install an application module depending this one (for example sale_substate) +#. You must install an application module depending this one (for example sale_substate) diff --git a/base_substate/security/base_substate_security.xml b/base_substate/security/base_substate_security.xml index 793ef434d2..4f98ee90ea 100644 --- a/base_substate/security/base_substate_security.xml +++ b/base_substate/security/base_substate_security.xml @@ -1,9 +1,7 @@ - + - Substate manager - + - diff --git a/base_substate/tests/__init__.py b/base_substate/tests/__init__.py index bc0f1b9e60..996498a1b5 100644 --- a/base_substate/tests/__init__.py +++ b/base_substate/tests/__init__.py @@ -1,4 +1,3 @@ - from . import models_mixin from . import sale_test from . import test_base_substate diff --git a/base_substate/tests/models_mixin.py b/base_substate/tests/models_mixin.py index 80d4fb8391..a238132e61 100644 --- a/base_substate/tests/models_mixin.py +++ b/base_substate/tests/models_mixin.py @@ -81,15 +81,13 @@ def _test_teardown_model(cls, env): # here we must remove the model from list of children of inherited # models parents = cls._inherit - parents = [ - parents] if isinstance(parents, str) else (parents or []) + parents = [parents] if isinstance(parents, str) else (parents or []) # keep a copy to be sure to not modify the original _inherit parents = list(parents) parents.extend(cls._inherits.keys()) parents.append("base") funcs = [ - attrgetter(kind + "_children") for kind in - ["_inherits", "_inherit"] + attrgetter(kind + "_children") for kind in ["_inherits", "_inherit"] ] for parent in parents: for func in funcs: @@ -100,8 +98,7 @@ def _test_teardown_model(cls, env): children.remove(cls._name) def _test_get_model_id(self): - self.env.cr.execute( - "SELECT id FROM ir_model WHERE model = %s", (self._name,)) + self.env.cr.execute("SELECT id FROM ir_model WHERE model = %s", (self._name,)) res = self.env.cr.fetchone() return res[0] if res else None diff --git a/base_substate/tests/sale_test.py b/base_substate/tests/sale_test.py index 618bcf68ed..4e59e2c36c 100644 --- a/base_substate/tests/sale_test.py +++ b/base_substate/tests/sale_test.py @@ -6,28 +6,28 @@ class SaleTest(models.Model, TestMixin): - _inherit = 'base.substate.mixin' + _inherit = "base.substate.mixin" _name = "base.substate.test.sale" _description = "Base substate Test Model" name = fields.Char(required=True) - user_id = fields.Many2one('res.users', string='Responsible') + user_id = fields.Many2one("res.users", string="Responsible") state = fields.Selection( - [('draft', 'New'), ('cancel', 'Cancelled'), - ('sale', 'Sale'), - ('done', 'Done')], - string="Status", readonly=True, default='draft') + [("draft", "New"), ("cancel", "Cancelled"), ("sale", "Sale"), ("done", "Done")], + string="Status", + readonly=True, + default="draft", + ) active = fields.Boolean(default=True) - partner_id = fields.Many2one('res.partner', string='Partner') + partner_id = fields.Many2one("res.partner", string="Partner") line_ids = fields.One2many( - comodel_name='base.substate.test.sale.line', - inverse_name='sale_id', + comodel_name="base.substate.test.sale.line", + inverse_name="sale_id", context={"active_test": False}, ) - amount_total = fields.Float( - compute='_compute_amount_total', store=True) + amount_total = fields.Float(compute="_compute_amount_total", store=True) - @api.depends('line_ids') + @api.depends("line_ids") def _compute_amount_total(cls): for record in cls: for line in record.line_ids: @@ -35,12 +35,12 @@ def _compute_amount_total(cls): @api.multi def button_confirm(cls): - cls.write({'state': 'sale'}) + cls.write({"state": "sale"}) return True @api.multi def button_cancel(cls): - cls.write({'state': 'cancel'}) + cls.write({"state": "cancel"}) class LineTest(models.Model, TestMixin): @@ -49,8 +49,8 @@ class LineTest(models.Model, TestMixin): name = fields.Char() sale_id = fields.Many2one( - comodel_name='base.substate.test.sale', - ondelete='cascade', + comodel_name="base.substate.test.sale", + ondelete="cascade", context={"active_test": False}, ) qty = fields.Float() diff --git a/base_substate/tests/test_base_substate.py b/base_substate/tests/test_base_substate.py index ec8b07f4b9..b12ea2382d 100644 --- a/base_substate/tests/test_base_substate.py +++ b/base_substate/tests/test_base_substate.py @@ -2,74 +2,91 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). from odoo.tests import common -from .sale_test import SaleTest, LineTest + +from .sale_test import LineTest, SaleTest @common.at_install(False) @common.post_install(True) class TestBaseSubstate(common.SavepointCase): - @classmethod def setUpClass(cls): super(TestBaseSubstate, cls).setUpClass() SaleTest._test_setup_models(cls.env, [SaleTest, LineTest]) LineTest._test_setup_model(cls.env) - cls.substate_test_sale = cls.env['base.substate.test.sale'] - cls.substate_test_sale_line = cls.env['base.substate.test.sale.line'] - - cls.base_substate = cls.env['base.substate.mixin'] - cls.substate_type = cls.env['base.substate.type'] - - cls.substate_type._fields['model'].selection.append( - ('base.substate.test.sale', 'Sale Order')) - - cls.substate_type = cls.env['base.substate.type'].create({ - 'name': "Sale", - 'model': "base.substate.test.sale", - 'target_state_field': "state", - }) - - cls.substate_val_quotation = cls.env['target.state.value'].create({ - 'name': "Quotation", - 'base_substate_type_id': cls.substate_type.id, - 'target_state_value': "draft", - }) - - cls.substate_val_sale = cls.env['target.state.value'].create({ - 'name': "Sale order", - 'base_substate_type_id': cls.substate_type.id, - 'target_state_value': "sale", - }) - cls.substate_under_negotiation = cls.env['base.substate'].create({ - 'name': "Under negotiation", - 'sequence': 1, - 'target_state_value_id': cls.substate_val_quotation.id, - }) - - cls.substate_won = cls.env['base.substate'].create({ - 'name': "Won", - 'sequence': 1, - 'target_state_value_id': cls.substate_val_quotation.id, - }) - - cls.substate_wait_docs = cls.env['base.substate'].create({ - 'name': "Waiting for legal documents", - 'sequence': 2, - 'target_state_value_id': cls.substate_val_sale.id, - }) - - cls.substate_valid_docs = cls.env['base.substate'].create({ - 'name': "To validate legal documents", - 'sequence': 3, - 'target_state_value_id': cls.substate_val_sale.id, - }) - - cls.substate_in_delivering = cls.env['base.substate'].create({ - 'name': "In delivering", - 'sequence': 4, - 'target_state_value_id': cls.substate_val_sale.id, - }) + cls.substate_test_sale = cls.env["base.substate.test.sale"] + cls.substate_test_sale_line = cls.env["base.substate.test.sale.line"] + + cls.base_substate = cls.env["base.substate.mixin"] + cls.substate_type = cls.env["base.substate.type"] + + cls.substate_type._fields["model"].selection.append( + ("base.substate.test.sale", "Sale Order") + ) + + cls.substate_type = cls.env["base.substate.type"].create( + { + "name": "Sale", + "model": "base.substate.test.sale", + "target_state_field": "state", + } + ) + + cls.substate_val_quotation = cls.env["target.state.value"].create( + { + "name": "Quotation", + "base_substate_type_id": cls.substate_type.id, + "target_state_value": "draft", + } + ) + + cls.substate_val_sale = cls.env["target.state.value"].create( + { + "name": "Sale order", + "base_substate_type_id": cls.substate_type.id, + "target_state_value": "sale", + } + ) + cls.substate_under_negotiation = cls.env["base.substate"].create( + { + "name": "Under negotiation", + "sequence": 1, + "target_state_value_id": cls.substate_val_quotation.id, + } + ) + + cls.substate_won = cls.env["base.substate"].create( + { + "name": "Won", + "sequence": 1, + "target_state_value_id": cls.substate_val_quotation.id, + } + ) + + cls.substate_wait_docs = cls.env["base.substate"].create( + { + "name": "Waiting for legal documents", + "sequence": 2, + "target_state_value_id": cls.substate_val_sale.id, + } + ) + + cls.substate_valid_docs = cls.env["base.substate"].create( + { + "name": "To validate legal documents", + "sequence": 3, + "target_state_value_id": cls.substate_val_sale.id, + } + ) + + cls.substate_in_delivering = cls.env["base.substate"].create( + { + "name": "In delivering", + "sequence": 4, + "target_state_value_id": cls.substate_val_sale.id, + } + ) @classmethod def tearDownClass(cls): @@ -78,27 +95,26 @@ def tearDownClass(cls): super().tearDownClass() def test_sale_order_substate(self): - partner = self.env.ref('base.res_partner_1') - so_test1 = self.substate_test_sale.create({ - 'name': 'Test base substate to basic sale', - 'partner_id': partner.id, - 'line_ids': [(0, 0, { - 'name': "line test", - 'amount': 120.0, - 'qty': 1.5, - })], - }) - self.assertTrue(so_test1.state == 'draft') - self.assertTrue(so_test1.substate_id == - self.substate_under_negotiation) + partner = self.env.ref("base.res_partner_1") + so_test1 = self.substate_test_sale.create( + { + "name": "Test base substate to basic sale", + "partner_id": partner.id, + "line_ids": [ + (0, 0, {"name": "line test", "amount": 120.0, "qty": 1.5,}) + ], + } + ) + self.assertTrue(so_test1.state == "draft") + self.assertTrue(so_test1.substate_id == self.substate_under_negotiation) # Test that validation of sale order change substate_id so_test1.button_confirm() - self.assertTrue(so_test1.state == 'sale') + self.assertTrue(so_test1.state == "sale") self.assertTrue(so_test1.substate_id == self.substate_wait_docs) # Test that substate_id is set to false if # there is not substate corresponding to state so_test1.button_cancel() - self.assertTrue(so_test1.state == 'cancel') + self.assertTrue(so_test1.state == "cancel") self.assertTrue(not so_test1.substate_id) diff --git a/base_substate/views/base_substate_type_views.xml b/base_substate/views/base_substate_type_views.xml index ef8bfbffa9..8abc769d45 100644 --- a/base_substate/views/base_substate_type_views.xml +++ b/base_substate/views/base_substate_type_views.xml @@ -1,79 +1,86 @@ - + - - base.substate.type - - - - - - - - - - - base.substate.type - -
- -
-
- - - - + + base.substate.type + + + + + + + + + + base.substate.type + + + +
+
+ + + + + -
-
-
-
-
- - - base.substate.type - - - - - - - - - - - Sub State Type - ir.actions.act_window - base.substate.type - form - tree,form - - [] - {} - - - - - - form - - - - - - - tree - - - - + + + + + + base.substate.type + + + + + + + + + + Sub State Type + ir.actions.act_window + base.substate.type + form + tree,form + + [] + {} + + + + + form + + + + + + tree + + + +
diff --git a/base_substate/views/base_substate_value_views.xml b/base_substate/views/base_substate_value_views.xml index be3dad089b..9e8b3b8df7 100644 --- a/base_substate/views/base_substate_value_views.xml +++ b/base_substate/views/base_substate_value_views.xml @@ -1,76 +1,84 @@ - + - - target.state.value - - - - - - - - - - - target.state.value - -
- -
-
- - - - + + target.state.value + + + + + + + + + + target.state.value + + + +
+
+ + + + + -
-
-
-
-
- - - target.state.value - - - - - - - - - - - Target State Value - ir.actions.act_window - target.state.value - form - tree,form - - [] - {} - - - - - - form - - - - - - - tree - - - - + + + + + + target.state.value + + + + + + + + + + Target State Value + ir.actions.act_window + target.state.value + form + tree,form + + [] + {} + + + + + form + + + + + + tree + + +
diff --git a/base_substate/views/base_substate_views.xml b/base_substate/views/base_substate_views.xml index f52d10969b..e404f8f3c9 100644 --- a/base_substate/views/base_substate_views.xml +++ b/base_substate/views/base_substate_views.xml @@ -1,84 +1,90 @@ - + - - base.substate - - - - - - - - - - - - - base.substate - -
- -
- -
-
-
- - - - - - - + + base.substate + + + + + + + + + + + + base.substate + + + +
+ +
+
+
+ + + + + + + + -
-
-
-
-
- - - base.substate - - - - - - - - - Base Substate - ir.actions.act_window - base.substate - form - tree,form - - [] - {} - - - - - - form - - - - - - - tree - - - - + + + + + + base.substate + + + + + + + + Base Substate + ir.actions.act_window + base.substate + form + tree,form + + [] + {} + + + + + form + + + + + + tree + + +
From 1e3f0088ff7b76b8ec6a3785e02fdefc8dacf53d Mon Sep 17 00:00:00 2001 From: Kitti U Date: Sun, 30 Aug 2020 11:04:10 +0700 Subject: [PATCH 03/17] [13.0][MIG] base_substate --- base_substate/README.rst | 15 ++++---- base_substate/__manifest__.py | 2 +- base_substate/i18n/base_substate.pot | 29 ++++++++++----- base_substate/models/base_substate_mixin.py | 37 ++++++++++++++++++- base_substate/readme/CONTRIBUTORS.rst | 1 + base_substate/readme/DESCRIPTION.rst | 2 +- base_substate/readme/USAGE.rst | 2 +- base_substate/static/description/index.html | 11 +++--- base_substate/tests/sale_test.py | 14 +++---- base_substate/tests/test_base_substate.py | 2 +- .../views/base_substate_type_views.xml | 1 - .../views/base_substate_value_views.xml | 1 - base_substate/views/base_substate_views.xml | 1 - 13 files changed, 81 insertions(+), 37 deletions(-) diff --git a/base_substate/README.rst b/base_substate/README.rst index e9772bdacf..e03817206d 100644 --- a/base_substate/README.rst +++ b/base_substate/README.rst @@ -14,13 +14,13 @@ Base Sub State :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--ux-lightgray.png?logo=github - :target: https://github.com/OCA/server-ux/tree/12.0/base_substate + :target: https://github.com/OCA/server-ux/tree/13.0/base_substate :alt: OCA/server-ux .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/server-ux-12-0/server-ux-12-0-base_substate + :target: https://translation.odoo-community.org/projects/server-ux-13-0/server-ux-13-0-base_substate :alt: Translate me on Weblate .. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png - :target: https://runbot.odoo-community.org/runbot/250/12.0 + :target: https://runbot.odoo-community.org/runbot/250/13.0 :alt: Try me on Runbot |badge1| |badge2| |badge3| |badge4| |badge5| @@ -36,7 +36,7 @@ example: * We can also send mail when the susbstate is reached. It is not useful for itself. You can see an example of implementation -in the 'sale_substate' module. (sale-workflow repository). +in the 'purchase_substate' module. (purchase-workflow repository). **Table of contents** @@ -46,7 +46,7 @@ in the 'sale_substate' module. (sale-workflow repository). Usage ===== -#. You must install an application module depending this one (for example sale_substate) +#. You must install an application module depending this one (for example purchase_substate) Bug Tracker =========== @@ -54,7 +54,7 @@ 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 smashing it by providing a detailed and welcomed -`feedback `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -70,6 +70,7 @@ Contributors ~~~~~~~~~~~~ * Mourad EL HADJ MIMOUNE +* Kitti U. Maintainers ~~~~~~~~~~~ @@ -84,6 +85,6 @@ 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-ux `_ project on GitHub. +This module is part of the `OCA/server-ux `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/base_substate/__manifest__.py b/base_substate/__manifest__.py index ddc8c4ce96..dd8219d50b 100644 --- a/base_substate/__manifest__.py +++ b/base_substate/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Base Sub State", - "version": "12.0.1.0.0", + "version": "13.0.1.0.0", "category": "Tools", "author": "Akretion, " "Odoo Community Association (OCA)", "website": "https://github.com/OCA/sale-workflow/", diff --git a/base_substate/i18n/base_substate.pot b/base_substate/i18n/base_substate.pot index 218c1a4cc7..7b28bc5cca 100644 --- a/base_substate/i18n/base_substate.pot +++ b/base_substate/i18n/base_substate.pot @@ -1,12 +1,12 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: -# * base_substate +# * base_substate # msgid "" msgstr "" -"Project-Id-Version: Odoo Server 12.0\n" +"Project-Id-Version: Odoo Server 13.0\n" "Report-Msgid-Bugs-To: \n" -"Last-Translator: <>\n" +"Last-Translator: \n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -91,7 +91,9 @@ msgstr "" #. module: base_substate #: model:ir.model.fields,help:base_substate.field_base_substate__mail_template_id -msgid "If set, an email will be sent to the partner when the object reaches this substate." +msgid "" +"If set, an email will be sent to the partner when the object reaches this " +"substate." msgstr "" #. module: base_substate @@ -188,24 +190,33 @@ msgstr "" #. module: base_substate #: model:ir.model.fields,help:base_substate.field_target_state_value__name -msgid "Target state translateble name.\n" +msgid "" +"Target state translateble name.\n" "Ex: for sale order \"Quotation\", \"Sale order\", \"Locked\"..." msgstr "" #. module: base_substate #: model:ir.model.fields,help:base_substate.field_base_substate_type__target_state_field -msgid "Technical target state field name. Ex for sale order \"state\" for other \"status\" ... " +msgid "" +"Technical target state field name. Ex for sale order \"state\" for other " +"\"status\" ... " msgstr "" #. module: base_substate #: model:ir.model.fields,help:base_substate.field_target_state_value__target_state_value -msgid "Technical target state value.\n" +msgid "" +"Technical target state value.\n" "Ex: for sale order \"draft\", \"sale\", \"done\", ..." msgstr "" #. module: base_substate -#: code:addons/base_substate/models/base_substate_mixin.py:67 +#: code:addons/base_substate/models/base_substate_mixin.py:0 #, python-format -msgid "This substate is not define for this object but for %s" +msgid "The substate \"%s\" is not define for the state \"%s\" but for \"%s\" " msgstr "" +#. module: base_substate +#: code:addons/base_substate/models/base_substate_mixin.py:0 +#, python-format +msgid "This substate is not define for this object but for %s" +msgstr "" diff --git a/base_substate/models/base_substate_mixin.py b/base_substate/models/base_substate_mixin.py index 66961006c8..cb994d3e0b 100644 --- a/base_substate/models/base_substate_mixin.py +++ b/base_substate/models/base_substate_mixin.py @@ -8,6 +8,42 @@ class BaseSubstateMixin(models.AbstractModel): _name = "base.substate.mixin" _description = "BaseSubstate Mixin" + _state_field = "state" + + @api.constrains("substate_id", _state_field) + def check_substate_id_value(self): + rec_states = dict(self._fields[self._state_field].selection) + for rec in self: + target_state = rec.substate_id.target_state_value_id.target_state_value + if rec.substate_id and rec.state != target_state: + raise ValidationError( + _( + 'The substate "%s" is not define for the state' + ' "%s" but for "%s" ' + ) + % ( + rec.substate_id.name, + _(rec_states[rec.state]), + _(rec_states[target_state]), + ) + ) + + def _track_template(self, tracking): + res = super()._track_template(tracking) + first_rec = self[0] + changes, tracking_value_ids = tracking[first_rec.id] + if "substate_id" in changes and first_rec.substate_id.mail_template_id: + res["substate_id"] = ( + first_rec.substate_id.mail_template_id, + { + "auto_delete_message": True, + "subtype_id": self.env["ir.model.data"].xmlid_to_res_id( + "mail.mt_note" + ), + "notif_layout": "mail.mail_notification_light", + }, + ) + return res def _get_default_substate_id(self, state_val=False): """ Gives default substate_id """ @@ -78,7 +114,6 @@ def _update_before_write_create(self, values): values["substate_id"] = self._get_default_substate_id(state_val) return values - @api.multi def write(self, values): values = self._update_before_write_create(values) res = super().write(values) diff --git a/base_substate/readme/CONTRIBUTORS.rst b/base_substate/readme/CONTRIBUTORS.rst index f55657f6b9..4bb895c6ca 100644 --- a/base_substate/readme/CONTRIBUTORS.rst +++ b/base_substate/readme/CONTRIBUTORS.rst @@ -1 +1,2 @@ * Mourad EL HADJ MIMOUNE +* Kitti U. diff --git a/base_substate/readme/DESCRIPTION.rst b/base_substate/readme/DESCRIPTION.rst index 290681551d..3268944e41 100644 --- a/base_substate/readme/DESCRIPTION.rst +++ b/base_substate/readme/DESCRIPTION.rst @@ -9,4 +9,4 @@ example: * We can also send mail when the susbstate is reached. It is not useful for itself. You can see an example of implementation -in the 'sale_substate' module. (sale-workflow repository). +in the 'purchase_substate' module. (purchase-workflow repository). diff --git a/base_substate/readme/USAGE.rst b/base_substate/readme/USAGE.rst index 1b8849ea93..fca986c290 100644 --- a/base_substate/readme/USAGE.rst +++ b/base_substate/readme/USAGE.rst @@ -1 +1 @@ -#. You must install an application module depending this one (for example sale_substate) +#. You must install an application module depending this one (for example purchase_substate) diff --git a/base_substate/static/description/index.html b/base_substate/static/description/index.html index 6511a08abd..2b0c25f672 100644 --- a/base_substate/static/description/index.html +++ b/base_substate/static/description/index.html @@ -367,7 +367,7 @@

Base Sub State

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

Beta License: AGPL-3 OCA/server-ux Translate me on Weblate Try me on Runbot

+

Beta License: AGPL-3 OCA/server-ux Translate me on Weblate Try me on Runbot

This module provide abstract models to manage customizable substates to be applied on different models (sale order, purchase, …).

@@ -378,7 +378,7 @@

example:

  • We can also send mail when the susbstate is reached.
  • It is not useful for itself. You can see an example of implementation -in the ‘sale_substate’ module. (sale-workflow repository).

    +in the ‘purchase_substate’ module. (purchase-workflow repository).

    Table of contents

      @@ -395,7 +395,7 @@

      example:

      Usage

        -
      1. You must install an application module depending this one (for example sale_substate)
      2. +
      3. You must install an application module depending this one (for example purchase_substate)
      @@ -403,7 +403,7 @@

      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 smashing it by providing a detailed and welcomed -feedback.

      +feedback.

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

      @@ -418,6 +418,7 @@

      Authors

      Contributors

      @@ -427,7 +428,7 @@

      Maintainers

      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-ux project on GitHub.

      +

      This module is part of the OCA/server-ux project on GitHub.

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

    diff --git a/base_substate/tests/sale_test.py b/base_substate/tests/sale_test.py index 4e59e2c36c..c3d80cc07d 100644 --- a/base_substate/tests/sale_test.py +++ b/base_substate/tests/sale_test.py @@ -28,19 +28,17 @@ class SaleTest(models.Model, TestMixin): amount_total = fields.Float(compute="_compute_amount_total", store=True) @api.depends("line_ids") - def _compute_amount_total(cls): - for record in cls: + def _compute_amount_total(self): + for record in self: for line in record.line_ids: record.amount_total += line.amount * line.qty - @api.multi - def button_confirm(cls): - cls.write({"state": "sale"}) + def button_confirm(self): + self.write({"state": "sale"}) return True - @api.multi - def button_cancel(cls): - cls.write({"state": "cancel"}) + def button_cancel(self): + self.write({"state": "cancel"}) class LineTest(models.Model, TestMixin): diff --git a/base_substate/tests/test_base_substate.py b/base_substate/tests/test_base_substate.py index b12ea2382d..25f855b6f8 100644 --- a/base_substate/tests/test_base_substate.py +++ b/base_substate/tests/test_base_substate.py @@ -101,7 +101,7 @@ def test_sale_order_substate(self): "name": "Test base substate to basic sale", "partner_id": partner.id, "line_ids": [ - (0, 0, {"name": "line test", "amount": 120.0, "qty": 1.5,}) + (0, 0, {"name": "line test", "amount": 120.0, "qty": 1.5}) ], } ) diff --git a/base_substate/views/base_substate_type_views.xml b/base_substate/views/base_substate_type_views.xml index 8abc769d45..eb7d3f4b47 100644 --- a/base_substate/views/base_substate_type_views.xml +++ b/base_substate/views/base_substate_type_views.xml @@ -47,7 +47,6 @@ Sub State Type ir.actions.act_window base.substate.type - form tree,form [] diff --git a/base_substate/views/base_substate_value_views.xml b/base_substate/views/base_substate_value_views.xml index 9e8b3b8df7..482f41aa73 100644 --- a/base_substate/views/base_substate_value_views.xml +++ b/base_substate/views/base_substate_value_views.xml @@ -51,7 +51,6 @@ Target State Value ir.actions.act_window target.state.value - form tree,form [] diff --git a/base_substate/views/base_substate_views.xml b/base_substate/views/base_substate_views.xml index e404f8f3c9..80805a3180 100644 --- a/base_substate/views/base_substate_views.xml +++ b/base_substate/views/base_substate_views.xml @@ -63,7 +63,6 @@ Base Substate ir.actions.act_window base.substate - form tree,form [] From 054173e872629ab70998a8ea74e95af83b95391c Mon Sep 17 00:00:00 2001 From: newtratip Date: Sat, 8 May 2021 14:54:11 +0700 Subject: [PATCH 04/17] [13.0][FIX] base_substate: Fix archived button --- base_substate/__manifest__.py | 2 +- base_substate/i18n/base_substate.pot | 5 +++++ base_substate/views/base_substate_views.xml | 18 ++++++++---------- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/base_substate/__manifest__.py b/base_substate/__manifest__.py index dd8219d50b..dca677fa24 100644 --- a/base_substate/__manifest__.py +++ b/base_substate/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Base Sub State", - "version": "13.0.1.0.0", + "version": "13.0.1.1.0", "category": "Tools", "author": "Akretion, " "Odoo Community Association (OCA)", "website": "https://github.com/OCA/sale-workflow/", diff --git a/base_substate/i18n/base_substate.pot b/base_substate/i18n/base_substate.pot index 7b28bc5cca..24b2b64fe4 100644 --- a/base_substate/i18n/base_substate.pot +++ b/base_substate/i18n/base_substate.pot @@ -25,6 +25,11 @@ msgstr "" msgid "Apply on" msgstr "" +#. module: base_substate +#: model_terms:ir.ui.view,arch_db:base_substate.base_substate_view_form +msgid "Archived" +msgstr "" + #. module: base_substate #: model:ir.actions.act_window,name:base_substate.act_open_base_substate_view #: model:ir.model,name:base_substate.model_base_substate diff --git a/base_substate/views/base_substate_views.xml b/base_substate/views/base_substate_views.xml index 80805a3180..3f7625249a 100644 --- a/base_substate/views/base_substate_views.xml +++ b/base_substate/views/base_substate_views.xml @@ -19,16 +19,14 @@
    -
    - -
    +
    + +