From 07d2afdb7fc356a904b28ca63cfdc10749c22631 Mon Sep 17 00:00:00 2001 From: Carlos Lopez Date: Wed, 20 Nov 2024 08:35:14 -0500 Subject: [PATCH 01/11] [ADD] sale_project_task_recurrency: Configuring Task Recurrence from the Product Form --- sale_project_task_recurrency/README.rst | 104 +++ sale_project_task_recurrency/__init__.py | 1 + sale_project_task_recurrency/__manifest__.py | 16 + .../i18n/sale_project_task_recurrency.pot | 150 ++++ .../models/__init__.py | 2 + .../models/product_template.py | 76 ++ .../models/sale_order_line.py | 75 ++ sale_project_task_recurrency/pyproject.toml | 3 + .../readme/CONFIGURE.md | 8 + .../readme/CONTRIBUTORS.md | 3 + .../readme/DESCRIPTION.md | 2 + sale_project_task_recurrency/readme/USAGE.md | 5 + .../static/description/icon.png | Bin 0 -> 9455 bytes .../static/description/index.html | 457 ++++++++++++ .../tests/__init__.py | 1 + .../tests/test_product_task_recurrency.py | 700 ++++++++++++++++++ .../views/product_template_views.xml | 70 ++ 17 files changed, 1673 insertions(+) create mode 100644 sale_project_task_recurrency/README.rst create mode 100644 sale_project_task_recurrency/__init__.py create mode 100644 sale_project_task_recurrency/__manifest__.py create mode 100644 sale_project_task_recurrency/i18n/sale_project_task_recurrency.pot create mode 100644 sale_project_task_recurrency/models/__init__.py create mode 100644 sale_project_task_recurrency/models/product_template.py create mode 100644 sale_project_task_recurrency/models/sale_order_line.py create mode 100644 sale_project_task_recurrency/pyproject.toml create mode 100644 sale_project_task_recurrency/readme/CONFIGURE.md create mode 100644 sale_project_task_recurrency/readme/CONTRIBUTORS.md create mode 100644 sale_project_task_recurrency/readme/DESCRIPTION.md create mode 100644 sale_project_task_recurrency/readme/USAGE.md create mode 100644 sale_project_task_recurrency/static/description/icon.png create mode 100644 sale_project_task_recurrency/static/description/index.html create mode 100644 sale_project_task_recurrency/tests/__init__.py create mode 100644 sale_project_task_recurrency/tests/test_product_task_recurrency.py create mode 100644 sale_project_task_recurrency/views/product_template_views.xml diff --git a/sale_project_task_recurrency/README.rst b/sale_project_task_recurrency/README.rst new file mode 100644 index 0000000000..388ec4b23f --- /dev/null +++ b/sale_project_task_recurrency/README.rst @@ -0,0 +1,104 @@ +============================ +Sale project task recurrency +============================ + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:bad249e38e180277d68f401bdf0bd9385ca0561141736c76f646a7f277648b82 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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/17.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-17-0/project-17-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=17.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 + +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..78bd9776b3 --- /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": "17.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/sale_project_task_recurrency.pot b/sale_project_task_recurrency/i18n/sale_project_task_recurrency.pot new file mode 100644 index 0000000000..ecc4743f4a --- /dev/null +++ b/sale_project_task_recurrency/i18n/sale_project_task_recurrency.pot @@ -0,0 +1,150 @@ +# 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_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,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_repeat_type__forever +msgid "Forever" +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_repeat_unit__month +msgid "Months" +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_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,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,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..ed9bc74487 --- /dev/null +++ b/sale_project_task_recurrency/models/product_template.py @@ -0,0 +1,76 @@ +# 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"), + ("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. + """, + ) + + @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..afb2240ce3 --- /dev/null +++ b/sale_project_task_recurrency/models/sale_order_line.py @@ -0,0 +1,75 @@ +# 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 + + +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 + ) + values.update( + { + "repeat_interval": self.product_id.task_repeat_interval, + "repeat_unit": self.product_id.task_repeat_unit, + "repeat_type": repeat_type, + "recurring_task": True, + "date_deadline": self._get_task_date_deadline(), + "repeat_until": self._get_task_repeat_until(), + } + ) + return values + + def _get_task_date_deadline(self): + self.ensure_one() + product = self.product_id + task_start_date_method = product.task_start_date_method + date_deadline_tz = fields.Datetime.context_timestamp( + self, datetime.now() + ) + relativedelta(hour=12, minute=0, second=0) + date_deadline = date_deadline_tz.astimezone(pytz.UTC).replace(tzinfo=None) + if ( + product.task_repeat_unit in ["month", "year"] + and task_start_date_method != "current_date" + ): + if product.task_repeat_unit == "month": + date_deadline += relativedelta(day=1) + if "_next" in task_start_date_method: + date_deadline += relativedelta(months=product.task_repeat_interval) + if "end_" in task_start_date_method: + date_deadline += relativedelta(day=31) + else: + date_deadline += relativedelta(month=1, day=1) + if "_next" in task_start_date_method: + date_deadline += relativedelta(years=product.task_repeat_interval) + if "end_" in task_start_date_method: + date_deadline += relativedelta(month=12, day=31) + return date_deadline + + def _get_task_repeat_until(self): + self.ensure_one() + product = self.product_id + repeat_until = False + if product.task_repeat_type == "repeat": + if product.task_repeat_unit == "month": + repeat_until = fields.Date.context_today(self) + relativedelta( + months=product.task_repeat_number * product.task_repeat_interval + ) + else: + repeat_until = fields.Date.context_today(self) + relativedelta( + years=product.task_repeat_number * product.task_repeat_interval + ) + 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..76723133dd --- /dev/null +++ b/sale_project_task_recurrency/readme/CONTRIBUTORS.md @@ -0,0 +1,3 @@ +- [Tecnativa](https://www.tecnativa.com): + - Pedro M. Baeza + - Carlos López \ No newline at end of file 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 0000000000000000000000000000000000000000..3a0328b516c4980e8e44cdb63fd945757ddd132d GIT binary patch literal 9455 zcmW++2RxMjAAjx~&dlBk9S+%}OXg)AGE&Cb*&}d0jUxM@u(PQx^-s)697TX`ehR4?GS^qbkof1cslKgkU)h65qZ9Oc=ml_0temigYLJfnz{IDzUf>bGs4N!v3=Z3jMq&A#7%rM5eQ#dc?k~! zVpnB`o+K7|Al`Q_U;eD$B zfJtP*jH`siUq~{KE)`jP2|#TUEFGRryE2`i0**z#*^6~AI|YzIWy$Cu#CSLW3q=GA z6`?GZymC;dCPk~rBS%eCb`5OLr;RUZ;D`}um=H)BfVIq%7VhiMr)_#G0N#zrNH|__ zc+blN2UAB0=617@>_u;MPHN;P;N#YoE=)R#i$k_`UAA>WWCcEVMh~L_ zj--gtp&|K1#58Yz*AHCTMziU1Jzt_jG0I@qAOHsk$2}yTmVkBp_eHuY$A9)>P6o~I z%aQ?!(GqeQ-Y+b0I(m9pwgi(IIZZzsbMv+9w{PFtd_<_(LA~0H(xz{=FhLB@(1&qHA5EJw1>>=%q2f&^X>IQ{!GJ4e9U z&KlB)z(84HmNgm2hg2C0>WM{E(DdPr+EeU_N@57;PC2&DmGFW_9kP&%?X4}+xWi)( z;)z%wI5>D4a*5XwD)P--sPkoY(a~WBw;E~AW`Yue4kFa^LM3X`8x|}ZUeMnqr}>kH zG%WWW>3ml$Yez?i%)2pbKPI7?5o?hydokgQyZsNEr{a|mLdt;X2TX(#B1j35xPnPW z*bMSSOauW>o;*=kO8ojw91VX!qoOQb)zHJ!odWB}d+*K?#sY_jqPdg{Sm2HdYzdEx zOGVPhVRTGPtv0o}RfVP;Nd(|CB)I;*t&QO8h zFfekr30S!-LHmV_Su-W+rEwYXJ^;6&3|L$mMC8*bQptyOo9;>Qb9Q9`ySe3%V$A*9 zeKEe+b0{#KWGp$F+tga)0RtI)nhMa-K@JS}2krK~n8vJ=Ngm?R!9G<~RyuU0d?nz# z-5EK$o(!F?hmX*2Yt6+coY`6jGbb7tF#6nHA zuKk=GGJ;ZwON1iAfG$E#Y7MnZVmrY|j0eVI(DN_MNFJmyZ|;w4tf@=CCDZ#5N_0K= z$;R~bbk?}TpfDjfB&aiQ$VA}s?P}xPERJG{kxk5~R`iRS(SK5d+Xs9swCozZISbnS zk!)I0>t=A<-^z(cmSFz3=jZ23u13X><0b)P)^1T_))Kr`e!-pb#q&J*Q`p+B6la%C zuVl&0duN<;uOsB3%T9Fp8t{ED108<+W(nOZd?gDnfNBC3>M8WE61$So|P zVvqH0SNtDTcsUdzaMDpT=Ty0pDHHNL@Z0w$Y`XO z2M-_r1S+GaH%pz#Uy0*w$Vdl=X=rQXEzO}d6J^R6zjM1u&c9vYLvLp?W7w(?np9x1 zE_0JSAJCPB%i7p*Wvg)pn5T`8k3-uR?*NT|J`eS#_#54p>!p(mLDvmc-3o0mX*mp_ zN*AeS<>#^-{S%W<*mz^!X$w_2dHWpcJ6^j64qFBft-o}o_Vx80o0>}Du;>kLts;$8 zC`7q$QI(dKYG`Wa8#wl@V4jVWBRGQ@1dr-hstpQL)Tl+aqVpGpbSfN>5i&QMXfiZ> zaA?T1VGe?rpQ@;+pkrVdd{klI&jVS@I5_iz!=UMpTsa~mBga?1r}aRBm1WS;TT*s0f0lY=JBl66Upy)-k4J}lh=P^8(SXk~0xW=T9v*B|gzIhN z>qsO7dFd~mgxAy4V?&)=5ieYq?zi?ZEoj)&2o)RLy=@hbCRcfT5jigwtQGE{L*8<@Yd{zg;CsL5mvzfDY}P-wos_6PfprFVaeqNE%h zKZhLtcQld;ZD+>=nqN~>GvROfueSzJD&BE*}XfU|H&(FssBqY=hPCt`d zH?@s2>I(|;fcW&YM6#V#!kUIP8$Nkdh0A(bEVj``-AAyYgwY~jB zT|I7Bf@%;7aL7Wf4dZ%VqF$eiaC38OV6oy3Z#TER2G+fOCd9Iaoy6aLYbPTN{XRPz z;U!V|vBf%H!}52L2gH_+j;`bTcQRXB+y9onc^wLm5wi3-Be}U>k_u>2Eg$=k!(l@I zcCg+flakT2Nej3i0yn+g+}%NYb?ta;R?(g5SnwsQ49U8Wng8d|{B+lyRcEDvR3+`O{zfmrmvFrL6acVP%yG98X zo&+VBg@px@i)%o?dG(`T;n*$S5*rnyiR#=wW}}GsAcfyQpE|>a{=$Hjg=-*_K;UtD z#z-)AXwSRY?OPefw^iI+ z)AXz#PfEjlwTes|_{sB?4(O@fg0AJ^g8gP}ex9Ucf*@_^J(s_5jJV}c)s$`Myn|Kd z$6>}#q^n{4vN@+Os$m7KV+`}c%4)4pv@06af4-x5#wj!KKb%caK{A&Y#Rfs z-po?Dcb1({W=6FKIUirH&(yg=*6aLCekcKwyfK^JN5{wcA3nhO(o}SK#!CINhI`-I z1)6&n7O&ZmyFMuNwvEic#IiOAwNkR=u5it{B9n2sAJV5pNhar=j5`*N!Na;c7g!l$ z3aYBqUkqqTJ=Re-;)s!EOeij=7SQZ3Hq}ZRds%IM*PtM$wV z@;rlc*NRK7i3y5BETSKuumEN`Xu_8GP1Ri=OKQ$@I^ko8>H6)4rjiG5{VBM>B|%`&&s^)jS|-_95&yc=GqjNo{zFkw%%HHhS~e=s zD#sfS+-?*t|J!+ozP6KvtOl!R)@@-z24}`9{QaVLD^9VCSR2b`b!KC#o;Ki<+wXB6 zx3&O0LOWcg4&rv4QG0)4yb}7BFSEg~=IR5#ZRj8kg}dS7_V&^%#Do==#`u zpy6{ox?jWuR(;pg+f@mT>#HGWHAJRRDDDv~@(IDw&R>9643kK#HN`!1vBJHnC+RM&yIh8{gG2q zA%e*U3|N0XSRa~oX-3EAneep)@{h2vvd3Xvy$7og(sayr@95+e6~Xvi1tUqnIxoIH zVWo*OwYElb#uyW{Imam6f2rGbjR!Y3`#gPqkv57dB6K^wRGxc9B(t|aYDGS=m$&S!NmCtrMMaUg(c zc2qC=2Z`EEFMW-me5B)24AqF*bV5Dr-M5ig(l-WPS%CgaPzs6p_gnCIvTJ=Y<6!gT zVt@AfYCzjjsMEGi=rDQHo0yc;HqoRNnNFeWZgcm?f;cp(6CNylj36DoL(?TS7eU#+ z7&mfr#y))+CJOXQKUMZ7QIdS9@#-}7y2K1{8)cCt0~-X0O!O?Qx#E4Og+;A2SjalQ zs7r?qn0H044=sDN$SRG$arw~n=+T_DNdSrarmu)V6@|?1-ZB#hRn`uilTGPJ@fqEy zGt(f0B+^JDP&f=r{#Y_wi#AVDf-y!RIXU^0jXsFpf>=Ji*TeqSY!H~AMbJdCGLhC) zn7Rx+sXw6uYj;WRYrLd^5IZq@6JI1C^YkgnedZEYy<&4(z%Q$5yv#Boo{AH8n$a zhb4Y3PWdr269&?V%uI$xMcUrMzl=;w<_nm*qr=c3Rl@i5wWB;e-`t7D&c-mcQl7x! zZWB`UGcw=Y2=}~wzrfLx=uet<;m3~=8I~ZRuzvMQUQdr+yTV|ATf1Uuomr__nDf=X zZ3WYJtHp_ri(}SQAPjv+Y+0=fH4krOP@S&=zZ-t1jW1o@}z;xk8 z(Nz1co&El^HK^NrhVHa-_;&88vTU>_J33=%{if;BEY*J#1n59=07jrGQ#IP>@u#3A z;!q+E1Rj3ZJ+!4bq9F8PXJ@yMgZL;>&gYA0%_Kbi8?S=XGM~dnQZQ!yBSgcZhY96H zrWnU;k)qy`rX&&xlDyA%(a1Hhi5CWkmg(`Gb%m(HKi-7Z!LKGRP_B8@`7&hdDy5n= z`OIxqxiVfX@OX1p(mQu>0Ai*v_cTMiw4qRt3~NBvr9oBy0)r>w3p~V0SCm=An6@3n)>@z!|o-$HvDK z|3D2ZMJkLE5loMKl6R^ez@Zz%S$&mbeoqH5`Bb){Ei21q&VP)hWS2tjShfFtGE+$z zzCR$P#uktu+#!w)cX!lWN1XU%K-r=s{|j?)Akf@q#3b#{6cZCuJ~gCxuMXRmI$nGtnH+-h z+GEi!*X=AP<|fG`1>MBdTb?28JYc=fGvAi2I<$B(rs$;eoJCyR6_bc~p!XR@O-+sD z=eH`-ye})I5ic1eL~TDmtfJ|8`0VJ*Yr=hNCd)G1p2MMz4C3^Mj?7;!w|Ly%JqmuW zlIEW^Ft%z?*|fpXda>Jr^1noFZEwFgVV%|*XhH@acv8rdGxeEX{M$(vG{Zw+x(ei@ zmfXb22}8-?Fi`vo-YVrTH*C?a8%M=Hv9MqVH7H^J$KsD?>!SFZ;ZsvnHr_gn=7acz z#W?0eCdVhVMWN12VV^$>WlQ?f;P^{(&pYTops|btm6aj>_Uz+hqpGwB)vWp0Cf5y< zft8-je~nn?W11plq}N)4A{l8I7$!ks_x$PXW-2XaRFswX_BnF{R#6YIwMhAgd5F9X zGmwdadS6(a^fjHtXg8=l?Rc0Sm%hk6E9!5cLVloEy4eh(=FwgP`)~I^5~pBEWo+F6 zSf2ncyMurJN91#cJTy_u8Y}@%!bq1RkGC~-bV@SXRd4F{R-*V`bS+6;W5vZ(&+I<9$;-V|eNfLa5n-6% z2(}&uGRF;p92eS*sE*oR$@pexaqr*meB)VhmIg@h{uzkk$9~qh#cHhw#>O%)b@+(| z^IQgqzuj~Sk(J;swEM-3TrJAPCq9k^^^`q{IItKBRXYe}e0Tdr=Huf7da3$l4PdpwWDop%^}n;dD#K4s#DYA8SHZ z&1!riV4W4R7R#C))JH1~axJ)RYnM$$lIR%6fIVA@zV{XVyx}C+a-Dt8Y9M)^KU0+H zR4IUb2CJ{Hg>CuaXtD50jB(_Tcx=Z$^WYu2u5kubqmwp%drJ6 z?Fo40g!Qd<-l=TQxqHEOuPX0;^z7iX?Ke^a%XT<13TA^5`4Xcw6D@Ur&VT&CUe0d} z1GjOVF1^L@>O)l@?bD~$wzgf(nxX1OGD8fEV?TdJcZc2KoUe|oP1#=$$7ee|xbY)A zDZq+cuTpc(fFdj^=!;{k03C69lMQ(|>uhRfRu%+!k&YOi-3|1QKB z z?n?eq1XP>p-IM$Z^C;2L3itnbJZAip*Zo0aw2bs8@(s^~*8T9go!%dHcAz2lM;`yp zD=7&xjFV$S&5uDaiScyD?B-i1ze`+CoRtz`Wn+Zl&#s4&}MO{@N!ufrzjG$B79)Y2d3tBk&)TxUTw@QS0TEL_?njX|@vq?Uz(nBFK5Pq7*xj#u*R&i|?7+6# z+|r_n#SW&LXhtheZdah{ZVoqwyT{D>MC3nkFF#N)xLi{p7J1jXlmVeb;cP5?e(=f# zuT7fvjSbjS781v?7{)-X3*?>tq?)Yd)~|1{BDS(pqC zC}~H#WXlkUW*H5CDOo<)#x7%RY)A;ShGhI5s*#cRDA8YgqG(HeKDx+#(ZQ?386dv! zlXCO)w91~Vw4AmOcATuV653fa9R$fyK8ul%rG z-wfS zihugoZyr38Im?Zuh6@RcF~t1anQu7>#lPpb#}4cOA!EM11`%f*07RqOVkmX{p~KJ9 z^zP;K#|)$`^Rb{rnHGH{~>1(fawV0*Z#)}M`m8-?ZJV<+e}s9wE# z)l&az?w^5{)`S(%MRzxdNqrs1n*-=jS^_jqE*5XDrA0+VE`5^*p3CuM<&dZEeCjoz zR;uu_H9ZPZV|fQq`Cyw4nscrVwi!fE6ciMmX$!_hN7uF;jjKG)d2@aC4ropY)8etW=xJvni)8eHi`H$%#zn^WJ5NLc-rqk|u&&4Z6fD_m&JfSI1Bvb?b<*n&sfl0^t z=HnmRl`XrFvMKB%9}>PaA`m-fK6a0(8=qPkWS5bb4=v?XcWi&hRY?O5HdulRi4?fN zlsJ*N-0Qw+Yic@s0(2uy%F@ib;GjXt01Fmx5XbRo6+n|pP(&nodMoap^z{~q ziEeaUT@Mxe3vJSfI6?uLND(CNr=#^W<1b}jzW58bIfyWTDle$mmS(|x-0|2UlX+9k zQ^EX7Nw}?EzVoBfT(-LT|=9N@^hcn-_p&sqG z&*oVs2JSU+N4ZD`FhCAWaS;>|wH2G*Id|?pa#@>tyxX`+4HyIArWDvVrX)2WAOQff z0qyHu&-S@i^MS-+j--!pr4fPBj~_8({~e1bfcl0wI1kaoN>mJL6KUPQm5N7lB(ui1 zE-o%kq)&djzWJ}ob<-GfDlkB;F31j-VHKvQUGQ3sp`CwyGJk_i!y^sD0fqC@$9|jO zOqN!r!8-p==F@ZVP=U$qSpY(gQ0)59P1&t@y?5rvg<}E+GB}26NYPp4f2YFQrQtot5mn3wu_qprZ=>Ig-$ zbW26Ws~IgY>}^5w`vTB(G`PTZaDiGBo5o(tp)qli|NeV( z@H_=R8V39rt5J5YB2Ky?4eJJ#b`_iBe2ot~6%7mLt5t8Vwi^Jy7|jWXqa3amOIoRb zOr}WVFP--DsS`1WpN%~)t3R!arKF^Q$e12KEqU36AWwnCBICpH4XCsfnyrHr>$I$4 z!DpKX$OKLWarN7nv@!uIA+~RNO)l$$w}p(;b>mx8pwYvu;dD_unryX_NhT8*Tj>BTrTTL&!?O+%Rv;b?B??gSzdp?6Uug9{ zd@V08Z$BdI?fpoCS$)t4mg4rT8Q_I}h`0d-vYZ^|dOB*Q^S|xqTV*vIg?@fVFSmMpaw0qtTRbx} z({Pg?#{2`sc9)M5N$*N|4;^t$+QP?#mov zGVC@I*lBVrOU-%2y!7%)fAKjpEFsgQc4{amtiHb95KQEwvf<(3T<9-Zm$xIew#P22 zc2Ix|App^>v6(3L_MCU0d3W##AB0M~3D00EWoKZqsJYT(#@w$Y_H7G22M~ApVFTRHMI_3be)Lkn#0F*V8Pq zc}`Cjy$bE;FJ6H7p=0y#R>`}-m4(0F>%@P|?7fx{=R^uFdISRnZ2W_xQhD{YuR3t< z{6yxu=4~JkeA;|(J6_nv#>Nvs&FuLA&PW^he@t(UwFFE8)|a!R{`E`K`i^ZnyE4$k z;(749Ix|oi$c3QbEJ3b~D_kQsPz~fIUKym($a_7dJ?o+40*OLl^{=&oq$<#Q(yyrp z{J-FAniyAw9tPbe&IhQ|a`DqFTVQGQ&Gq3!C2==4x{6EJwiPZ8zub-iXoUtkJiG{} zPaR&}_fn8_z~(=;5lD-aPWD3z8PZS@AaUiomF!G8I}Mf>e~0g#BelA-5#`cj;O5>N Xviia!U7SGha1wx#SCgwmn*{w2TRX*I literal 0 HcmV?d00001 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..fa39a0ff1e --- /dev/null +++ b/sale_project_task_recurrency/static/description/index.html @@ -0,0 +1,457 @@ + + + + + +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
    • +
    +
  • +
+
+
+

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..1f999ea627 --- /dev/null +++ b/sale_project_task_recurrency/tests/test_product_task_recurrency.py @@ -0,0 +1,700 @@ +# 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_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) 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..0f4017db9b --- /dev/null +++ b/sale_project_task_recurrency/views/product_template_views.xml @@ -0,0 +1,70 @@ + + + + + + view.product.template.form + product.template + + + + + + + + + From cab334baf167dd5654b0cdcb6dd14022a68b4fa2 Mon Sep 17 00:00:00 2001 From: Carlos Lopez Date: Fri, 22 Nov 2024 21:45:54 +0000 Subject: [PATCH 02/11] Added translation using Weblate (Spanish) --- sale_project_task_recurrency/i18n/es.po | 173 ++++++++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 sale_project_task_recurrency/i18n/es.po diff --git a/sale_project_task_recurrency/i18n/es.po b/sale_project_task_recurrency/i18n/es.po new file mode 100644 index 0000000000..631579896d --- /dev/null +++ b/sale_project_task_recurrency/i18n/es.po @@ -0,0 +1,173 @@ +# 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-11-22 22:11+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_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,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_repeat_type__forever +msgid "Forever" +msgstr "Para siempre" + +#. 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_repeat_unit__month +msgid "Months" +msgstr "Meses" + +#. 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_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,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,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" From 934c076a262426ba20846aa275f3bd83acdaa5bf Mon Sep 17 00:00:00 2001 From: mymage Date: Sat, 23 Nov 2024 16:51:17 +0000 Subject: [PATCH 03/11] Added translation using Weblate (Italian) --- sale_project_task_recurrency/i18n/it.po | 178 ++++++++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 sale_project_task_recurrency/i18n/it.po diff --git a/sale_project_task_recurrency/i18n/it.po b/sale_project_task_recurrency/i18n/it.po new file mode 100644 index 0000000000..c06b21fd73 --- /dev/null +++ b/sale_project_task_recurrency/i18n/it.po @@ -0,0 +1,178 @@ +# 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-11-25 11: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_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,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_repeat_type__forever +msgid "Forever" +msgstr "Per sempre" + +#. 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_repeat_unit__month +msgid "Months" +msgstr "Mesi" + +#. 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_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,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,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" From 22fa5f075182fe9540f581c9eed36100b51afb1d Mon Sep 17 00:00:00 2001 From: Carlos Lopez Date: Mon, 16 Dec 2024 08:00:37 -0500 Subject: [PATCH 04/11] [IMP] sale_project_task_recurrency: Allow forcing a specific month when the recurrence is yearly --- sale_project_task_recurrency/README.rst | 38 +++---- sale_project_task_recurrency/__manifest__.py | 2 +- sale_project_task_recurrency/i18n/es.po | 94 ++++++++++++++-- sale_project_task_recurrency/i18n/it.po | 106 +++++++++++++++--- .../i18n/sale_project_task_recurrency.pot | 66 +++++++++++ .../models/product_template.py | 17 +++ .../models/sale_order_line.py | 28 +++-- .../static/description/index.html | 2 +- .../tests/test_product_task_recurrency.py | 49 ++++++++ .../views/product_template_views.xml | 5 + 10 files changed, 352 insertions(+), 55 deletions(-) diff --git a/sale_project_task_recurrency/README.rst b/sale_project_task_recurrency/README.rst index 388ec4b23f..21133e10c7 100644 --- a/sale_project_task_recurrency/README.rst +++ b/sale_project_task_recurrency/README.rst @@ -7,7 +7,7 @@ Sale project task recurrency !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:bad249e38e180277d68f401bdf0bd9385ca0561141736c76f646a7f277648b82 + !! source digest: sha256:333fe55af0b7c235786aba0f66d75dfd763960781b34f0701bf3548783f2793e !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png @@ -39,26 +39,26 @@ sales orders directly in the product form. 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: +- 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. + - 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. +- 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 =========== @@ -81,10 +81,10 @@ Authors Contributors ------------ -- `Tecnativa `__: +- `Tecnativa `__: - - Pedro M. Baeza - - Carlos López + - Pedro M. Baeza + - Carlos López Maintainers ----------- diff --git a/sale_project_task_recurrency/__manifest__.py b/sale_project_task_recurrency/__manifest__.py index 78bd9776b3..1bd4207941 100644 --- a/sale_project_task_recurrency/__manifest__.py +++ b/sale_project_task_recurrency/__manifest__.py @@ -2,7 +2,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). { "name": "Sale project task recurrency", - "version": "17.0.1.0.0", + "version": "17.0.1.1.0", "summary": "Configuring Task Recurrence from the Product Form.", "author": "Tecnativa,Odoo Community Association (OCA)", "website": "https://github.com/OCA/project", diff --git a/sale_project_task_recurrency/i18n/es.po b/sale_project_task_recurrency/i18n/es.po index 631579896d..e9e51498dc 100644 --- a/sale_project_task_recurrency/i18n/es.po +++ b/sale_project_task_recurrency/i18n/es.po @@ -22,6 +22,16 @@ msgstr "" 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 "" + +#. 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" @@ -32,6 +42,11 @@ msgstr "Fecha actual" 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 "" + #. 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 @@ -48,6 +63,17 @@ msgstr "Fin del período actual" 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 "" + +#. 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_template__task_force_month +msgid "Force Month" +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" @@ -59,11 +85,46 @@ msgstr "Para siempre" 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 "" + +#. 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 "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 "" + +#. 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" @@ -90,6 +151,11 @@ msgstr "Repetir cada" 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__9 +msgid "September" +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 @@ -120,22 +186,30 @@ msgid "" " 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" +" - 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" +" 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" +" 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" +" 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" +" 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" +"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" diff --git a/sale_project_task_recurrency/i18n/it.po b/sale_project_task_recurrency/i18n/it.po index c06b21fd73..123def0001 100644 --- a/sale_project_task_recurrency/i18n/it.po +++ b/sale_project_task_recurrency/i18n/it.po @@ -22,6 +22,16 @@ msgstr "" 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 "" + +#. 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" @@ -32,6 +42,11 @@ msgstr "Data attuale" 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 "" + #. 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 @@ -48,6 +63,17 @@ msgstr "Fine periodo attuale" 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 "" + +#. 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_template__task_force_month +msgid "Force Month" +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" @@ -59,11 +85,46 @@ msgstr "Per sempre" 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 "" + +#. 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 "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 "" + +#. 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" @@ -90,6 +151,11 @@ msgstr "Ripeti ogni" 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__9 +msgid "September" +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 @@ -120,18 +186,26 @@ msgid "" " 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" +" - 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" +" 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" +" 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" +" 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" +" 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" @@ -141,23 +215,23 @@ msgstr "" " - 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" +" 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" +" 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" +" 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" +" e il periodo selezionato è \"Anno/i\", la data di inizio sarà il " +"31/12/2025.\n" " " #. module: sale_project_task_recurrency diff --git a/sale_project_task_recurrency/i18n/sale_project_task_recurrency.pot b/sale_project_task_recurrency/i18n/sale_project_task_recurrency.pot index ecc4743f4a..efb318bdb9 100644 --- a/sale_project_task_recurrency/i18n/sale_project_task_recurrency.pot +++ b/sale_project_task_recurrency/i18n/sale_project_task_recurrency.pot @@ -19,6 +19,16 @@ msgstr "" 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" @@ -29,6 +39,11 @@ msgstr "" 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 @@ -45,6 +60,17 @@ msgstr "" 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,field_description:sale_project_task_recurrency.field_product_product__task_force_month +#: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_template__task_force_month +msgid "Force Month" +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" @@ -56,11 +82,46 @@ msgstr "" 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" @@ -87,6 +148,11 @@ msgstr "" 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__9 +msgid "September" +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 diff --git a/sale_project_task_recurrency/models/product_template.py b/sale_project_task_recurrency/models/product_template.py index ed9bc74487..40e3e3c28e 100644 --- a/sale_project_task_recurrency/models/product_template.py +++ b/sale_project_task_recurrency/models/product_template.py @@ -56,6 +56,23 @@ class ProductTemplate(models.Model): 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", + ) @api.onchange("service_tracking") def _onchange_service_tracking(self): diff --git a/sale_project_task_recurrency/models/sale_order_line.py b/sale_project_task_recurrency/models/sale_order_line.py index afb2240ce3..872ff061bb 100644 --- a/sale_project_task_recurrency/models/sale_order_line.py +++ b/sale_project_task_recurrency/models/sale_order_line.py @@ -19,14 +19,15 @@ def _timesheet_create_task_prepare_values(self, project): if self.product_id.task_repeat_type == "repeat" else self.product_id.task_repeat_type ) + date_deadline = self._get_task_date_deadline() values.update( { "repeat_interval": self.product_id.task_repeat_interval, "repeat_unit": self.product_id.task_repeat_unit, "repeat_type": repeat_type, "recurring_task": True, - "date_deadline": self._get_task_date_deadline(), - "repeat_until": self._get_task_repeat_until(), + "date_deadline": date_deadline, + "repeat_until": self._get_task_repeat_until(date_deadline), } ) return values @@ -34,10 +35,17 @@ def _timesheet_create_task_prepare_values(self, project): def _get_task_date_deadline(self): self.ensure_one() product = self.product_id + force_month = int(product.task_force_month) if product.task_force_month else 0 task_start_date_method = product.task_start_date_method date_deadline_tz = fields.Datetime.context_timestamp( self, datetime.now() ) + relativedelta(hour=12, minute=0, second=0) + if ( + force_month + and product.task_repeat_unit == "year" + and task_start_date_method == "current_date" + ): + date_deadline_tz += relativedelta(month=force_month) date_deadline = date_deadline_tz.astimezone(pytz.UTC).replace(tzinfo=None) if ( product.task_repeat_unit in ["month", "year"] @@ -50,25 +58,29 @@ def _get_task_date_deadline(self): if "end_" in task_start_date_method: date_deadline += relativedelta(day=31) else: - date_deadline += relativedelta(month=1, day=1) + date_deadline += relativedelta(month=force_month or 1, day=1) if "_next" in task_start_date_method: date_deadline += relativedelta(years=product.task_repeat_interval) if "end_" in task_start_date_method: - date_deadline += relativedelta(month=12, day=31) + date_deadline += relativedelta(month=force_month or 12, day=31) return date_deadline - def _get_task_repeat_until(self): + 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 = fields.Date.context_today(self) + relativedelta( + repeat_until = date_start + relativedelta( months=product.task_repeat_number * product.task_repeat_interval ) else: - repeat_until = fields.Date.context_today(self) + relativedelta( - years=product.task_repeat_number * product.task_repeat_interval + 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 diff --git a/sale_project_task_recurrency/static/description/index.html b/sale_project_task_recurrency/static/description/index.html index fa39a0ff1e..8d9f7b642f 100644 --- a/sale_project_task_recurrency/static/description/index.html +++ b/sale_project_task_recurrency/static/description/index.html @@ -367,7 +367,7 @@

Sale project task recurrency

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -!! source digest: sha256:bad249e38e180277d68f401bdf0bd9385ca0561141736c76f646a7f277648b82 +!! source digest: sha256:333fe55af0b7c235786aba0f66d75dfd763960781b34f0701bf3548783f2793e !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

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

This module allows configuring the recurrence of tasks created from diff --git a/sale_project_task_recurrency/tests/test_product_task_recurrency.py b/sale_project_task_recurrency/tests/test_product_task_recurrency.py index 1f999ea627..c039a330fb 100644 --- a/sale_project_task_recurrency/tests/test_product_task_recurrency.py +++ b/sale_project_task_recurrency/tests/test_product_task_recurrency.py @@ -698,3 +698,52 @@ def test_task_recurrency_year_repeat_number(self): 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") + ) diff --git a/sale_project_task_recurrency/views/product_template_views.xml b/sale_project_task_recurrency/views/product_template_views.xml index 0f4017db9b..dc48fcf83b 100644 --- a/sale_project_task_recurrency/views/product_template_views.xml +++ b/sale_project_task_recurrency/views/product_template_views.xml @@ -63,6 +63,11 @@ required="recurring_task" groups="project.group_project_recurring_tasks" /> + From 819c55689a581577f37f44cca74e2f36604735ce Mon Sep 17 00:00:00 2001 From: Carlos Lopez Date: Mon, 16 Dec 2024 17:28:25 +0000 Subject: [PATCH 05/11] Translated using Weblate (Spanish) Currently translated at 100.0% (35 of 35 strings) Translation: project-17.0/project-17.0-sale_project_task_recurrency Translate-URL: https://translation.odoo-community.org/projects/project-17-0/project-17-0-sale_project_task_recurrency/es/ --- sale_project_task_recurrency/i18n/es.po | 28 ++++++++++++------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/sale_project_task_recurrency/i18n/es.po b/sale_project_task_recurrency/i18n/es.po index e9e51498dc..2efda44c60 100644 --- a/sale_project_task_recurrency/i18n/es.po +++ b/sale_project_task_recurrency/i18n/es.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 17.0\n" "Report-Msgid-Bugs-To: \n" -"PO-Revision-Date: 2024-11-22 22:11+0000\n" +"PO-Revision-Date: 2024-12-16 17:49+0000\n" "Last-Translator: Carlos Lopez \n" "Language-Team: none\n" "Language: es\n" @@ -25,12 +25,12 @@ 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 "" +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 "" +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 @@ -45,7 +45,7 @@ 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 "" +msgstr "Diciembre" #. module: sale_project_task_recurrency #: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_product__task_repeat_until @@ -66,13 +66,13 @@ 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 "" +msgstr "Febrero" #. 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_template__task_force_month msgid "Force Month" -msgstr "" +msgstr "Forzar mes" #. module: sale_project_task_recurrency #: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_repeat_type__forever @@ -88,27 +88,27 @@ 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 "" +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 "" +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 "" +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 "" +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 "" +msgstr "Mayo" #. module: sale_project_task_recurrency #: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_repeat_unit__month @@ -118,12 +118,12 @@ 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 "" +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 "" +msgstr "Octubre" #. module: sale_project_task_recurrency #: model:ir.model,name:sale_project_task_recurrency.model_product_template @@ -154,7 +154,7 @@ 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__9 msgid "September" -msgstr "" +msgstr "Septiembre" #. module: sale_project_task_recurrency #: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_product__task_start_date_method From 6d753c5764b0b852f79634a61b29535cfb0da62e Mon Sep 17 00:00:00 2001 From: mymage Date: Tue, 17 Dec 2024 10:59:03 +0000 Subject: [PATCH 06/11] Translated using Weblate (Italian) Currently translated at 100.0% (35 of 35 strings) Translation: project-17.0/project-17.0-sale_project_task_recurrency Translate-URL: https://translation.odoo-community.org/projects/project-17-0/project-17-0-sale_project_task_recurrency/it/ --- sale_project_task_recurrency/i18n/it.po | 28 ++++++++++++------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/sale_project_task_recurrency/i18n/it.po b/sale_project_task_recurrency/i18n/it.po index 123def0001..18a955db13 100644 --- a/sale_project_task_recurrency/i18n/it.po +++ b/sale_project_task_recurrency/i18n/it.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 17.0\n" "Report-Msgid-Bugs-To: \n" -"PO-Revision-Date: 2024-11-25 11:06+0000\n" +"PO-Revision-Date: 2024-12-17 13:06+0000\n" "Last-Translator: mymage \n" "Language-Team: none\n" "Language: it\n" @@ -25,12 +25,12 @@ 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 "" +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 "" +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 @@ -45,7 +45,7 @@ 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 "" +msgstr "Dicembre" #. module: sale_project_task_recurrency #: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_product__task_repeat_until @@ -66,13 +66,13 @@ 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 "" +msgstr "Febbraio" #. 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_template__task_force_month msgid "Force Month" -msgstr "" +msgstr "Forza mese" #. module: sale_project_task_recurrency #: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_repeat_type__forever @@ -88,27 +88,27 @@ 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 "" +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 "" +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 "" +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 "" +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 "" +msgstr "Maggio" #. module: sale_project_task_recurrency #: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_repeat_unit__month @@ -118,12 +118,12 @@ 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 "" +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 "" +msgstr "Ottobre" #. module: sale_project_task_recurrency #: model:ir.model,name:sale_project_task_recurrency.model_product_template @@ -154,7 +154,7 @@ 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__9 msgid "September" -msgstr "" +msgstr "Settembre" #. module: sale_project_task_recurrency #: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_product__task_start_date_method From 51288471984c70e128be71a091baf5cc2ec64ded Mon Sep 17 00:00:00 2001 From: Carlos Lopez Date: Wed, 18 Dec 2024 16:40:08 -0500 Subject: [PATCH 07/11] [IMP] sale_project_task_recurrency: Add options to force month intervals for Quarter and Semester When the task is created, the interval is automatically adjusted to months (3 months for Quarter and 6 months for Semester) since Odoo does not support these options directly. --- sale_project_task_recurrency/README.rst | 2 +- sale_project_task_recurrency/__manifest__.py | 2 +- sale_project_task_recurrency/i18n/es.po | 59 +++ sale_project_task_recurrency/i18n/it.po | 59 +++ .../i18n/sale_project_task_recurrency.pot | 59 +++ .../models/product_template.py | 23 + .../models/sale_order_line.py | 82 +++- .../static/description/index.html | 2 +- .../tests/test_product_task_recurrency.py | 394 ++++++++++++++++++ .../views/product_template_views.xml | 12 +- 10 files changed, 667 insertions(+), 27 deletions(-) diff --git a/sale_project_task_recurrency/README.rst b/sale_project_task_recurrency/README.rst index 21133e10c7..88f0d2d95c 100644 --- a/sale_project_task_recurrency/README.rst +++ b/sale_project_task_recurrency/README.rst @@ -7,7 +7,7 @@ Sale project task recurrency !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:333fe55af0b7c235786aba0f66d75dfd763960781b34f0701bf3548783f2793e + !! source digest: sha256:d5da2188db2ac87ba6c4ebb660e25aa1e53a534b122f01791f97e48ae666a311 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png diff --git a/sale_project_task_recurrency/__manifest__.py b/sale_project_task_recurrency/__manifest__.py index 1bd4207941..b616bef5f5 100644 --- a/sale_project_task_recurrency/__manifest__.py +++ b/sale_project_task_recurrency/__manifest__.py @@ -2,7 +2,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). { "name": "Sale project task recurrency", - "version": "17.0.1.1.0", + "version": "17.0.1.2.0", "summary": "Configuring Task Recurrence from the Product Form.", "author": "Tecnativa,Odoo Community Association (OCA)", "website": "https://github.com/OCA/project", diff --git a/sale_project_task_recurrency/i18n/es.po b/sale_project_task_recurrency/i18n/es.po index 2efda44c60..56281bea53 100644 --- a/sale_project_task_recurrency/i18n/es.po +++ b/sale_project_task_recurrency/i18n/es.po @@ -68,17 +68,49 @@ msgstr "Fin del próximo período" 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 @@ -135,6 +167,11 @@ msgstr "Producto" 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" @@ -151,11 +188,27 @@ msgstr "Repetir cada" 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 @@ -178,6 +231,12 @@ msgstr "Inicio del próximo período" 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 diff --git a/sale_project_task_recurrency/i18n/it.po b/sale_project_task_recurrency/i18n/it.po index 18a955db13..a725503303 100644 --- a/sale_project_task_recurrency/i18n/it.po +++ b/sale_project_task_recurrency/i18n/it.po @@ -68,17 +68,49 @@ msgstr "Fine del periodo successivo" 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 @@ -135,6 +167,11 @@ msgstr "Prodotto" 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" @@ -151,11 +188,27 @@ msgstr "Ripeti ogni" 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 @@ -178,6 +231,12 @@ msgstr "Inizio del periodo successivo" 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 diff --git a/sale_project_task_recurrency/i18n/sale_project_task_recurrency.pot b/sale_project_task_recurrency/i18n/sale_project_task_recurrency.pot index efb318bdb9..52456869e8 100644 --- a/sale_project_task_recurrency/i18n/sale_project_task_recurrency.pot +++ b/sale_project_task_recurrency/i18n/sale_project_task_recurrency.pot @@ -65,17 +65,49 @@ msgstr "" 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 @@ -132,6 +164,11 @@ msgstr "" 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" @@ -148,11 +185,27 @@ msgstr "" 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 @@ -175,6 +228,12 @@ msgstr "" 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 diff --git a/sale_project_task_recurrency/models/product_template.py b/sale_project_task_recurrency/models/product_template.py index 40e3e3c28e..736f0b4ab8 100644 --- a/sale_project_task_recurrency/models/product_template.py +++ b/sale_project_task_recurrency/models/product_template.py @@ -13,6 +13,8 @@ class ProductTemplate(models.Model): ("day", "Days"), ("week", "Weeks"), ("month", "Months"), + ("quarter", "Quarters"), + ("semester", "Semesters"), ("year", "Years"), ], default="week", @@ -73,6 +75,27 @@ class ProductTemplate(models.Model): ], "Force Month", ) + task_force_month_quarter = fields.Selection( + [ + ("1", "First month"), + ("2", "Second month"), + ("3", "Third month"), + ], + "Force Month", + 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", + help="Force the month to be used inside the semester", + ) @api.onchange("service_tracking") def _onchange_service_tracking(self): diff --git a/sale_project_task_recurrency/models/sale_order_line.py b/sale_project_task_recurrency/models/sale_order_line.py index 872ff061bb..79a770efbf 100644 --- a/sale_project_task_recurrency/models/sale_order_line.py +++ b/sale_project_task_recurrency/models/sale_order_line.py @@ -7,6 +7,13 @@ 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" @@ -19,11 +26,21 @@ def _timesheet_create_task_prepare_values(self, project): 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": self.product_id.task_repeat_interval, - "repeat_unit": self.product_id.task_repeat_unit, + "repeat_interval": repeat_interval, + "repeat_unit": repeat_unit, "repeat_type": repeat_type, "recurring_task": True, "date_deadline": date_deadline, @@ -35,35 +52,54 @@ def _timesheet_create_task_prepare_values(self, project): def _get_task_date_deadline(self): self.ensure_one() product = self.product_id - force_month = int(product.task_force_month) if product.task_force_month else 0 + date_now = fields.Datetime.context_timestamp(self, datetime.now()) task_start_date_method = product.task_start_date_method - date_deadline_tz = fields.Datetime.context_timestamp( - self, datetime.now() - ) + relativedelta(hour=12, minute=0, second=0) + # 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["task_force_month_%s" % 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 ( - force_month - and product.task_repeat_unit == "year" + forced_month + and product.task_repeat_unit in ["quarter", "semester", "year"] and task_start_date_method == "current_date" ): - date_deadline_tz += relativedelta(month=force_month) - date_deadline = date_deadline_tz.astimezone(pytz.UTC).replace(tzinfo=None) + date_deadline += relativedelta(month=forced_month) if ( - product.task_repeat_unit in ["month", "year"] + product.task_repeat_unit in ["month", "quarter", "semester", "year"] and task_start_date_method != "current_date" ): - if product.task_repeat_unit == "month": - date_deadline += relativedelta(day=1) - if "_next" in task_start_date_method: - date_deadline += relativedelta(months=product.task_repeat_interval) - if "end_" in task_start_date_method: + 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) - else: - date_deadline += relativedelta(month=force_month or 1, day=1) - if "_next" in task_start_date_method: - date_deadline += relativedelta(years=product.task_repeat_interval) - if "end_" in task_start_date_method: - date_deadline += relativedelta(month=force_month or 12, day=31) - return date_deadline + return date_deadline.astimezone(pytz.UTC).replace(tzinfo=None) def _get_task_repeat_until(self, date_start): self.ensure_one() diff --git a/sale_project_task_recurrency/static/description/index.html b/sale_project_task_recurrency/static/description/index.html index 8d9f7b642f..c1bdcb042e 100644 --- a/sale_project_task_recurrency/static/description/index.html +++ b/sale_project_task_recurrency/static/description/index.html @@ -367,7 +367,7 @@

Sale project task recurrency

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -!! source digest: sha256:333fe55af0b7c235786aba0f66d75dfd763960781b34f0701bf3548783f2793e +!! source digest: sha256:d5da2188db2ac87ba6c4ebb660e25aa1e53a534b122f01791f97e48ae666a311 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

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

This module allows configuring the recurrence of tasks created from diff --git a/sale_project_task_recurrency/tests/test_product_task_recurrency.py b/sale_project_task_recurrency/tests/test_product_task_recurrency.py index c039a330fb..0903aca092 100644 --- a/sale_project_task_recurrency/tests/test_product_task_recurrency.py +++ b/sale_project_task_recurrency/tests/test_product_task_recurrency.py @@ -328,6 +328,188 @@ def test_task_recurrency_3_month(self): 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): @@ -747,3 +929,215 @@ def test_task_recurrency_year_force_month(self): 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") + ) diff --git a/sale_project_task_recurrency/views/product_template_views.xml b/sale_project_task_recurrency/views/product_template_views.xml index dc48fcf83b..bf8ba92323 100644 --- a/sale_project_task_recurrency/views/product_template_views.xml +++ b/sale_project_task_recurrency/views/product_template_views.xml @@ -59,7 +59,7 @@ @@ -68,6 +68,16 @@ invisible="task_repeat_unit != 'year'" groups="project.group_project_recurring_tasks" /> + + From dbeb09c627f3da5db9e9ce9775484ada1205d61a Mon Sep 17 00:00:00 2001 From: Bhavesh Heliconia Date: Fri, 20 Dec 2024 18:30:54 +0530 Subject: [PATCH 08/11] [IMP] sale_project_task_recurrency: pre-commit auto fixes --- sale_project_task_recurrency/models/sale_order_line.py | 2 +- sale_project_task_recurrency/views/product_template_views.xml | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/sale_project_task_recurrency/models/sale_order_line.py b/sale_project_task_recurrency/models/sale_order_line.py index 79a770efbf..5fa6fdeee0 100644 --- a/sale_project_task_recurrency/models/sale_order_line.py +++ b/sale_project_task_recurrency/models/sale_order_line.py @@ -59,7 +59,7 @@ def _get_task_date_deadline(self): forced_month = int(product.task_force_month or 0) if product.task_repeat_unit in ["quarter", "semester"]: forced_month = int( - product["task_force_month_%s" % product.task_repeat_unit] or 0 + 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 diff --git a/sale_project_task_recurrency/views/product_template_views.xml b/sale_project_task_recurrency/views/product_template_views.xml index bf8ba92323..83c3e0b837 100644 --- a/sale_project_task_recurrency/views/product_template_views.xml +++ b/sale_project_task_recurrency/views/product_template_views.xml @@ -1,6 +1,5 @@ - view.product.template.form @@ -81,5 +80,4 @@ - From a4a1f983c3ba46d7be71a0974dbdbb2cd2f1ddd8 Mon Sep 17 00:00:00 2001 From: Bhavesh Heliconia Date: Fri, 20 Dec 2024 18:32:30 +0530 Subject: [PATCH 09/11] [MIG] sale_project_task_recurrency: Migration to 18.0 --- sale_project_task_recurrency/README.rst | 20 ++- sale_project_task_recurrency/__manifest__.py | 2 +- .../i18n/sale_project_task_recurrency.pot | 16 +- .../models/product_template.py | 4 +- .../readme/CONTRIBUTORS.md | 3 +- .../static/description/index.html | 35 +++-- .../tests/test_product_task_recurrency.py | 143 ++++++++++++++++++ 7 files changed, 194 insertions(+), 29 deletions(-) diff --git a/sale_project_task_recurrency/README.rst b/sale_project_task_recurrency/README.rst index 88f0d2d95c..ed967fc6f3 100644 --- a/sale_project_task_recurrency/README.rst +++ b/sale_project_task_recurrency/README.rst @@ -1,3 +1,7 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + ============================ Sale project task recurrency ============================ @@ -7,23 +11,23 @@ Sale project task recurrency !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:d5da2188db2ac87ba6c4ebb660e25aa1e53a534b122f01791f97e48ae666a311 + !! source digest: sha256:a8fb6261560d37413404425eeba52efac805eaa92c242f35812c73074c490774 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |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 +.. |badge2| image:: https://img.shields.io/badge/license-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/17.0/sale_project_task_recurrency + :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-17-0/project-17-0-sale_project_task_recurrency + :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=17.0 + :target: https://runboat.odoo-community.org/builds?repo=OCA/project&target_branch=18.0 :alt: Try me on Runboat |badge1| |badge2| |badge3| |badge4| |badge5| @@ -66,7 +70,7 @@ Bug Tracker Bugs are tracked on `GitHub Issues `_. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed -`feedback `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -86,6 +90,8 @@ Contributors - Pedro M. Baeza - Carlos López +- ``Heliconia Solutions Pvt. Ltd. ``\ \_ + Maintainers ----------- @@ -99,6 +105,6 @@ OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use. -This module is part of the `OCA/project `_ project on GitHub. +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/__manifest__.py b/sale_project_task_recurrency/__manifest__.py index b616bef5f5..562a0c8c4d 100644 --- a/sale_project_task_recurrency/__manifest__.py +++ b/sale_project_task_recurrency/__manifest__.py @@ -2,7 +2,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). { "name": "Sale project task recurrency", - "version": "17.0.1.2.0", + "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", diff --git a/sale_project_task_recurrency/i18n/sale_project_task_recurrency.pot b/sale_project_task_recurrency/i18n/sale_project_task_recurrency.pot index 52456869e8..d52849cb94 100644 --- a/sale_project_task_recurrency/i18n/sale_project_task_recurrency.pot +++ b/sale_project_task_recurrency/i18n/sale_project_task_recurrency.pot @@ -4,7 +4,7 @@ # msgid "" msgstr "" -"Project-Id-Version: Odoo Server 17.0\n" +"Project-Id-Version: Odoo Server 18.0\n" "Report-Msgid-Bugs-To: \n" "Last-Translator: \n" "Language-Team: \n" @@ -78,12 +78,20 @@ 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 +msgid "Force Month" +msgstr "" + +#. module: sale_project_task_recurrency +#: 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_template__task_force_month_quarter +msgid "Force Month Quarter" +msgstr "" + +#. module: sale_project_task_recurrency +#: 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_semester -msgid "Force Month" +msgid "Force Month Semester" msgstr "" #. module: sale_project_task_recurrency diff --git a/sale_project_task_recurrency/models/product_template.py b/sale_project_task_recurrency/models/product_template.py index 736f0b4ab8..4cb103a743 100644 --- a/sale_project_task_recurrency/models/product_template.py +++ b/sale_project_task_recurrency/models/product_template.py @@ -81,7 +81,7 @@ class ProductTemplate(models.Model): ("2", "Second month"), ("3", "Third month"), ], - "Force Month", + "Force Month Quarter", help="Force the month to be used inside the quarter", ) task_force_month_semester = fields.Selection( @@ -93,7 +93,7 @@ class ProductTemplate(models.Model): ("5", "Fifth month"), ("6", "Sixth month"), ], - "Force Month", + "Force Month Semester", help="Force the month to be used inside the semester", ) diff --git a/sale_project_task_recurrency/readme/CONTRIBUTORS.md b/sale_project_task_recurrency/readme/CONTRIBUTORS.md index 76723133dd..01b2b70185 100644 --- a/sale_project_task_recurrency/readme/CONTRIBUTORS.md +++ b/sale_project_task_recurrency/readme/CONTRIBUTORS.md @@ -1,3 +1,4 @@ - [Tecnativa](https://www.tecnativa.com): - Pedro M. Baeza - - Carlos López \ No newline at end of file + - Carlos López +- `Heliconia Solutions Pvt. Ltd. `_ diff --git a/sale_project_task_recurrency/static/description/index.html b/sale_project_task_recurrency/static/description/index.html index c1bdcb042e..a041c659f5 100644 --- a/sale_project_task_recurrency/static/description/index.html +++ b/sale_project_task_recurrency/static/description/index.html @@ -3,7 +3,7 @@ -Sale project task recurrency +README.rst -

-

Sale project task recurrency

+
+ + +Odoo Community Association + +
+

Sale project task recurrency

-

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

+

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

@@ -387,7 +392,7 @@

Sale project task recurrency

-

Configuration

+

Configuration

  • Go to Project > Configuration > Settings.
  • In the Tasks Management section, enable the Recurring Tasks @@ -403,7 +408,7 @@

    Configuration

-

Usage

+

Usage

  • Go to Sales > Orders > Quotations.
  • Create a new sales order and select the product created earlier.
  • @@ -414,33 +419,34 @@

    Usage

-

Bug Tracker

+

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.

+feedback.

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

-

Credits

+

Credits

-

Authors

+

Authors

  • Tecnativa
-

Contributors

+

Contributors

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

Maintainers

+

Maintainers

This module is maintained by the OCA.

Odoo Community Association @@ -448,10 +454,11 @@

Maintainers

OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.

-

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

+

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/test_product_task_recurrency.py b/sale_project_task_recurrency/tests/test_product_task_recurrency.py index 0903aca092..a7246212d8 100644 --- a/sale_project_task_recurrency/tests/test_product_task_recurrency.py +++ b/sale_project_task_recurrency/tests/test_product_task_recurrency.py @@ -1141,3 +1141,146 @@ def test_task_recurrency_semester_force_month(self): 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) From 0a3b063998963d9fc1e537e0a43aa327ce9d6e03 Mon Sep 17 00:00:00 2001 From: mymage Date: Sat, 2 Aug 2025 13:49:19 +0000 Subject: [PATCH 10/11] Translated using Weblate (Italian) Currently translated at 100.0% (45 of 45 strings) Translation: project-18.0/project-18.0-sale_project_task_recurrency Translate-URL: https://translation.odoo-community.org/projects/project-18-0/project-18-0-sale_project_task_recurrency/it/ --- sale_project_task_recurrency/i18n/it.po | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/sale_project_task_recurrency/i18n/it.po b/sale_project_task_recurrency/i18n/it.po index a725503303..e4e52c51b9 100644 --- a/sale_project_task_recurrency/i18n/it.po +++ b/sale_project_task_recurrency/i18n/it.po @@ -6,7 +6,7 @@ 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" +"PO-Revision-Date: 2025-08-02 14:55+0000\n" "Last-Translator: mymage \n" "Language-Team: none\n" "Language: it\n" @@ -14,7 +14,7 @@ msgstr "" "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" +"X-Generator: Weblate 5.10.4\n" #. module: sale_project_task_recurrency #: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_product__task_repeat_number @@ -71,13 +71,13 @@ 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 "" +msgstr "Quinto mese" #. 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 "" +msgstr "Primo mese" #. module: sale_project_task_recurrency #: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_product__task_force_month @@ -93,13 +93,13 @@ msgstr "Forza mese" #: 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 "" +msgstr "Forze il mese da utilizzare nel trimestre" #. 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 "" +msgstr "Forse il mese da utilizzare nel semestre" #. module: sale_project_task_recurrency #: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_repeat_type__forever @@ -109,7 +109,7 @@ 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 "" +msgstr "Quarto mese" #. module: sale_project_task_recurrency #: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_product__recurring_task @@ -170,7 +170,7 @@ 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 "" +msgstr "Trimestri" #. module: sale_project_task_recurrency #: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_repeat_type__repeat @@ -192,12 +192,12 @@ msgstr "Riga ordine di vendita" #: 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 "" +msgstr "Secondo mese" #. 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 "" +msgstr "Semestri" #. module: sale_project_task_recurrency #: model:ir.model.fields.selection,name:sale_project_task_recurrency.selection__product_template__task_force_month__9 @@ -207,7 +207,7 @@ 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 "" +msgstr "Sesto mese" #. module: sale_project_task_recurrency #: model:ir.model.fields,field_description:sale_project_task_recurrency.field_product_product__task_start_date_method @@ -235,7 +235,7 @@ msgstr "Unità ripetizione lavoro" #: 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 "" +msgstr "Terzo mese" #. module: sale_project_task_recurrency #: model:ir.model.fields,help:sale_project_task_recurrency.field_product_product__task_start_date_method From 974f024179685b48f26fcbeff85255ac6ea4bea7 Mon Sep 17 00:00:00 2001 From: Ruchir Shukla Date: Mon, 8 Dec 2025 16:02:26 +0530 Subject: [PATCH 11/11] [MIG] sale_project_task_recurrency: Migration to 19.0 --- sale_project_task_recurrency/README.rst | 13 +++++++------ sale_project_task_recurrency/__manifest__.py | 2 +- sale_project_task_recurrency/readme/CONFIGURE.md | 4 ++-- .../static/description/index.html | 9 +++++---- .../tests/test_product_task_recurrency.py | 1 - 5 files changed, 15 insertions(+), 14 deletions(-) diff --git a/sale_project_task_recurrency/README.rst b/sale_project_task_recurrency/README.rst index ed967fc6f3..bd414774db 100644 --- a/sale_project_task_recurrency/README.rst +++ b/sale_project_task_recurrency/README.rst @@ -21,13 +21,13 @@ Sale project task recurrency :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 + :target: https://github.com/OCA/project/tree/19.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 + :target: https://translation.odoo-community.org/projects/project-19-0/project-19-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 + :target: https://runboat.odoo-community.org/builds?repo=OCA/project&target_branch=19.0 :alt: Try me on Runboat |badge1| |badge2| |badge3| |badge4| |badge5| @@ -43,7 +43,8 @@ sales orders directly in the product form. Configuration ============= -- Go to *Project > Configuration > Settings*. +- Go to Project > Open an existing Project or Create a new one > + *Settings Tab* - In the *Tasks Management* section, enable the *Recurring Tasks* checkbox. - Go to *Sales > Products > Products*. @@ -70,7 +71,7 @@ Bug Tracker Bugs are tracked on `GitHub Issues `_. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed -`feedback `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -105,6 +106,6 @@ OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use. -This module is part of the `OCA/project `_ project on GitHub. +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/__manifest__.py b/sale_project_task_recurrency/__manifest__.py index 562a0c8c4d..fa5629b0ab 100644 --- a/sale_project_task_recurrency/__manifest__.py +++ b/sale_project_task_recurrency/__manifest__.py @@ -2,7 +2,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). { "name": "Sale project task recurrency", - "version": "18.0.1.0.0", + "version": "19.0.1.0.0", "summary": "Configuring Task Recurrence from the Product Form.", "author": "Tecnativa,Odoo Community Association (OCA)", "website": "https://github.com/OCA/project", diff --git a/sale_project_task_recurrency/readme/CONFIGURE.md b/sale_project_task_recurrency/readme/CONFIGURE.md index 5c0869e7e5..16d21fd333 100644 --- a/sale_project_task_recurrency/readme/CONFIGURE.md +++ b/sale_project_task_recurrency/readme/CONFIGURE.md @@ -1,8 +1,8 @@ -- Go to *Project > Configuration > Settings*. +- Go to Project > Open an existing Project or Create a new one > *Settings Tab* - 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 + - Fill in the recurrence fields. diff --git a/sale_project_task_recurrency/static/description/index.html b/sale_project_task_recurrency/static/description/index.html index a041c659f5..935243ebe9 100644 --- a/sale_project_task_recurrency/static/description/index.html +++ b/sale_project_task_recurrency/static/description/index.html @@ -374,7 +374,7 @@

Sale project task recurrency

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! source digest: sha256:a8fb6261560d37413404425eeba52efac805eaa92c242f35812c73074c490774 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

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

+

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

@@ -394,7 +394,8 @@

Sale project task recurrency

Configuration

    -
  • Go to Project > Configuration > Settings.
  • +
  • Go to Project > Open an existing Project or Create a new one > +Settings Tab
  • In the Tasks Management section, enable the Recurring Tasks checkbox.
  • Go to Sales > Products > Products.
  • @@ -423,7 +424,7 @@

    Bug Tracker

    Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed -feedback.

    +feedback.

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

@@ -454,7 +455,7 @@

Maintainers

OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.

-

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

+

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/test_product_task_recurrency.py b/sale_project_task_recurrency/tests/test_product_task_recurrency.py index a7246212d8..5131a50bc5 100644 --- a/sale_project_task_recurrency/tests/test_product_task_recurrency.py +++ b/sale_project_task_recurrency/tests/test_product_task_recurrency.py @@ -35,7 +35,6 @@ def setUpClass(cls): "name": "Service", "type": "service", "uom_id": cls.uom_hour.id, - "uom_po_id": cls.uom_hour.id, "service_type": "manual", "service_tracking": "no", }