diff --git a/sale_project_task_recurrency/README.rst b/sale_project_task_recurrency/README.rst new file mode 100644 index 0000000000..974d47b4ef --- /dev/null +++ b/sale_project_task_recurrency/README.rst @@ -0,0 +1,106 @@ +============================ +Sale project task recurrency +============================ + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:d5da2188db2ac87ba6c4ebb660e25aa1e53a534b122f01791f97e48ae666a311 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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%2Fproject-lightgray.png?logo=github + :target: https://github.com/OCA/project/tree/18.0/sale_project_task_recurrency + :alt: OCA/project +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/project-18-0/project-18-0-sale_project_task_recurrency + :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/project&target_branch=18.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allows configuring the recurrence of tasks created from +sales orders directly in the product form. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +- Go to *Project > Configuration > Settings*. +- In the *Tasks Management* section, enable the *Recurring Tasks* + checkbox. +- Go to *Sales > Products > Products*. +- Create a new product with the following options: + + - Product Type: Service + - Create on Order: Task or Project & Task + - Check the box for Is Recurring Task. + - Fill in the recurrence fields. + +Usage +===== + +- Go to *Sales > Orders > Quotations*. +- Create a new sales order and select the product created earlier. +- Confirm the sales order. +- Click the Task smart-button. +- Mark the task as done, and new tasks will be automatically created + according to the recurrence settings configured on the product. + +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 +------- + +* Tecnativa + +Contributors +------------ + +- `Tecnativa `__: + + - Pedro M. Baeza + - Carlos López + +- ``Heliconia Solutions Pvt. Ltd. ``\ \_ + +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/project `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/sale_project_task_recurrency/__init__.py b/sale_project_task_recurrency/__init__.py new file mode 100644 index 0000000000..0650744f6b --- /dev/null +++ b/sale_project_task_recurrency/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/sale_project_task_recurrency/__manifest__.py b/sale_project_task_recurrency/__manifest__.py new file mode 100644 index 0000000000..562a0c8c4d --- /dev/null +++ b/sale_project_task_recurrency/__manifest__.py @@ -0,0 +1,16 @@ +# Copyright 2024 Tecnativa - Carlos López +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +{ + "name": "Sale project task recurrency", + "version": "18.0.1.0.0", + "summary": "Configuring Task Recurrence from the Product Form.", + "author": "Tecnativa,Odoo Community Association (OCA)", + "website": "https://github.com/OCA/project", + "category": "Sales", + "depends": [ + "sale_project", + ], + "data": ["views/product_template_views.xml"], + "installable": True, + "license": "AGPL-3", +} diff --git a/sale_project_task_recurrency/i18n/es.po b/sale_project_task_recurrency/i18n/es.po new file mode 100644 index 0000000000..56281bea53 --- /dev/null +++ b/sale_project_task_recurrency/i18n/es.po @@ -0,0 +1,306 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_project_task_recurrency +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2024-12-16 17:49+0000\n" +"Last-Translator: Carlos Lopez \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 5.6.2\n" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_product__task_repeat_number +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_template__task_repeat_number +msgid "# Repeats" +msgstr "# Repeticiones" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month__4 +msgid "April" +msgstr "Abril" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month__8 +msgid "August" +msgstr "Agosto" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_start_date_method__current_date +msgid "Current date" +msgstr "Fecha actual" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_repeat_unit__day +msgid "Days" +msgstr "Días" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month__12 +msgid "December" +msgstr "Diciembre" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_product__task_repeat_until +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_template__task_repeat_until +msgid "End Date" +msgstr "Fecha final" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_start_date_method__end_this +msgid "End of current period" +msgstr "Fin del período actual" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_start_date_method__end_next +msgid "End of next period" +msgstr "Fin del próximo período" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month__2 +msgid "February" +msgstr "Febrero" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month_semester__5 +msgid "Fifth month" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month_quarter__1 +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month_semester__1 +msgid "First month" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_product__task_force_month +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_product__task_force_month_quarter +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_product__task_force_month_semester +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_template__task_force_month +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_template__task_force_month_quarter +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_template__task_force_month_semester +msgid "Force Month" +msgstr "Forzar mes" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields,help:sale_project_task_recurrency.field_product_product__task_force_month_quarter +#: model:ir.model.fields,help:sale_project_task_recurrency.field_product_template__task_force_month_quarter +msgid "Force the month to be used inside the quarter" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields,help:sale_project_task_recurrency.field_product_product__task_force_month_semester +#: model:ir.model.fields,help:sale_project_task_recurrency.field_product_template__task_force_month_semester +msgid "Force the month to be used inside the semester" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_repeat_type__forever +msgid "Forever" +msgstr "Para siempre" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month_semester__4 +msgid "Fourth month" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_product__recurring_task +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_template__recurring_task +msgid "Is recurring task?" +msgstr "¿Es tarea recurrente?" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month__1 +msgid "January" +msgstr "Enero" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month__7 +msgid "July" +msgstr "Julio" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month__6 +msgid "June" +msgstr "Junio" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month__3 +msgid "March" +msgstr "Marzo" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month__5 +msgid "May" +msgstr "Mayo" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_repeat_unit__month +msgid "Months" +msgstr "Meses" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month__11 +msgid "November" +msgstr "Noviembre" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month__10 +msgid "October" +msgstr "Octubre" + +#. module: sale_project_task_recurrency +#: model:ir.model,name:sale_project_task_recurrency.model_product_template +msgid "Product" +msgstr "Producto" + +#. module: sale_project_task_recurrency +#: model:ir.model,name:sale_project_task_recurrency.model_product_product +msgid "Product Variant" +msgstr "Variante" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_repeat_unit__quarter +msgid "Quarters" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_repeat_type__repeat +msgid "Repeat" +msgstr "Repetir" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_product__task_repeat_interval +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_template__task_repeat_interval +msgid "Repeat Every" +msgstr "Repetir cada" + +#. module: sale_project_task_recurrency +#: model:ir.model,name:sale_project_task_recurrency.model_sale_order_line +msgid "Sales Order Line" +msgstr "Línea de orden de venta" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month_quarter__2 +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month_semester__2 +msgid "Second month" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_repeat_unit__semester +msgid "Semesters" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month__9 +msgid "September" +msgstr "Septiembre" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month_semester__6 +msgid "Sixth month" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_product__task_start_date_method +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_template__task_start_date_method +msgid "Start Date Method" +msgstr "Método de fecha de inicio" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_start_date_method__start_this +msgid "Start of current period" +msgstr "Inicio del período actual" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_start_date_method__start_next +msgid "Start of next period" +msgstr "Inicio del próximo período" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_product__task_repeat_unit +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_template__task_repeat_unit +msgid "Task Repeat Unit" +msgstr "Repetir tarea hasta" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month_quarter__3 +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month_semester__3 +msgid "Third month" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields,help:sale_project_task_recurrency.field_product_product__task_start_date_method +#: model:ir.model.fields,help:sale_project_task_recurrency.field_product_template__task_start_date_method +msgid "" +"This field allows to define how the start date of the task will\n" +" be calculated:\n" +"\n" +" - Current date: The start date will be Current date.\n" +" - Start of current period: The start date will be the first day of " +"the actual\n" +" period selected. Example: If we are on 2024/08/27\n" +" and the period selected is 'Year(s)' the start date will be " +"2024/01/01.\n" +" - End of current period: The start date will be the last day of the " +"actual\n" +" period selected. Example: If we are on 2024/08/27\n" +" and the period selected is 'Year(s)' the start date will be " +"2024/12/31.\n" +" - Start of next period: The start date will be the first day of the " +"next\n" +" period selected. Example: If we are on 2024/08/27\n" +" and the period selected is 'Year(s)' the start date will be " +"2025/01/01.\n" +" - End of next period: The start date will be the last day of the " +"actual\n" +" period selected. Example: If we are on 2024/08/27\n" +" and the period selected is 'Year(s)' the start date will be " +"2025/12/31.\n" +" " +msgstr "" +"Este campo permite definir cómo se calculará la fecha de inicio de la " +"tarea:\n" +"- Fecha actual: La fecha de inicio será la fecha actual. \n" +"- Inicio del período actual: La fecha de inicio será el primer día del " +"período actual seleccionado. \n" +"\tEjemplo: Si estamos en 27/08/2024 y el período seleccionado es 'Año(s)', " +"la fecha de inicio será 01/01/2024. \n" +"- Fin del período actual: La fecha de inicio será el último día del período " +"actual seleccionado. \n" +"\tEjemplo: Si estamos en 27/08/2024 y el período seleccionado es 'Año(s)', " +"la fecha de inicio será 31/12/2024. \n" +"- Inicio del próximo período: La fecha de inicio será el primer día del " +"próximo período seleccionado. \n" +"\tEjemplo: Si estamos en 27/08/2024 y el período seleccionado es 'Año(s)', " +"la fecha de inicio será 01/01/2025. \n" +"- Fin del próximo período: La fecha de inicio será el último día del próximo " +"período seleccionado. \n" +"\tEjemplo: Si estamos en 27/08/2024 y el período seleccionado es 'Año(s)', " +"la fecha de inicio será 31/12/2025.\n" +" " + +#. module: sale_project_task_recurrency +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_product__task_repeat_type +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_template__task_repeat_type +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_repeat_type__until +msgid "Until" +msgstr "Hasta" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_repeat_unit__week +msgid "Weeks" +msgstr "Semanas" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_repeat_unit__year +msgid "Years" +msgstr "Años" diff --git a/sale_project_task_recurrency/i18n/it.po b/sale_project_task_recurrency/i18n/it.po new file mode 100644 index 0000000000..a725503303 --- /dev/null +++ b/sale_project_task_recurrency/i18n/it.po @@ -0,0 +1,311 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_project_task_recurrency +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2024-12-17 13:06+0000\n" +"Last-Translator: mymage \n" +"Language-Team: none\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 5.6.2\n" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_product__task_repeat_number +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_template__task_repeat_number +msgid "# Repeats" +msgstr "N° ripetizioni" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month__4 +msgid "April" +msgstr "Aprile" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month__8 +msgid "August" +msgstr "Agosto" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_start_date_method__current_date +msgid "Current date" +msgstr "Data attuale" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_repeat_unit__day +msgid "Days" +msgstr "Giorni" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month__12 +msgid "December" +msgstr "Dicembre" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_product__task_repeat_until +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_template__task_repeat_until +msgid "End Date" +msgstr "Data fine" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_start_date_method__end_this +msgid "End of current period" +msgstr "Fine periodo attuale" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_start_date_method__end_next +msgid "End of next period" +msgstr "Fine del periodo successivo" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month__2 +msgid "February" +msgstr "Febbraio" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month_semester__5 +msgid "Fifth month" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month_quarter__1 +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month_semester__1 +msgid "First month" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_product__task_force_month +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_product__task_force_month_quarter +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_product__task_force_month_semester +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_template__task_force_month +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_template__task_force_month_quarter +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_template__task_force_month_semester +msgid "Force Month" +msgstr "Forza mese" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields,help:sale_project_task_recurrency.field_product_product__task_force_month_quarter +#: model:ir.model.fields,help:sale_project_task_recurrency.field_product_template__task_force_month_quarter +msgid "Force the month to be used inside the quarter" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields,help:sale_project_task_recurrency.field_product_product__task_force_month_semester +#: model:ir.model.fields,help:sale_project_task_recurrency.field_product_template__task_force_month_semester +msgid "Force the month to be used inside the semester" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_repeat_type__forever +msgid "Forever" +msgstr "Per sempre" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month_semester__4 +msgid "Fourth month" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_product__recurring_task +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_template__recurring_task +msgid "Is recurring task?" +msgstr "È un lavoro ricorrente?" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month__1 +msgid "January" +msgstr "Gennaio" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month__7 +msgid "July" +msgstr "Luglio" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month__6 +msgid "June" +msgstr "Giugno" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month__3 +msgid "March" +msgstr "Marzo" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month__5 +msgid "May" +msgstr "Maggio" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_repeat_unit__month +msgid "Months" +msgstr "Mesi" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month__11 +msgid "November" +msgstr "Novembre" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month__10 +msgid "October" +msgstr "Ottobre" + +#. module: sale_project_task_recurrency +#: model:ir.model,name:sale_project_task_recurrency.model_product_template +msgid "Product" +msgstr "Prodotto" + +#. module: sale_project_task_recurrency +#: model:ir.model,name:sale_project_task_recurrency.model_product_product +msgid "Product Variant" +msgstr "Variante prodotto" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_repeat_unit__quarter +msgid "Quarters" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_repeat_type__repeat +msgid "Repeat" +msgstr "Ripeti" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_product__task_repeat_interval +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_template__task_repeat_interval +msgid "Repeat Every" +msgstr "Ripeti ogni" + +#. module: sale_project_task_recurrency +#: model:ir.model,name:sale_project_task_recurrency.model_sale_order_line +msgid "Sales Order Line" +msgstr "Riga ordine di vendita" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month_quarter__2 +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month_semester__2 +msgid "Second month" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_repeat_unit__semester +msgid "Semesters" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month__9 +msgid "September" +msgstr "Settembre" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month_semester__6 +msgid "Sixth month" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_product__task_start_date_method +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_template__task_start_date_method +msgid "Start Date Method" +msgstr "Metodo data inizio" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_start_date_method__start_this +msgid "Start of current period" +msgstr "Inizio periodo attuale" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_start_date_method__start_next +msgid "Start of next period" +msgstr "Inizio del periodo successivo" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_product__task_repeat_unit +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_template__task_repeat_unit +msgid "Task Repeat Unit" +msgstr "Unità ripetizione lavoro" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month_quarter__3 +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month_semester__3 +msgid "Third month" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields,help:sale_project_task_recurrency.field_product_product__task_start_date_method +#: model:ir.model.fields,help:sale_project_task_recurrency.field_product_template__task_start_date_method +msgid "" +"This field allows to define how the start date of the task will\n" +" be calculated:\n" +"\n" +" - Current date: The start date will be Current date.\n" +" - Start of current period: The start date will be the first day of " +"the actual\n" +" period selected. Example: If we are on 2024/08/27\n" +" and the period selected is 'Year(s)' the start date will be " +"2024/01/01.\n" +" - End of current period: The start date will be the last day of the " +"actual\n" +" period selected. Example: If we are on 2024/08/27\n" +" and the period selected is 'Year(s)' the start date will be " +"2024/12/31.\n" +" - Start of next period: The start date will be the first day of the " +"next\n" +" period selected. Example: If we are on 2024/08/27\n" +" and the period selected is 'Year(s)' the start date will be " +"2025/01/01.\n" +" - End of next period: The start date will be the last day of the " +"actual\n" +" period selected. Example: If we are on 2024/08/27\n" +" and the period selected is 'Year(s)' the start date will be " +"2025/12/31.\n" +" " +msgstr "" +"Questo campo consente di definire come verrà calcolata la data \n" +" di inizio dell'attività:\n" +"\n" +" - Data corrente: la data di inizio sarà la data corrente.\n" +" - Inizio del periodo corrente: la data di inizio sarà il primo " +"giorno del periodo effettivo\n" +" selezionato. Esempio: se siamo al 27/08/2024\n" +" e il periodo selezionato è \"Anno/i\", la data di inizio sarà il " +"01/01/2024.\n" +" - Fine del periodo corrente: la data di inizio sarà l'ultimo giorno " +"del periodo effettivo\n" +" selezionato. Esempio: se siamo al 27/08/2024\n" +" e il periodo selezionato è \"Anno/i\", la data di inizio sarà il " +"31/12/2024.\n" +" - Inizio del periodo successivo: la data di inizio sarà il primo " +"giorno del periodo successivo\n" +" selezionato. Esempio: se siamo al 27/08/2024\n" +" e il periodo selezionato è \"Anno/i\", la data di inizio sarà il " +"01/01/2025. \n" +" - Fine del periodo successivo: la data di inizio sarà l'ultimo " +"giorno del periodo effettivo\n" +" selezionato. Esempio: se siamo al 27/08/2024\n" +" e il periodo selezionato è \"Anno/i\", la data di inizio sarà il " +"31/12/2025.\n" +" " + +#. module: sale_project_task_recurrency +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_product__task_repeat_type +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_template__task_repeat_type +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_repeat_type__until +msgid "Until" +msgstr "Fino a" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_repeat_unit__week +msgid "Weeks" +msgstr "Settimane" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_repeat_unit__year +msgid "Years" +msgstr "Anni" diff --git a/sale_project_task_recurrency/i18n/sale_project_task_recurrency.pot b/sale_project_task_recurrency/i18n/sale_project_task_recurrency.pot new file mode 100644 index 0000000000..52456869e8 --- /dev/null +++ b/sale_project_task_recurrency/i18n/sale_project_task_recurrency.pot @@ -0,0 +1,275 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_project_task_recurrency +# +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: sale_project_task_recurrency +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_product__task_repeat_number +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_template__task_repeat_number +msgid "# Repeats" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month__4 +msgid "April" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month__8 +msgid "August" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_start_date_method__current_date +msgid "Current date" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_repeat_unit__day +msgid "Days" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month__12 +msgid "December" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_product__task_repeat_until +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_template__task_repeat_until +msgid "End Date" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_start_date_method__end_this +msgid "End of current period" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_start_date_method__end_next +msgid "End of next period" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month__2 +msgid "February" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month_semester__5 +msgid "Fifth month" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month_quarter__1 +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month_semester__1 +msgid "First month" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_product__task_force_month +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_product__task_force_month_quarter +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_product__task_force_month_semester +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_template__task_force_month +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_template__task_force_month_quarter +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_template__task_force_month_semester +msgid "Force Month" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields,help:sale_project_task_recurrency.field_product_product__task_force_month_quarter +#: model:ir.model.fields,help:sale_project_task_recurrency.field_product_template__task_force_month_quarter +msgid "Force the month to be used inside the quarter" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields,help:sale_project_task_recurrency.field_product_product__task_force_month_semester +#: model:ir.model.fields,help:sale_project_task_recurrency.field_product_template__task_force_month_semester +msgid "Force the month to be used inside the semester" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_repeat_type__forever +msgid "Forever" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month_semester__4 +msgid "Fourth month" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_product__recurring_task +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_template__recurring_task +msgid "Is recurring task?" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month__1 +msgid "January" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month__7 +msgid "July" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month__6 +msgid "June" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month__3 +msgid "March" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month__5 +msgid "May" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_repeat_unit__month +msgid "Months" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month__11 +msgid "November" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month__10 +msgid "October" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model,name:sale_project_task_recurrency.model_product_template +msgid "Product" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model,name:sale_project_task_recurrency.model_product_product +msgid "Product Variant" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_repeat_unit__quarter +msgid "Quarters" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_repeat_type__repeat +msgid "Repeat" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_product__task_repeat_interval +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_template__task_repeat_interval +msgid "Repeat Every" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model,name:sale_project_task_recurrency.model_sale_order_line +msgid "Sales Order Line" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month_quarter__2 +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month_semester__2 +msgid "Second month" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_repeat_unit__semester +msgid "Semesters" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month__9 +msgid "September" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month_semester__6 +msgid "Sixth month" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_product__task_start_date_method +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_template__task_start_date_method +msgid "Start Date Method" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_start_date_method__start_this +msgid "Start of current period" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_start_date_method__start_next +msgid "Start of next period" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_product__task_repeat_unit +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_template__task_repeat_unit +msgid "Task Repeat Unit" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month_quarter__3 +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month_semester__3 +msgid "Third month" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields,help:sale_project_task_recurrency.field_product_product__task_start_date_method +#: model:ir.model.fields,help:sale_project_task_recurrency.field_product_template__task_start_date_method +msgid "" +"This field allows to define how the start date of the task will\n" +" be calculated:\n" +"\n" +" - Current date: The start date will be Current date.\n" +" - Start of current period: The start date will be the first day of the actual\n" +" period selected. Example: If we are on 2024/08/27\n" +" and the period selected is 'Year(s)' the start date will be 2024/01/01.\n" +" - End of current period: The start date will be the last day of the actual\n" +" period selected. Example: If we are on 2024/08/27\n" +" and the period selected is 'Year(s)' the start date will be 2024/12/31.\n" +" - Start of next period: The start date will be the first day of the next\n" +" period selected. Example: If we are on 2024/08/27\n" +" and the period selected is 'Year(s)' the start date will be 2025/01/01.\n" +" - End of next period: The start date will be the last day of the actual\n" +" period selected. Example: If we are on 2024/08/27\n" +" and the period selected is 'Year(s)' the start date will be 2025/12/31.\n" +" " +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_product__task_repeat_type +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_template__task_repeat_type +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_repeat_type__until +msgid "Until" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_repeat_unit__week +msgid "Weeks" +msgstr "" + +#. module: sale_project_task_recurrency +#: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_repeat_unit__year +msgid "Years" +msgstr "" diff --git a/sale_project_task_recurrency/models/__init__.py b/sale_project_task_recurrency/models/__init__.py new file mode 100644 index 0000000000..83527162d0 --- /dev/null +++ b/sale_project_task_recurrency/models/__init__.py @@ -0,0 +1,2 @@ +from . import product_template +from . import sale_order_line diff --git a/sale_project_task_recurrency/models/product_template.py b/sale_project_task_recurrency/models/product_template.py new file mode 100644 index 0000000000..4cb103a743 --- /dev/null +++ b/sale_project_task_recurrency/models/product_template.py @@ -0,0 +1,116 @@ +# Copyright 2024 Tecnativa - Carlos López +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from odoo import api, fields, models + + +class ProductTemplate(models.Model): + _inherit = "product.template" + + recurring_task = fields.Boolean(string="Is recurring task?") + task_repeat_interval = fields.Integer(string="Repeat Every", default=1) + task_repeat_unit = fields.Selection( + [ + ("day", "Days"), + ("week", "Weeks"), + ("month", "Months"), + ("quarter", "Quarters"), + ("semester", "Semesters"), + ("year", "Years"), + ], + default="week", + ) + task_repeat_type = fields.Selection( + [ + ("forever", "Forever"), + ("repeat", "Repeat"), + ("until", "Until"), + ], + default="forever", + string="Until", + ) + task_repeat_number = fields.Integer(string="# Repeats", default=1) + task_repeat_until = fields.Date(string="End Date") + task_start_date_method = fields.Selection( + [ + ("current_date", "Current date"), + ("start_this", "Start of current period"), + ("end_this", "End of current period"), + ("start_next", "Start of next period"), + ("end_next", "End of next period"), + ], + "Start Date Method", + default="current_date", + help="""This field allows to define how the start date of the task will + be calculated: + + - Current date: The start date will be Current date. + - Start of current period: The start date will be the first day of the actual + period selected. Example: If we are on 2024/08/27 + and the period selected is 'Year(s)' the start date will be 2024/01/01. + - End of current period: The start date will be the last day of the actual + period selected. Example: If we are on 2024/08/27 + and the period selected is 'Year(s)' the start date will be 2024/12/31. + - Start of next period: The start date will be the first day of the next + period selected. Example: If we are on 2024/08/27 + and the period selected is 'Year(s)' the start date will be 2025/01/01. + - End of next period: The start date will be the last day of the actual + period selected. Example: If we are on 2024/08/27 + and the period selected is 'Year(s)' the start date will be 2025/12/31. + """, + ) + task_force_month = fields.Selection( + [ + ("1", "January"), + ("2", "February"), + ("3", "March"), + ("4", "April"), + ("5", "May"), + ("6", "June"), + ("7", "July"), + ("8", "August"), + ("9", "September"), + ("10", "October"), + ("11", "November"), + ("12", "December"), + ], + "Force Month", + ) + task_force_month_quarter = fields.Selection( + [ + ("1", "First month"), + ("2", "Second month"), + ("3", "Third month"), + ], + "Force Month Quarter", + help="Force the month to be used inside the quarter", + ) + task_force_month_semester = fields.Selection( + [ + ("1", "First month"), + ("2", "Second month"), + ("3", "Third month"), + ("4", "Fourth month"), + ("5", "Fifth month"), + ("6", "Sixth month"), + ], + "Force Month Semester", + help="Force the month to be used inside the semester", + ) + + @api.onchange("service_tracking") + def _onchange_service_tracking(self): + res = super()._onchange_service_tracking() + if self.service_tracking not in ["task_global_project", "task_in_project"]: + self.recurring_task = False + return res + + +class ProductProduct(models.Model): + _inherit = "product.product" + + @api.onchange("service_tracking") + def _onchange_service_tracking(self): + res = super()._onchange_service_tracking() + if self.service_tracking not in ["task_global_project", "task_in_project"]: + self.recurring_task = False + return res diff --git a/sale_project_task_recurrency/models/sale_order_line.py b/sale_project_task_recurrency/models/sale_order_line.py new file mode 100644 index 0000000000..5fa6fdeee0 --- /dev/null +++ b/sale_project_task_recurrency/models/sale_order_line.py @@ -0,0 +1,123 @@ +# Copyright 2024 Tecnativa - Carlos López +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from datetime import datetime + +import pytz +from dateutil.relativedelta import relativedelta + +from odoo import fields, models + +MONTH_NB_TASK_MAPPING = { + "month": 1, + "quarter": 3, + "semester": 6, + "year": 12, +} + + +class SaleOrderLine(models.Model): + _inherit = "sale.order.line" + + def _timesheet_create_task_prepare_values(self, project): + values = super()._timesheet_create_task_prepare_values(project) + if self.product_id.recurring_task: + repeat_type = ( + "until" + if self.product_id.task_repeat_type == "repeat" + else self.product_id.task_repeat_type + ) + repeat_unit = ( + "month" + if self.product_id.task_repeat_unit in ["quarter", "semester"] + else self.product_id.task_repeat_unit + ) + repeat_interval = self.product_id.task_repeat_interval + if self.product_id.task_repeat_unit == "quarter": + repeat_interval *= 3 + elif self.product_id.task_repeat_unit == "semester": + repeat_interval *= 6 + date_deadline = self._get_task_date_deadline() + values.update( + { + "repeat_interval": repeat_interval, + "repeat_unit": repeat_unit, + "repeat_type": repeat_type, + "recurring_task": True, + "date_deadline": date_deadline, + "repeat_until": self._get_task_repeat_until(date_deadline), + } + ) + return values + + def _get_task_date_deadline(self): + self.ensure_one() + product = self.product_id + date_now = fields.Datetime.context_timestamp(self, datetime.now()) + task_start_date_method = product.task_start_date_method + # Initial deadline based on current date and time + date_deadline = date_now.replace(hour=12, minute=0, second=0) + forced_month = int(product.task_force_month or 0) + if product.task_repeat_unit in ["quarter", "semester"]: + forced_month = int( + product[f"task_force_month_{product.task_repeat_unit}"] or 0 + ) + month_period = month = date_deadline.month + month_nb = MONTH_NB_TASK_MAPPING.get(product.task_repeat_unit) or 0 + if product.task_repeat_unit in ["quarter", "semester", "year"]: + month_nb = MONTH_NB_TASK_MAPPING[product.task_repeat_unit] + # The period number is started by 0 to be able to calculate the month + period_number = (month - 1) // month_nb + if product.task_repeat_unit == "year": + month_period = 1 + elif product.task_repeat_unit != "month": + # Checking quarterly and semesterly + month_period = period_number * month_nb + 1 + if product.task_repeat_unit != "month" and forced_month: + # When the selected period is year, the period_number field is + # 0, so forced_month will take the value of the forced month set + # on product. + forced_month = month_nb * period_number + forced_month + if ( + forced_month + and product.task_repeat_unit in ["quarter", "semester", "year"] + and task_start_date_method == "current_date" + ): + date_deadline += relativedelta(month=forced_month) + if ( + product.task_repeat_unit in ["month", "quarter", "semester", "year"] + and task_start_date_method != "current_date" + ): + is_end = "end_" in task_start_date_method + # If forced_month is set, use it, but if it isn't use the month_period + date_deadline += relativedelta(day=1, month=forced_month or month_period) + if is_end: + increment = month_nb - 1 if not forced_month else 0 + date_deadline += relativedelta(months=increment, day=31) + if "_next" in task_start_date_method: + date_deadline += relativedelta( + months=month_nb * product.task_repeat_interval + ) + if is_end: + date_deadline += relativedelta(day=31) + return date_deadline.astimezone(pytz.UTC).replace(tzinfo=None) + + def _get_task_repeat_until(self, date_start): + self.ensure_one() + product = self.product_id + repeat_until = False + if product.task_repeat_type == "repeat": + if product.task_repeat_unit == "month": + repeat_until = date_start + relativedelta( + months=product.task_repeat_number * product.task_repeat_interval + ) + else: + force_month = ( + int(product.task_force_month) if product.task_force_month else 0 + ) + repeat_until = date_start + relativedelta( + years=product.task_repeat_number * product.task_repeat_interval, + month=force_month or None, + ) + elif product.task_repeat_type == "until": + repeat_until = product.task_repeat_until + return repeat_until diff --git a/sale_project_task_recurrency/pyproject.toml b/sale_project_task_recurrency/pyproject.toml new file mode 100644 index 0000000000..4231d0cccb --- /dev/null +++ b/sale_project_task_recurrency/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/sale_project_task_recurrency/readme/CONFIGURE.md b/sale_project_task_recurrency/readme/CONFIGURE.md new file mode 100644 index 0000000000..5c0869e7e5 --- /dev/null +++ b/sale_project_task_recurrency/readme/CONFIGURE.md @@ -0,0 +1,8 @@ +- Go to *Project > Configuration > Settings*. +- In the *Tasks Management* section, enable the *Recurring Tasks* checkbox. +- Go to *Sales > Products > Products*. +- Create a new product with the following options: + - Product Type: Service + - Create on Order: Task or Project & Task + - Check the box for Is Recurring Task. + - Fill in the recurrence fields. \ No newline at end of file diff --git a/sale_project_task_recurrency/readme/CONTRIBUTORS.md b/sale_project_task_recurrency/readme/CONTRIBUTORS.md new file mode 100644 index 0000000000..01b2b70185 --- /dev/null +++ b/sale_project_task_recurrency/readme/CONTRIBUTORS.md @@ -0,0 +1,4 @@ +- [Tecnativa](https://www.tecnativa.com): + - Pedro M. Baeza + - Carlos López +- `Heliconia Solutions Pvt. Ltd. `_ diff --git a/sale_project_task_recurrency/readme/DESCRIPTION.md b/sale_project_task_recurrency/readme/DESCRIPTION.md new file mode 100644 index 0000000000..a9f043084a --- /dev/null +++ b/sale_project_task_recurrency/readme/DESCRIPTION.md @@ -0,0 +1,2 @@ + +This module allows configuring the recurrence of tasks created from sales orders directly in the product form. \ No newline at end of file diff --git a/sale_project_task_recurrency/readme/USAGE.md b/sale_project_task_recurrency/readme/USAGE.md new file mode 100644 index 0000000000..c598b918ce --- /dev/null +++ b/sale_project_task_recurrency/readme/USAGE.md @@ -0,0 +1,5 @@ +- Go to *Sales \> Orders \> Quotations*. +- Create a new sales order and select the product created earlier. +- Confirm the sales order. +- Click the Task smart-button. +- Mark the task as done, and new tasks will be automatically created according to the recurrence settings configured on the product. \ No newline at end of file diff --git a/sale_project_task_recurrency/static/description/icon.png b/sale_project_task_recurrency/static/description/icon.png new file mode 100644 index 0000000000..3a0328b516 Binary files /dev/null and b/sale_project_task_recurrency/static/description/icon.png differ diff --git a/sale_project_task_recurrency/static/description/index.html b/sale_project_task_recurrency/static/description/index.html new file mode 100644 index 0000000000..9a5a5b5880 --- /dev/null +++ b/sale_project_task_recurrency/static/description/index.html @@ -0,0 +1,458 @@ + + + + + +Sale project task recurrency + + + +
+

Sale project task recurrency

+ + +

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

+

This module allows configuring the recurrence of tasks created from +sales orders directly in the product form.

+

Table of contents

+ +
+

Configuration

+
    +
  • Go to Project > Configuration > Settings.
  • +
  • In the Tasks Management section, enable the Recurring Tasks +checkbox.
  • +
  • Go to Sales > Products > Products.
  • +
  • Create a new product with the following options:
      +
    • Product Type: Service
    • +
    • Create on Order: Task or Project & Task
    • +
    • Check the box for Is Recurring Task.
    • +
    • Fill in the recurrence fields.
    • +
    +
  • +
+
+
+

Usage

+
    +
  • Go to Sales > Orders > Quotations.
  • +
  • Create a new sales order and select the product created earlier.
  • +
  • Confirm the sales order.
  • +
  • Click the Task smart-button.
  • +
  • Mark the task as done, and new tasks will be automatically created +according to the recurrence settings configured on the product.
  • +
+
+
+

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

+
    +
  • Tecnativa
  • +
+
+
+

Contributors

+
    +
  • Tecnativa:
      +
    • Pedro M. Baeza
    • +
    • Carlos López
    • +
    +
  • +
  • Heliconia Solutions Pvt. Ltd. <https://www.heliconia.io>_
  • +
+
+
+

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

+

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

+
+
+
+ + diff --git a/sale_project_task_recurrency/tests/__init__.py b/sale_project_task_recurrency/tests/__init__.py new file mode 100644 index 0000000000..4da82abb3e --- /dev/null +++ b/sale_project_task_recurrency/tests/__init__.py @@ -0,0 +1 @@ +from . import test_product_task_recurrency diff --git a/sale_project_task_recurrency/tests/test_product_task_recurrency.py b/sale_project_task_recurrency/tests/test_product_task_recurrency.py new file mode 100644 index 0000000000..a7246212d8 --- /dev/null +++ b/sale_project_task_recurrency/tests/test_product_task_recurrency.py @@ -0,0 +1,1286 @@ +# Copyright 2024 Tecnativa - Carlos López +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + + +from freezegun import freeze_time + +from odoo import fields +from odoo.tests import new_test_user +from odoo.tests.common import users +from odoo.tools import mute_logger + +from odoo.addons.base.tests.common import BaseCommon + + +class TestProductTaskRecurrency(BaseCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.Task = cls.env["project.task"] + cls.Product = cls.env["product.product"] + cls.SaleOrder = cls.env["sale.order"] + cls.SaleLine = cls.env["sale.order.line"] + cls.env.user.tz = "UTC" + cls.user = new_test_user( + cls.env, + login="test-user", + groups="sales_team.group_sale_salesman,project.group_project_recurring_tasks", + tz=cls.env.user.tz, + ) + cls.uom_hour = cls.env.ref("uom.product_uom_hour") + cls.project_global = cls.env["project.project"].create( + {"name": "Global Project", "allow_billable": True} + ) + product_vals = { + "name": "Service", + "type": "service", + "uom_id": cls.uom_hour.id, + "uom_po_id": cls.uom_hour.id, + "service_type": "manual", + "service_tracking": "no", + } + cls.service_no_task = cls.Product.create(product_vals) + cls.service_task = cls.Product.create( + dict( + product_vals, + name="Service task", + service_tracking="task_global_project", + project_id=cls.project_global.id, + ) + ) + cls.service_task_recurrency = cls.Product.create( + dict( + product_vals, + name="Service task recurrency", + service_tracking="task_global_project", + project_id=cls.project_global.id, + recurring_task=True, + task_repeat_interval=1, + task_repeat_unit="day", + ) + ) + cls.sale_order = cls.SaleOrder.create( + { + "partner_id": cls.partner.id, + "partner_invoice_id": cls.partner.id, + "partner_shipping_id": cls.partner.id, + } + ) + cls.sol_no_task = cls.SaleLine.create( + { + "product_id": cls.service_no_task.id, + "product_uom_qty": 10, + "order_id": cls.sale_order.id, + } + ) + cls.sol_task = cls.SaleLine.create( + { + "product_id": cls.service_task.id, + "product_uom_qty": 10, + "order_id": cls.sale_order.id, + } + ) + cls.sol_task_recurrency = cls.SaleLine.create( + { + "product_id": cls.service_task_recurrency.id, + "product_uom_qty": 10, + "order_id": cls.sale_order.id, + } + ) + + @mute_logger("odoo.models.unlink") + def _reprocess_sale_order(self): + self.sale_order.tasks_ids.unlink() + self.sale_order._action_cancel() + self.sale_order.action_draft() + self.sale_order.action_confirm() + + def _get_last_task(self, task): + last_task_id = task.recurrence_id._get_last_task_id_per_recurrence_id()[ + task.recurrence_id.id + ] + return self.Task.browse(last_task_id) + + @users("test-user") + @freeze_time("2024-11-15") + def test_task_recurrency_day(self): + """Every 1 day forever""" + self.service_task_recurrency.task_repeat_unit = "day" + self.sale_order.action_confirm() + self.assertFalse(self.sol_no_task.task_id) + self.assertFalse(self.sol_task.task_id.recurring_task) + task = self.sol_task_recurrency.task_id + self.assertTrue(task.recurring_task) + self.assertEqual(task.repeat_interval, 1) + self.assertEqual(task.repeat_unit, "day") + self.assertEqual(task.repeat_type, "forever") + self.assertFalse(task.repeat_until) + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2024-11-15") + ) + self.assertEqual(task.recurring_count, 1) + task.state = "1_done" + task.invalidate_recordset(["recurring_count"]) + self.assertEqual(task.recurring_count, 2) + last_task = self._get_last_task(task) + self.assertEqual( + last_task.date_deadline.date(), fields.Date.from_string("2024-11-16") + ) + + @users("test-user") + @freeze_time("2024-11-15") + def test_task_recurrency_week(self): + """Every 1 week forever""" + self.service_task_recurrency.task_repeat_unit = "week" + self.sale_order.action_confirm() + self.assertFalse(self.sol_no_task.task_id) + self.assertFalse(self.sol_task.task_id.recurring_task) + task = self.sol_task_recurrency.task_id + self.assertTrue(task.recurring_task) + self.assertEqual(task.repeat_interval, 1) + self.assertEqual(task.repeat_unit, "week") + self.assertEqual(task.repeat_type, "forever") + self.assertFalse(task.repeat_until) + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2024-11-15") + ) + self.assertEqual(task.recurring_count, 1) + task.state = "1_done" + task.invalidate_recordset(["recurring_count"]) + self.assertEqual(task.recurring_count, 2) + last_task = self._get_last_task(task) + self.assertEqual( + last_task.date_deadline.date(), fields.Date.from_string("2024-11-22") + ) + + @users("test-user") + @freeze_time("2024-11-15") + def test_task_recurrency_month(self): + """Every 1 month forever""" + self.service_task_recurrency.task_repeat_unit = "month" + self.sale_order.action_confirm() + self.assertFalse(self.sol_no_task.task_id) + self.assertFalse(self.sol_task.task_id.recurring_task) + task = self.sol_task_recurrency.task_id + self.assertTrue(task.recurring_task) + self.assertEqual(task.repeat_interval, 1) + self.assertEqual(task.repeat_unit, "month") + self.assertEqual(task.repeat_type, "forever") + self.assertFalse(task.repeat_until) + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2024-11-15") + ) + self.assertEqual(task.recurring_count, 1) + task.state = "1_done" + task.invalidate_recordset(["recurring_count"]) + self.assertEqual(task.recurring_count, 2) + last_task = self._get_last_task(task) + self.assertEqual( + last_task.date_deadline.date(), fields.Date.from_string("2024-12-15") + ) + # start_date_method = "start_this" + self.service_task_recurrency.task_start_date_method = "start_this" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2024-11-01") + ) + self.assertEqual(task.recurring_count, 1) + task.state = "1_done" + task.invalidate_recordset(["recurring_count"]) + self.assertEqual(task.recurring_count, 2) + last_task = self._get_last_task(task) + self.assertEqual( + last_task.date_deadline.date(), fields.Date.from_string("2024-12-01") + ) + # start_date_method = "end_this" + self.service_task_recurrency.task_start_date_method = "end_this" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2024-11-30") + ) + self.assertEqual(task.recurring_count, 1) + task.state = "1_done" + task.invalidate_recordset(["recurring_count"]) + self.assertEqual(task.recurring_count, 2) + last_task = self._get_last_task(task) + self.assertEqual( + last_task.date_deadline.date(), fields.Date.from_string("2024-12-30") + ) + # start_date_method = "start_next" + self.service_task_recurrency.task_start_date_method = "start_next" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2024-12-01") + ) + self.assertEqual(task.recurring_count, 1) + task.state = "1_done" + task.invalidate_recordset(["recurring_count"]) + self.assertEqual(task.recurring_count, 2) + last_task = self._get_last_task(task) + self.assertEqual( + last_task.date_deadline.date(), fields.Date.from_string("2025-01-01") + ) + # start_date_method = "end_next" + self.service_task_recurrency.task_start_date_method = "end_next" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2024-12-31") + ) + self.assertEqual(task.recurring_count, 1) + task.state = "1_done" + task.invalidate_recordset(["recurring_count"]) + self.assertEqual(task.recurring_count, 2) + last_task = self._get_last_task(task) + self.assertEqual( + last_task.date_deadline.date(), fields.Date.from_string("2025-01-31") + ) + + @users("test-user") + @freeze_time("2024-11-15") + def test_task_recurrency_3_month(self): + """Every 3 month forever""" + self.service_task_recurrency.task_repeat_unit = "month" + self.service_task_recurrency.task_repeat_interval = 3 + self.sale_order.action_confirm() + self.assertFalse(self.sol_no_task.task_id) + self.assertFalse(self.sol_task.task_id.recurring_task) + task = self.sol_task_recurrency.task_id + self.assertTrue(task.recurring_task) + self.assertEqual(task.repeat_interval, 3) + self.assertEqual(task.repeat_unit, "month") + self.assertEqual(task.repeat_type, "forever") + self.assertFalse(task.repeat_until) + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2024-11-15") + ) + self.assertEqual(task.recurring_count, 1) + task.state = "1_done" + task.invalidate_recordset(["recurring_count"]) + self.assertEqual(task.recurring_count, 2) + last_task = self._get_last_task(task) + self.assertEqual( + last_task.date_deadline.date(), fields.Date.from_string("2025-02-15") + ) + # start_date_method = "start_this" + self.service_task_recurrency.task_start_date_method = "start_this" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2024-11-01") + ) + self.assertEqual(task.recurring_count, 1) + task.state = "1_done" + task.invalidate_recordset(["recurring_count"]) + self.assertEqual(task.recurring_count, 2) + last_task = self._get_last_task(task) + self.assertEqual( + last_task.date_deadline.date(), fields.Date.from_string("2025-02-01") + ) + # start_date_method = "end_this" + self.service_task_recurrency.task_start_date_method = "end_this" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2024-11-30") + ) + self.assertEqual(task.recurring_count, 1) + task.state = "1_done" + task.invalidate_recordset(["recurring_count"]) + self.assertEqual(task.recurring_count, 2) + last_task = self._get_last_task(task) + self.assertEqual( + last_task.date_deadline.date(), fields.Date.from_string("2025-02-28") + ) + # start_date_method = "start_next" + self.service_task_recurrency.task_start_date_method = "start_next" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2025-02-01") + ) + self.assertEqual(task.recurring_count, 1) + task.state = "1_done" + task.invalidate_recordset(["recurring_count"]) + self.assertEqual(task.recurring_count, 2) + last_task = self._get_last_task(task) + self.assertEqual( + last_task.date_deadline.date(), fields.Date.from_string("2025-05-01") + ) + # start_date_method = "end_next" + self.service_task_recurrency.task_start_date_method = "end_next" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2025-02-28") + ) + self.assertEqual(task.recurring_count, 1) + task.state = "1_done" + task.invalidate_recordset(["recurring_count"]) + self.assertEqual(task.recurring_count, 2) + last_task = self._get_last_task(task) + # date is 28/05 instead 31/05 + # because Odoo in roject.task not force to end of month, only added month + self.assertEqual( + last_task.date_deadline.date(), fields.Date.from_string("2025-05-28") + ) + + @users("test-user") + @freeze_time("2024-11-15") + def test_task_recurrency_quarter(self): + """Every 1 quarter forever""" + self.service_task_recurrency.task_repeat_unit = "quarter" + self.service_task_recurrency.task_repeat_interval = 1 + self.sale_order.action_confirm() + self.assertFalse(self.sol_no_task.task_id) + self.assertFalse(self.sol_task.task_id.recurring_task) + task = self.sol_task_recurrency.task_id + self.assertTrue(task.recurring_task) + # quarter is 3 months + self.assertEqual(task.repeat_interval, 3) + self.assertEqual(task.repeat_unit, "month") + self.assertEqual(task.repeat_type, "forever") + self.assertFalse(task.repeat_until) + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2024-11-15") + ) + self.assertEqual(task.recurring_count, 1) + task.state = "1_done" + task.invalidate_recordset(["recurring_count"]) + self.assertEqual(task.recurring_count, 2) + last_task = self._get_last_task(task) + self.assertEqual( + last_task.date_deadline.date(), fields.Date.from_string("2025-02-15") + ) + # start_date_method = "start_this" + # on November, the quarter start on October + self.service_task_recurrency.task_start_date_method = "start_this" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2024-10-01") + ) + self.assertEqual(task.recurring_count, 1) + task.state = "1_done" + task.invalidate_recordset(["recurring_count"]) + self.assertEqual(task.recurring_count, 2) + last_task = self._get_last_task(task) + self.assertEqual( + last_task.date_deadline.date(), fields.Date.from_string("2025-01-01") + ) + # start_date_method = "end_this" + # on November, the quarter ends on December + self.service_task_recurrency.task_start_date_method = "end_this" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2024-12-31") + ) + self.assertEqual(task.recurring_count, 1) + task.state = "1_done" + task.invalidate_recordset(["recurring_count"]) + self.assertEqual(task.recurring_count, 2) + last_task = self._get_last_task(task) + self.assertEqual( + last_task.date_deadline.date(), fields.Date.from_string("2025-03-31") + ) + # start_date_method = "start_next" + # on November, the next quarter starts on January + self.service_task_recurrency.task_start_date_method = "start_next" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2025-01-01") + ) + self.assertEqual(task.recurring_count, 1) + task.state = "1_done" + task.invalidate_recordset(["recurring_count"]) + self.assertEqual(task.recurring_count, 2) + last_task = self._get_last_task(task) + self.assertEqual( + last_task.date_deadline.date(), fields.Date.from_string("2025-04-01") + ) + # start_date_method = "end_next" + self.service_task_recurrency.task_start_date_method = "end_next" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2025-03-31") + ) + self.assertEqual(task.recurring_count, 1) + task.state = "1_done" + task.invalidate_recordset(["recurring_count"]) + self.assertEqual(task.recurring_count, 2) + last_task = self._get_last_task(task) + self.assertEqual( + last_task.date_deadline.date(), fields.Date.from_string("2025-06-30") + ) + + @users("test-user") + @freeze_time("2024-11-15") + def test_task_recurrency_semester(self): + """Every 1 quarter forever""" + self.service_task_recurrency.task_repeat_unit = "semester" + self.service_task_recurrency.task_repeat_interval = 1 + self.sale_order.action_confirm() + self.assertFalse(self.sol_no_task.task_id) + self.assertFalse(self.sol_task.task_id.recurring_task) + task = self.sol_task_recurrency.task_id + self.assertTrue(task.recurring_task) + # semester is 6 months + self.assertEqual(task.repeat_interval, 6) + self.assertEqual(task.repeat_unit, "month") + self.assertEqual(task.repeat_type, "forever") + self.assertFalse(task.repeat_until) + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2024-11-15") + ) + self.assertEqual(task.recurring_count, 1) + task.state = "1_done" + task.invalidate_recordset(["recurring_count"]) + self.assertEqual(task.recurring_count, 2) + last_task = self._get_last_task(task) + self.assertEqual( + last_task.date_deadline.date(), fields.Date.from_string("2025-05-15") + ) + # start_date_method = "start_this" + # on November, the semester start on July + self.service_task_recurrency.task_start_date_method = "start_this" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2024-07-01") + ) + self.assertEqual(task.recurring_count, 1) + task.state = "1_done" + task.invalidate_recordset(["recurring_count"]) + self.assertEqual(task.recurring_count, 2) + last_task = self._get_last_task(task) + self.assertEqual( + last_task.date_deadline.date(), fields.Date.from_string("2025-01-01") + ) + # start_date_method = "end_this" + # on November, the semester ends on December + self.service_task_recurrency.task_start_date_method = "end_this" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2024-12-31") + ) + self.assertEqual(task.recurring_count, 1) + task.state = "1_done" + task.invalidate_recordset(["recurring_count"]) + self.assertEqual(task.recurring_count, 2) + last_task = self._get_last_task(task) + self.assertEqual( + last_task.date_deadline.date(), fields.Date.from_string("2025-06-30") + ) + # start_date_method = "start_next" + # on November, the next semester starts on January + self.service_task_recurrency.task_start_date_method = "start_next" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2025-01-01") + ) + self.assertEqual(task.recurring_count, 1) + task.state = "1_done" + task.invalidate_recordset(["recurring_count"]) + self.assertEqual(task.recurring_count, 2) + last_task = self._get_last_task(task) + self.assertEqual( + last_task.date_deadline.date(), fields.Date.from_string("2025-07-01") + ) + # start_date_method = "end_next" + self.service_task_recurrency.task_start_date_method = "end_next" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2025-06-30") + ) + self.assertEqual(task.recurring_count, 1) + task.state = "1_done" + task.invalidate_recordset(["recurring_count"]) + self.assertEqual(task.recurring_count, 2) + last_task = self._get_last_task(task) + self.assertEqual( + last_task.date_deadline.date(), fields.Date.from_string("2025-12-30") + ) + + @users("test-user") + @freeze_time("2024-11-15") + def test_task_recurrency_year(self): + """Every 1 year forever""" + self.service_task_recurrency.task_repeat_unit = "year" + self.sale_order.action_confirm() + self.assertFalse(self.sol_no_task.task_id) + self.assertFalse(self.sol_task.task_id.recurring_task) + task = self.sol_task_recurrency.task_id + self.assertTrue(task.recurring_task) + self.assertEqual(task.repeat_interval, 1) + self.assertEqual(task.repeat_unit, "year") + self.assertEqual(task.repeat_type, "forever") + self.assertFalse(task.repeat_until) + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2024-11-15") + ) + self.assertEqual(task.recurring_count, 1) + task.state = "1_done" + task.invalidate_recordset(["recurring_count"]) + self.assertEqual(task.recurring_count, 2) + last_task = self._get_last_task(task) + self.assertEqual( + last_task.date_deadline.date(), fields.Date.from_string("2025-11-15") + ) + # start_date_method = "start_this" + self.service_task_recurrency.task_start_date_method = "start_this" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2024-01-01") + ) + self.assertEqual(task.recurring_count, 1) + task.state = "1_done" + task.invalidate_recordset(["recurring_count"]) + self.assertEqual(task.recurring_count, 2) + last_task = self._get_last_task(task) + self.assertEqual( + last_task.date_deadline.date(), fields.Date.from_string("2025-01-01") + ) + # start_date_method = "end_this" + self.service_task_recurrency.task_start_date_method = "end_this" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2024-12-31") + ) + self.assertEqual(task.recurring_count, 1) + task.state = "1_done" + task.invalidate_recordset(["recurring_count"]) + self.assertEqual(task.recurring_count, 2) + last_task = self._get_last_task(task) + self.assertEqual( + last_task.date_deadline.date(), fields.Date.from_string("2025-12-31") + ) + # start_date_method = "start_next" + self.service_task_recurrency.task_start_date_method = "start_next" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2025-01-01") + ) + self.assertEqual(task.recurring_count, 1) + task.state = "1_done" + task.invalidate_recordset(["recurring_count"]) + self.assertEqual(task.recurring_count, 2) + last_task = self._get_last_task(task) + self.assertEqual( + last_task.date_deadline.date(), fields.Date.from_string("2026-01-01") + ) + # start_date_method = "end_next" + self.service_task_recurrency.task_start_date_method = "end_next" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2025-12-31") + ) + self.assertEqual(task.recurring_count, 1) + task.state = "1_done" + task.invalidate_recordset(["recurring_count"]) + self.assertEqual(task.recurring_count, 2) + last_task = self._get_last_task(task) + self.assertEqual( + last_task.date_deadline.date(), fields.Date.from_string("2026-12-31") + ) + + @users("test-user") + @freeze_time("2024-11-15") + def test_task_recurrency_3_year(self): + """Every 3 year forever""" + self.service_task_recurrency.task_repeat_unit = "year" + self.service_task_recurrency.task_repeat_interval = 3 + self.sale_order.action_confirm() + self.assertFalse(self.sol_no_task.task_id) + self.assertFalse(self.sol_task.task_id.recurring_task) + task = self.sol_task_recurrency.task_id + self.assertTrue(task.recurring_task) + self.assertEqual(task.repeat_interval, 3) + self.assertEqual(task.repeat_unit, "year") + self.assertEqual(task.repeat_type, "forever") + self.assertFalse(task.repeat_until) + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2024-11-15") + ) + self.assertEqual(task.recurring_count, 1) + task.state = "1_done" + task.invalidate_recordset(["recurring_count"]) + self.assertEqual(task.recurring_count, 2) + last_task = self._get_last_task(task) + self.assertEqual( + last_task.date_deadline.date(), fields.Date.from_string("2027-11-15") + ) + # start_date_method = "start_this" + self.service_task_recurrency.task_start_date_method = "start_this" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2024-01-01") + ) + self.assertEqual(task.recurring_count, 1) + task.state = "1_done" + task.invalidate_recordset(["recurring_count"]) + self.assertEqual(task.recurring_count, 2) + last_task = self._get_last_task(task) + self.assertEqual( + last_task.date_deadline.date(), fields.Date.from_string("2027-01-01") + ) + # start_date_method = "end_this" + self.service_task_recurrency.task_start_date_method = "end_this" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2024-12-31") + ) + self.assertEqual(task.recurring_count, 1) + task.state = "1_done" + task.invalidate_recordset(["recurring_count"]) + self.assertEqual(task.recurring_count, 2) + last_task = self._get_last_task(task) + self.assertEqual( + last_task.date_deadline.date(), fields.Date.from_string("2027-12-31") + ) + # start_date_method = "start_next" + self.service_task_recurrency.task_start_date_method = "start_next" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2027-01-01") + ) + self.assertEqual(task.recurring_count, 1) + task.state = "1_done" + task.invalidate_recordset(["recurring_count"]) + self.assertEqual(task.recurring_count, 2) + last_task = self._get_last_task(task) + self.assertEqual( + last_task.date_deadline.date(), fields.Date.from_string("2030-01-01") + ) + # start_date_method = "end_next" + self.service_task_recurrency.task_start_date_method = "end_next" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2027-12-31") + ) + self.assertEqual(task.recurring_count, 1) + task.state = "1_done" + task.invalidate_recordset(["recurring_count"]) + self.assertEqual(task.recurring_count, 2) + last_task = self._get_last_task(task) + self.assertEqual( + last_task.date_deadline.date(), fields.Date.from_string("2030-12-31") + ) + + @users("test-user") + @freeze_time("2024-11-15") + def test_task_recurrency_month_repeat_until(self): + """Every 3 months until 2025-06-15""" + self.service_task_recurrency.task_repeat_interval = 3 + self.service_task_recurrency.task_repeat_unit = "month" + self.service_task_recurrency.task_repeat_type = "until" + self.service_task_recurrency.task_repeat_until = fields.Date.from_string( + "2025-06-15" + ) + self.sale_order.action_confirm() + self.assertFalse(self.sol_no_task.task_id) + self.assertFalse(self.sol_task.task_id.recurring_task) + task = self.sol_task_recurrency.task_id + self.assertTrue(task.recurring_task) + self.assertEqual(task.repeat_interval, 3) + self.assertEqual(task.repeat_unit, "month") + self.assertEqual(task.repeat_type, "until") + self.assertEqual(task.repeat_until, fields.Date.from_string("2025-06-15")) + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2024-11-15") + ) + self.assertEqual(task.recurring_count, 1) + # generate a new task + task.state = "1_done" + task.invalidate_recordset(["recurring_count"]) + self.assertEqual(task.recurring_count, 2) + last_task = self._get_last_task(task) + self.assertEqual(last_task.repeat_until, fields.Date.from_string("2025-06-15")) + self.assertEqual( + last_task.date_deadline.date(), fields.Date.from_string("2025-02-15") + ) + # generate a new task + last_task.state = "1_done" + task.invalidate_recordset(["recurring_count"]) + self.assertEqual(task.recurring_count, 3) + last_task = self._get_last_task(task) + self.assertEqual(last_task.repeat_until, fields.Date.from_string("2025-06-15")) + self.assertEqual( + last_task.date_deadline.date(), fields.Date.from_string("2025-05-15") + ) + # no generate a new task + last_task.state = "1_done" + task.invalidate_recordset(["recurring_count"]) + self.assertEqual(task.recurring_count, 3) + + @users("test-user") + @freeze_time("2024-11-15") + def test_task_recurrency_month_repeat_number(self): + """Every 3 months, 3 repeats, until 2025-08-15""" + self.service_task_recurrency.task_repeat_interval = 3 + self.service_task_recurrency.task_repeat_unit = "month" + self.service_task_recurrency.task_repeat_type = "repeat" + self.service_task_recurrency.task_repeat_number = 3 + self.sale_order.action_confirm() + self.assertFalse(self.sol_no_task.task_id) + self.assertFalse(self.sol_task.task_id.recurring_task) + task = self.sol_task_recurrency.task_id + self.assertTrue(task.recurring_task) + self.assertEqual(task.repeat_interval, 3) + self.assertEqual(task.repeat_unit, "month") + self.assertEqual(task.repeat_type, "until") + self.assertEqual(task.repeat_until, fields.Date.from_string("2025-08-15")) + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2024-11-15") + ) + self.assertEqual(task.recurring_count, 1) + # generate a new task(repeat 1) + task.state = "1_done" + task.invalidate_recordset(["recurring_count"]) + self.assertEqual(task.recurring_count, 2) + last_task = self._get_last_task(task) + self.assertEqual(last_task.repeat_until, fields.Date.from_string("2025-08-15")) + self.assertEqual( + last_task.date_deadline.date(), fields.Date.from_string("2025-02-15") + ) + # generate a new task(repeat 2) + last_task.state = "1_done" + task.invalidate_recordset(["recurring_count"]) + self.assertEqual(task.recurring_count, 3) + last_task = self._get_last_task(task) + self.assertEqual(last_task.repeat_until, fields.Date.from_string("2025-08-15")) + self.assertEqual( + last_task.date_deadline.date(), fields.Date.from_string("2025-05-15") + ) + # generate a new task(repeat 3) + last_task.state = "1_done" + task.invalidate_recordset(["recurring_count"]) + self.assertEqual(task.recurring_count, 4) + last_task = self._get_last_task(task) + self.assertEqual(last_task.repeat_until, fields.Date.from_string("2025-08-15")) + self.assertEqual( + last_task.date_deadline.date(), fields.Date.from_string("2025-08-15") + ) + # no generate a new task + last_task.state = "1_done" + task.invalidate_recordset(["recurring_count"]) + self.assertEqual(task.recurring_count, 4) + + @users("test-user") + @freeze_time("2024-11-15") + def test_task_recurrency_year_repeat_until(self): + """Every 1 years until 2026-12-31""" + self.service_task_recurrency.task_repeat_interval = 1 + self.service_task_recurrency.task_repeat_unit = "year" + self.service_task_recurrency.task_repeat_type = "until" + self.service_task_recurrency.task_repeat_until = fields.Date.from_string( + "2026-12-31" + ) + self.sale_order.action_confirm() + self.assertFalse(self.sol_no_task.task_id) + self.assertFalse(self.sol_task.task_id.recurring_task) + task = self.sol_task_recurrency.task_id + self.assertTrue(task.recurring_task) + self.assertEqual(task.repeat_interval, 1) + self.assertEqual(task.repeat_unit, "year") + self.assertEqual(task.repeat_type, "until") + self.assertEqual(task.repeat_until, fields.Date.from_string("2026-12-31")) + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2024-11-15") + ) + self.assertEqual(task.recurring_count, 1) + # generate a new task + task.state = "1_done" + task.invalidate_recordset(["recurring_count"]) + self.assertEqual(task.recurring_count, 2) + last_task = self._get_last_task(task) + self.assertEqual(last_task.repeat_until, fields.Date.from_string("2026-12-31")) + self.assertEqual( + last_task.date_deadline.date(), fields.Date.from_string("2025-11-15") + ) + # generate a new task + last_task.state = "1_done" + task.invalidate_recordset(["recurring_count"]) + self.assertEqual(task.recurring_count, 3) + last_task = self._get_last_task(task) + self.assertEqual(last_task.repeat_until, fields.Date.from_string("2026-12-31")) + self.assertEqual( + last_task.date_deadline.date(), fields.Date.from_string("2026-11-15") + ) + # no generate a new task + last_task.state = "1_done" + task.invalidate_recordset(["recurring_count"]) + self.assertEqual(task.recurring_count, 3) + + @users("test-user") + @freeze_time("2024-11-15") + def test_task_recurrency_year_repeat_number(self): + """Every 1 year, 3 repeats, until 2027-11-15""" + self.service_task_recurrency.task_repeat_interval = 1 + self.service_task_recurrency.task_repeat_unit = "year" + self.service_task_recurrency.task_repeat_type = "repeat" + self.service_task_recurrency.task_repeat_number = 3 + self.sale_order.action_confirm() + self.assertFalse(self.sol_no_task.task_id) + self.assertFalse(self.sol_task.task_id.recurring_task) + task = self.sol_task_recurrency.task_id + self.assertTrue(task.recurring_task) + self.assertEqual(task.repeat_interval, 1) + self.assertEqual(task.repeat_unit, "year") + self.assertEqual(task.repeat_type, "until") + self.assertEqual(task.repeat_until, fields.Date.from_string("2027-11-15")) + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2024-11-15") + ) + self.assertEqual(task.recurring_count, 1) + # generate a new task(repeat 1) + task.state = "1_done" + task.invalidate_recordset(["recurring_count"]) + self.assertEqual(task.recurring_count, 2) + last_task = self._get_last_task(task) + self.assertEqual(last_task.repeat_until, fields.Date.from_string("2027-11-15")) + self.assertEqual( + last_task.date_deadline.date(), fields.Date.from_string("2025-11-15") + ) + # generate a new task(repeat 2) + last_task.state = "1_done" + task.invalidate_recordset(["recurring_count"]) + self.assertEqual(task.recurring_count, 3) + last_task = self._get_last_task(task) + self.assertEqual(last_task.repeat_until, fields.Date.from_string("2027-11-15")) + self.assertEqual( + last_task.date_deadline.date(), fields.Date.from_string("2026-11-15") + ) + # generate a new task(repeat 3) + last_task.state = "1_done" + task.invalidate_recordset(["recurring_count"]) + self.assertEqual(task.recurring_count, 4) + last_task = self._get_last_task(task) + self.assertEqual(last_task.repeat_until, fields.Date.from_string("2027-11-15")) + self.assertEqual( + last_task.date_deadline.date(), fields.Date.from_string("2027-11-15") + ) + # no generate a new task + last_task.state = "1_done" + task.invalidate_recordset(["recurring_count"]) + self.assertEqual(task.recurring_count, 4) + + @users("test-user") + @freeze_time("2024-11-15") + def test_task_recurrency_year_force_month(self): + """Every 1 year, force month to July""" + self.service_task_recurrency.task_repeat_interval = 1 + self.service_task_recurrency.task_repeat_unit = "year" + self.service_task_recurrency.task_force_month = "6" + self.service_task_recurrency.task_repeat_type = "forever" + self.sale_order.action_confirm() + self.assertFalse(self.sol_no_task.task_id) + self.assertFalse(self.sol_task.task_id.recurring_task) + task = self.sol_task_recurrency.task_id + self.assertTrue(task.recurring_task) + self.assertEqual(task.repeat_interval, 1) + self.assertEqual(task.repeat_unit, "year") + self.assertEqual(task.repeat_type, "forever") + self.assertEqual(task.recurring_count, 1) + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2024-06-15") + ) + # start_this + self.service_task_recurrency.task_start_date_method = "start_this" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2024-06-01") + ) + # end_this + self.service_task_recurrency.task_start_date_method = "end_this" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2024-06-30") + ) + # start_next + self.service_task_recurrency.task_start_date_method = "start_next" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2025-06-01") + ) + # end_next + self.service_task_recurrency.task_start_date_method = "end_next" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2025-06-30") + ) + + @users("test-user") + @freeze_time("2024-11-15") + def test_task_recurrency_quarter_force_month(self): + # Force month to firts month of quarter: January, April, July, October + self.service_task_recurrency.task_repeat_interval = 1 + self.service_task_recurrency.task_repeat_unit = "quarter" + self.service_task_recurrency.task_force_month_quarter = "1" + self.service_task_recurrency.task_repeat_type = "forever" + self.sale_order.action_confirm() + task = self.sol_task_recurrency.task_id + self.assertTrue(task.recurring_task) + self.assertEqual(task.repeat_interval, 3) + self.assertEqual(task.repeat_unit, "month") + self.assertEqual(task.repeat_type, "forever") + self.assertEqual(task.recurring_count, 1) + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2024-10-15") + ) + # start_this + self.service_task_recurrency.task_start_date_method = "start_this" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2024-10-01") + ) + # end_this + self.service_task_recurrency.task_start_date_method = "end_this" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2024-10-31") + ) + # start_next + self.service_task_recurrency.task_start_date_method = "start_next" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2025-01-01") + ) + # end_next + self.service_task_recurrency.task_start_date_method = "end_next" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2025-01-31") + ) + # Force month to second month of quarter: February, May, August, November + self.service_task_recurrency.task_force_month_quarter = "2" + self.service_task_recurrency.task_start_date_method = "current_date" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertTrue(task.recurring_task) + self.assertEqual(task.repeat_interval, 3) + self.assertEqual(task.repeat_unit, "month") + self.assertEqual(task.recurring_count, 1) + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2024-11-15") + ) + # start_this + self.service_task_recurrency.task_start_date_method = "start_this" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2024-11-01") + ) + # end_this + self.service_task_recurrency.task_start_date_method = "end_this" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2024-11-30") + ) + # start_next + self.service_task_recurrency.task_start_date_method = "start_next" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2025-02-01") + ) + # end_next + self.service_task_recurrency.task_start_date_method = "end_next" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2025-02-28") + ) + # Force month to third month of quarter: March, June, September, December + self.service_task_recurrency.task_force_month_quarter = "3" + self.service_task_recurrency.task_start_date_method = "current_date" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertTrue(task.recurring_task) + self.assertEqual(task.repeat_interval, 3) + self.assertEqual(task.repeat_unit, "month") + self.assertEqual(task.recurring_count, 1) + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2024-12-15") + ) + # start_this + self.service_task_recurrency.task_start_date_method = "start_this" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2024-12-01") + ) + # end_this + self.service_task_recurrency.task_start_date_method = "end_this" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2024-12-31") + ) + # start_next + self.service_task_recurrency.task_start_date_method = "start_next" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2025-03-01") + ) + # end_next + self.service_task_recurrency.task_start_date_method = "end_next" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2025-03-31") + ) + + @users("test-user") + @freeze_time("2024-11-15") + def test_task_recurrency_semester_force_month(self): + # Force month to second month of semester + self.service_task_recurrency.task_repeat_interval = 1 + self.service_task_recurrency.task_repeat_unit = "semester" + self.service_task_recurrency.task_force_month_semester = "2" + self.service_task_recurrency.task_repeat_type = "forever" + self.sale_order.action_confirm() + task = self.sol_task_recurrency.task_id + self.assertTrue(task.recurring_task) + self.assertEqual(task.repeat_interval, 6) + self.assertEqual(task.repeat_unit, "month") + self.assertEqual(task.repeat_type, "forever") + self.assertEqual(task.recurring_count, 1) + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2024-08-15") + ) + # start_this + self.service_task_recurrency.task_start_date_method = "start_this" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2024-08-01") + ) + # end_this + self.service_task_recurrency.task_start_date_method = "end_this" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2024-08-31") + ) + # start_next + self.service_task_recurrency.task_start_date_method = "start_next" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2025-02-01") + ) + # end_next + self.service_task_recurrency.task_start_date_method = "end_next" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2025-02-28") + ) + # Force month to four month of semester + self.service_task_recurrency.task_force_month_semester = "4" + self.service_task_recurrency.task_start_date_method = "current_date" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertTrue(task.recurring_task) + self.assertEqual(task.recurring_count, 1) + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2024-10-15") + ) + # start_this + self.service_task_recurrency.task_start_date_method = "start_this" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2024-10-01") + ) + # end_this + self.service_task_recurrency.task_start_date_method = "end_this" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2024-10-31") + ) + # start_next + self.service_task_recurrency.task_start_date_method = "start_next" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2025-04-01") + ) + # end_next + self.service_task_recurrency.task_start_date_method = "end_next" + self._reprocess_sale_order() + task = self.sol_task_recurrency.task_id + self.assertEqual( + task.date_deadline.date(), fields.Date.from_string("2025-04-30") + ) + + def test_service_tracking_onchange(self): + """Test the onchange behavior of service_tracking field for both + product.template and product.product models""" + + # Test product.template onchange + template = self.service_task_recurrency.product_tmpl_id + self.assertTrue(template.recurring_task) + + # Test changing to 'no' service tracking + template.service_tracking = "no" + template._onchange_service_tracking() + self.assertFalse(template.recurring_task) + + # Test changing back to task_global_project + template.service_tracking = "task_global_project" + template._onchange_service_tracking() + # Note: changing service_tracking doesn't automatically re-enable recurring_task + self.assertFalse(template.recurring_task) + + # Test changing to 'task_in_project' + template.recurring_task = True + template.service_tracking = "task_in_project" + template._onchange_service_tracking() + self.assertTrue(template.recurring_task) + + # Test changing to 'project_only' + template.service_tracking = "project_only" + template._onchange_service_tracking() + self.assertFalse(template.recurring_task) + + # Test product.product onchange + product = self.service_task_recurrency + product.recurring_task = True + + # Test changing to 'no' service tracking + product.service_tracking = "no" + product._onchange_service_tracking() + self.assertFalse(product.recurring_task) + + # Test changing back to task_global_project + product.service_tracking = "task_global_project" + product._onchange_service_tracking() + # Note: changing service_tracking doesn't automatically re-enable recurring_task + self.assertFalse(product.recurring_task) + + # Test changing to 'task_in_project' + product.recurring_task = True + product.service_tracking = "task_in_project" + product._onchange_service_tracking() + self.assertTrue(product.recurring_task) + + # Test changing to 'project_only' + product.service_tracking = "project_only" + product._onchange_service_tracking() + self.assertFalse(product.recurring_task) + + def test_service_tracking_create_variants(self): + # Create a product attribute and value + attribute = self.env["product.attribute"].create( + { + "name": "Test Attribute", + } + ) + val1 = self.env["product.attribute.value"].create( + {"name": "Value 1", "attribute_id": attribute.id} + ) + val2 = self.env["product.attribute.value"].create( + {"name": "Value 2", "attribute_id": attribute.id} + ) + + # Create a product template with the service tracking and recurring task + template = self.env["product.template"].create( + { + "name": "Test Service with Variants", + "type": "service", + "service_tracking": "task_global_project", + "recurring_task": True, + "attribute_line_ids": [ + ( + 0, + 0, + { + "attribute_id": attribute.id, + "value_ids": [(6, 0, [val1.id, val2.id])], + }, + ) + ], + } + ) + + # Create product variants + variants = template.product_variant_ids + + # Ensure recurring_task is set on variants + for variant in variants: + self.assertTrue(variant.recurring_task) + + # Change service_tracking to task_in_project + variant = variants[0] # Assuming the first variant + variant.service_tracking = "task_in_project" + variant._onchange_service_tracking() + + # Assert that recurring_task is now True after service_tracking change + self.assertTrue(variant.recurring_task) + + def test_service_tracking_edge_cases(self): + """Test edge cases and special scenarios for service tracking""" + + product = self.service_task_recurrency + + # Test rapid changes in service tracking + service_tracking_values = [ + "no", + "task_global_project", + "project_only", + "task_in_project", + ] + for tracking in service_tracking_values: + product.recurring_task = True + product.service_tracking = tracking + product._onchange_service_tracking() + if tracking in ["task_global_project", "task_in_project"]: + self.assertTrue(product.recurring_task) + else: + self.assertFalse(product.recurring_task) + + # Test changing service tracking with recurring task enabled/disabled + product.recurring_task = True + product.service_tracking = "task_global_project" + product._onchange_service_tracking() + self.assertTrue(product.recurring_task) + + # Test disabling service tracking + product.service_tracking = "no" + product._onchange_service_tracking() + self.assertFalse(product.recurring_task) + + # Test re-enabling service tracking + # Note: It should not automatically re-enable recurring_task + product.service_tracking = "task_global_project" + product._onchange_service_tracking() + self.assertFalse(product.recurring_task) diff --git a/sale_project_task_recurrency/views/product_template_views.xml b/sale_project_task_recurrency/views/product_template_views.xml new file mode 100644 index 0000000000..83c3e0b837 --- /dev/null +++ b/sale_project_task_recurrency/views/product_template_views.xml @@ -0,0 +1,83 @@ + + + + + view.product.template.form + product.template + + + + + + + +