diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000000..0bd92bd791 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,14 @@ + +[report] +include = + ${TRAVIS_BUILD_DIR}/* + +omit = + */scenario/* + */scenarios/* + */test/* + */tests/* + *_example/* + *__init__.py + *__openerp__.py + *__manifest__.py diff --git a/base_substate/README.rst b/base_substate/README.rst new file mode 100644 index 0000000000..3178ce4ad1 --- /dev/null +++ b/base_substate/README.rst @@ -0,0 +1,95 @@ +============== +Base Sub State +============== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:84d1194b4cb2ddfc3fd9bbade580d42a584642ea0243a07295e92d53ca67d781 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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/18.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-18-0/server-ux-18-0-base_substate + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/server-ux&target_branch=18.0 + :alt: Try me on Runboat + +|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 substate is reached. + +It is not useful by itself. You can see an example of implementation in +the 'purchase_substate' module. (purchase-workflow repository). + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +1. You must install an application module depending this one (for + example purchase_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 to smash 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 +- Kitti U. +- Alexei Rivera (migration to 15.0) + +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..1021e633f8 --- /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": "18.0.1.0.0", + "category": "Tools", + "author": "Akretion, " "Odoo Community Association (OCA)", + "website": "https://github.com/OCA/server-ux", + "license": "AGPL-3", + "depends": ["base", "mail"], + "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..7618c7fa83 --- /dev/null +++ b/base_substate/i18n/base_substate.pot @@ -0,0 +1,221 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * base_substate +# +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: 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_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 +#: 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_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_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__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 +#. odoo-python +#: code:addons/base_substate/models/base_substate_mixin.py:0 +#, python-format +msgid "" +"The substate %(name)s is not defined for the state %(state)s but for " +"%(target_state)s " +msgstr "" + +#. module: base_substate +#. odoo-python +#: 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/i18n/es.po b/base_substate/i18n/es.po new file mode 100644 index 0000000000..8ed151561a --- /dev/null +++ b/base_substate/i18n/es.po @@ -0,0 +1,237 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * base_substate +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2023-11-11 15:17+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: base_substate +#: model:ir.model.fields,field_description:base_substate.field_base_substate__active +msgid "Active" +msgstr "Activo" + +#. 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 "Aplicar sobre" + +#. module: base_substate +#: model_terms:ir.ui.view,arch_db:base_substate.base_substate_view_form +msgid "Archived" +msgstr "Archivado" + +#. 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 "Subestado Base" + +#. module: base_substate +#: model:ir.model,name:base_substate.model_base_substate_type +msgid "Base Substate Type" +msgstr "Tipo de Subestado Base" + +#. module: base_substate +#: model:ir.model,name:base_substate.model_base_substate_mixin +msgid "BaseSubstate Mixin" +msgstr "Mezcla de subestado base" + +#. 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 "Creado por" + +#. 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 "Creado el" + +#. module: base_substate +#: model:ir.model.fields,field_description:base_substate.field_base_substate__description +msgid "Description" +msgstr "Descripción" + +#. 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_type__display_name +#: model:ir.model.fields,field_description:base_substate.field_target_state_value__display_name +msgid "Display Name" +msgstr "Mostrar Nombre" + +#. module: base_substate +#: model:ir.model.fields,field_description:base_substate.field_base_substate__mail_template_id +msgid "Email Template" +msgstr "Plantilla de Correo Electrónico" + +#. 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 "Indica el orden de secuencia al aplicar el subestado por defecto" + +#. 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_type__id +#: model:ir.model.fields,field_description:base_substate.field_target_state_value__id +msgid "ID" +msgstr "ID (identificación)" + +#. 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 "" +"Si se establece, se enviará un correo electrónico al socio cuando el objeto " +"alcance este subestado." + +#. 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 "Actualizado por Última vez por" + +#. 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 "Última Actualización el" + +#. 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 "Modelo para uso técnico" + +#. module: base_substate +#: model:ir.model.fields,field_description:base_substate.field_base_substate_type__name +msgid "Name" +msgstr "Nombre" + +#. module: base_substate +#: model:ir.model.fields,field_description:base_substate.field_base_substate__sequence +msgid "Sequence" +msgstr "Secuencia" + +#. module: base_substate +#: model:ir.model.fields,field_description:base_substate.field_base_substate_mixin__substate_id +msgid "Sub State" +msgstr "Sub Estado" + +#. module: base_substate +#: model:ir.ui.menu,name:base_substate.menu_substate_config +msgid "Sub State Configuration" +msgstr "Configuración del Sub Estado" + +#. 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 "Tipo de Sub Estado" + +#. module: base_substate +#: model:ir.model.fields,field_description:base_substate.field_base_substate__name +msgid "Substate Name" +msgstr "Nombre del Subestado" + +#. module: base_substate +#: model:ir.model.fields,field_description:base_substate.field_target_state_value__base_substate_type_id +msgid "Substate Type" +msgstr "Tipo de Subestado" + +#. module: base_substate +#: model:res.groups,name:base_substate.group_substate_manager +msgid "Substate manager" +msgstr "Gerente del Subestado" + +#. module: base_substate +#: model:ir.model.fields,field_description:base_substate.field_base_substate_type__target_state_field +msgid "Target State Field" +msgstr "Objetivo Campo del Estado" + +#. 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 "Valor Objetivo del Estado" + +#. module: base_substate +#: model:ir.model.fields,field_description:base_substate.field_target_state_value__name +msgid "Target state Name" +msgstr "Nombre del Estado objetivo" + +#. 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 "" +"Nombre traducible del estado de destino.\n" +"Ej: para orden de venta \"Quotation\", \"Sale order\", \"Locked\"..." + +#. 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 "" +"Nombre de campo de estado de destino técnico. Ej para pedido de venta " +"\"state \" para otro \"status \" ... " + +#. 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 "" +"Valor de estado del objetivo técnico.\n" +"Ej: para pedido de venta \"draft \", \"sale \", \"done \", ..." + +#. module: base_substate +#. odoo-python +#: code:addons/base_substate/models/base_substate_mixin.py:0 +#, python-format +msgid "" +"The substate %(name)s is not defined for the state %(state)s but for " +"%(target_state)s " +msgstr "" +"El subestado %(name)s no está definido para el estado %(state)s sino para " +"%(target_state)s " + +#. module: base_substate +#. odoo-python +#: 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 "Este subestado no está definido para este objeto sino para %s" + +#~ msgid "Last Modified on" +#~ msgstr "Última Modificación el" diff --git a/base_substate/i18n/it.po b/base_substate/i18n/it.po new file mode 100644 index 0000000000..69daada9e3 --- /dev/null +++ b/base_substate/i18n/it.po @@ -0,0 +1,238 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * base_substate +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2024-01-03 09:36+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 4.17\n" + +#. module: base_substate +#: model:ir.model.fields,field_description:base_substate.field_base_substate__active +msgid "Active" +msgstr "Attivo" + +#. 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 "Applica a" + +#. module: base_substate +#: model_terms:ir.ui.view,arch_db:base_substate.base_substate_view_form +msgid "Archived" +msgstr "In archivio" + +#. 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 "Substato base" + +#. module: base_substate +#: model:ir.model,name:base_substate.model_base_substate_type +msgid "Base Substate Type" +msgstr "Tipo substato base" + +#. module: base_substate +#: model:ir.model,name:base_substate.model_base_substate_mixin +msgid "BaseSubstate Mixin" +msgstr "Mixin substato base" + +#. 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 "Creato da" + +#. 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 "Creato il" + +#. module: base_substate +#: model:ir.model.fields,field_description:base_substate.field_base_substate__description +msgid "Description" +msgstr "Descrizione" + +#. 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_type__display_name +#: model:ir.model.fields,field_description:base_substate.field_target_state_value__display_name +msgid "Display Name" +msgstr "Nome visualizzato" + +#. module: base_substate +#: model:ir.model.fields,field_description:base_substate.field_base_substate__mail_template_id +msgid "Email Template" +msgstr "Modello e-mail" + +#. 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 "Fornisce la sequenza quando si applica il substato predefinito" + +#. 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_type__id +#: model:ir.model.fields,field_description:base_substate.field_target_state_value__id +msgid "ID" +msgstr "ID" + +#. 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 "" +"Se impostato, verrà mandata una e-mail al partner quando l'oggetto raggiunge " +"questo substato." + +#. 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 "Ultimo aggiornamento di" + +#. 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 "Ultimo aggiornamento il" + +#. 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 "Modello per uso tecnico" + +#. module: base_substate +#: model:ir.model.fields,field_description:base_substate.field_base_substate_type__name +msgid "Name" +msgstr "Nome" + +#. module: base_substate +#: model:ir.model.fields,field_description:base_substate.field_base_substate__sequence +msgid "Sequence" +msgstr "Sequenza" + +#. module: base_substate +#: model:ir.model.fields,field_description:base_substate.field_base_substate_mixin__substate_id +msgid "Sub State" +msgstr "Substato" + +#. module: base_substate +#: model:ir.ui.menu,name:base_substate.menu_substate_config +msgid "Sub State Configuration" +msgstr "Configurazione substato" + +#. 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 "Tipo substato" + +#. module: base_substate +#: model:ir.model.fields,field_description:base_substate.field_base_substate__name +msgid "Substate Name" +msgstr "Nome substato" + +#. module: base_substate +#: model:ir.model.fields,field_description:base_substate.field_target_state_value__base_substate_type_id +msgid "Substate Type" +msgstr "Tipo substato" + +#. module: base_substate +#: model:res.groups,name:base_substate.group_substate_manager +msgid "Substate manager" +msgstr "Responsabile substato" + +#. module: base_substate +#: model:ir.model.fields,field_description:base_substate.field_base_substate_type__target_state_field +msgid "Target State Field" +msgstr "Campo stato obiettivo" + +#. 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 "Valore stato obiettivo" + +#. module: base_substate +#: model:ir.model.fields,field_description:base_substate.field_target_state_value__name +msgid "Target state Name" +msgstr "Nome stato obiettivo" + +#. 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 "" +"Nome traducibile dello stato obiettivo.\n" +"Es: per ordine di vendita \"Preventivo\", \"Ordine di vendita\", " +"\"Bloccato\"..." + +#. 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 "" +"Nome tecnico del campo obiettivo dello stato. Es: per ordine di vendita " +"\"state\", per altri \"status\" ... " + +#. 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 "" +"Valore tecnico dello stato obiettivo.\n" +"Es: per ordine di vendita \"draft\", \"sale\", \"done\", ..." + +#. module: base_substate +#. odoo-python +#: code:addons/base_substate/models/base_substate_mixin.py:0 +#, python-format +msgid "" +"The substate %(name)s is not defined for the state %(state)s but for " +"%(target_state)s " +msgstr "" +"il substato %(name)s non è definito per lo sato %(state)s ma per " +"%(target_state)s " + +#. module: base_substate +#. odoo-python +#: 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 "Questo sottostato non è definito per questo oggetto ma per %s" + +#~ msgid "Last Modified on" +#~ msgstr "Ultima modifica il" 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..7255a6d9bd --- /dev/null +++ b/base_substate/models/base_substate.py @@ -0,0 +1,107 @@ +# 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 defines 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 example in sale_substate 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=[("base.substate.test.sale", "Sale Order")], + 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..6511bbc517 --- /dev/null +++ b/base_substate/models/base_substate_mixin.py @@ -0,0 +1,131 @@ +# 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" + _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 %(name)s is not defined for the state" + " %(state)s but for %(target_state)s " + ) + % { + "name": rec.substate_id.name, + "state": _(rec_states[rec.state]), + "target_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""" + 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(), + index=True, + domain=lambda self: [("model", "=", self._name)], + copy=False, + # tracking=True, + ) + + @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 + + def write(self, values): + values = self._update_before_write_create(values) + res = super().write(values) + return res + + @api.model_create_multi + def create(self, vals_list): + for vals in vals_list: + vals = self._update_before_write_create(vals) + res = super().create(vals_list) + return res diff --git a/base_substate/pyproject.toml b/base_substate/pyproject.toml new file mode 100644 index 0000000000..4231d0cccb --- /dev/null +++ b/base_substate/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/base_substate/readme/CONTRIBUTORS.md b/base_substate/readme/CONTRIBUTORS.md new file mode 100644 index 0000000000..9b8a4effd0 --- /dev/null +++ b/base_substate/readme/CONTRIBUTORS.md @@ -0,0 +1,3 @@ +- Mourad EL HADJ MIMOUNE \<\> +- Kitti U. \<\> +- Alexei Rivera \<\> (migration to 15.0) diff --git a/base_substate/readme/DESCRIPTION.md b/base_substate/readme/DESCRIPTION.md new file mode 100644 index 0000000000..d00de1535b --- /dev/null +++ b/base_substate/readme/DESCRIPTION.md @@ -0,0 +1,11 @@ +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 substate is reached. + +It is not useful by itself. You can see an example of implementation in +the 'purchase_substate' module. (purchase-workflow repository). diff --git a/base_substate/readme/USAGE.md b/base_substate/readme/USAGE.md new file mode 100644 index 0000000000..bc9abb0944 --- /dev/null +++ b/base_substate/readme/USAGE.md @@ -0,0 +1,2 @@ +1. You must install an application module depending this one (for + example purchase_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..4f98ee90ea --- /dev/null +++ b/base_substate/security/base_substate_security.xml @@ -0,0 +1,7 @@ + + + + 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 0000000000..3a0328b516 Binary files /dev/null and b/base_substate/static/description/icon.png differ diff --git a/base_substate/static/description/index.html b/base_substate/static/description/index.html new file mode 100644 index 0000000000..b63b3bf8da --- /dev/null +++ b/base_substate/static/description/index.html @@ -0,0 +1,439 @@ + + + + + +Base Sub State + + + +
+

Base Sub State

+ + +

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

+

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 substate is reached.
  • +
+

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

+

Table of contents

+ +
+

Usage

+
    +
  1. You must install an application module depending this one (for +example purchase_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 to smash it by providing a detailed and welcomed +feedback.

+

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

+
+ +
+
+

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..bd03da7c21 --- /dev/null +++ b/base_substate/tests/__init__.py @@ -0,0 +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 new file mode 100644 index 0000000000..4c1199d9f0 --- /dev/null +++ b/base_substate/tests/models_mixin.py @@ -0,0 +1,125 @@ +# 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: + """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(f"SELECT id FROM ir_model WHERE model = {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": f"Fake ACL for {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..c3d80cc07d --- /dev/null +++ b/base_substate/tests/sale_test.py @@ -0,0 +1,55 @@ +# 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(self): + for record in self: + for line in record.line_ids: + record.amount_total += line.amount * line.qty + + def button_confirm(self): + self.write({"state": "sale"}) + return True + + def button_cancel(self): + self.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..53a18a9b97 --- /dev/null +++ b/base_substate/tests/test_base_substate.py @@ -0,0 +1,127 @@ +# Copyright 2020 Akretion Mourad EL HADJ MIMOUNE +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo_test_helper import FakeModelLoader + +from odoo.tests.common import TransactionCase + +# from .sale_test import LineTest, SaleTest + + +class TestBaseSubstate(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.loader = FakeModelLoader(cls.env, cls.__module__) + cls.loader.backup_registry() + from .sale_test import LineTest, SaleTest + + cls.loader.update_registry( + ( + SaleTest, + LineTest, + ) + ) + + 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_state = cls.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_state.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_state.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): + cls.loader.restore_registry() + return 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..227a2c126e --- /dev/null +++ b/base_substate/views/base_substate_type_views.xml @@ -0,0 +1,87 @@ + + + + + base.substate.type.list + base.substate.type + + + + + + + + + + base.substate.type + base.substate.type.form + +
+ +
+
+ + + + + + +
+
+
+
+ + base.substate.type + base.substate.type.search + + + + + + + + + + Sub State Type + ir.actions.act_window + base.substate.type + list,form + + [] + {} + + + + +
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..27d6648c8a --- /dev/null +++ b/base_substate/views/base_substate_value_views.xml @@ -0,0 +1,83 @@ + + + + + target.state.value + + + + + + + + + + target.state.value + +
+ +
+
+ + + + + + +
+
+
+
+ + target.state.value + + + + + + + + + + Target State Value + ir.actions.act_window + target.state.value + list,form + + [] + {} + + + +
diff --git a/base_substate/views/base_substate_views.xml b/base_substate/views/base_substate_views.xml new file mode 100644 index 0000000000..d5b5fe7c1b --- /dev/null +++ b/base_substate/views/base_substate_views.xml @@ -0,0 +1,88 @@ + + + + + base.substate + + + + + + + + + + + + base.substate + +
+ +
+ +
+
+
+ + + + + + + + + + +
+
+
+
+ + base.substate + + + + + + + + Base Substate + ir.actions.act_window + base.substate + list,form + + [] + {} + + + +