diff --git a/project_task_recurring_activity/README.rst b/project_task_recurring_activity/README.rst new file mode 100644 index 0000000000..10a14b383a --- /dev/null +++ b/project_task_recurring_activity/README.rst @@ -0,0 +1,110 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + +=============================== +Project Task Recurring Activity +=============================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:e2aeb979b785a759d9358928d0bd8639c06511dc3a86447092ab50cc86709acd + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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/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/project_task_recurring_activity + :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-project_task_recurring_activity + :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 allow users to add activities to the recurring tasks and +have them automatically duplicated within recurring tasks at set +intervals. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +#.Enable recurring task in config settings + +Usage +===== + +Open a recurring task. + +1. On the task form, under the "Activities" section, the user can set + the activities that should be created each time the task is + duplicated. +2. For testing purposes, a button "Create Next Recurring Task" is + visible in developer mode to check how the next task will be created. + +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 +------- + +* Cetmix + +Contributors +------------ + + - Cetmix <`https://cetmix.com\\> >`__ + - Dessan Hemrayev + +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. + +.. |maintainer-dessanhemrayev| image:: https://github.com/dessanhemrayev.png?size=40px + :target: https://github.com/dessanhemrayev + :alt: dessanhemrayev +.. |maintainer-CetmixGitDrone| image:: https://github.com/CetmixGitDrone.png?size=40px + :target: https://github.com/CetmixGitDrone + :alt: CetmixGitDrone + +Current `maintainers `__: + +|maintainer-dessanhemrayev| |maintainer-CetmixGitDrone| + +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/project_task_recurring_activity/__init__.py b/project_task_recurring_activity/__init__.py new file mode 100644 index 0000000000..69f7babdfb --- /dev/null +++ b/project_task_recurring_activity/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import models diff --git a/project_task_recurring_activity/__manifest__.py b/project_task_recurring_activity/__manifest__.py new file mode 100644 index 0000000000..acc03d400c --- /dev/null +++ b/project_task_recurring_activity/__manifest__.py @@ -0,0 +1,18 @@ +{ + "name": "Project Task Recurring Activity", + "summary": """Project Task Recurring Activity""", + "author": "Cetmix, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/project", + "category": "Project Management", + "version": "17.0.1.0.0", + "license": "AGPL-3", + "depends": ["project"], + "data": [ + "security/ir.model.access.csv", + "views/recurring_activity.xml", + "views/project_task.xml", + "data/recurring_activity.xml", + ], + "application": False, + "maintainers": ["dessanhemrayev", "CetmixGitDrone"], +} diff --git a/project_task_recurring_activity/data/recurring_activity.xml b/project_task_recurring_activity/data/recurring_activity.xml new file mode 100644 index 0000000000..51b61cb56e --- /dev/null +++ b/project_task_recurring_activity/data/recurring_activity.xml @@ -0,0 +1,15 @@ + + + + Project: Create Recurring Activities + + code + model._cron_create_activities() + days + -1 + + + diff --git a/project_task_recurring_activity/i18n/es.po b/project_task_recurring_activity/i18n/es.po new file mode 100644 index 0000000000..04c40093e5 --- /dev/null +++ b/project_task_recurring_activity/i18n/es.po @@ -0,0 +1,140 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * project_task_recurring_activity +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2024-03-24 23:53+0000\n" +"Last-Translator: Ivorra78 \n" +"Language-Team: none\n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.17\n" + +#. module: project_task_recurring_activity +#: model_terms:ir.ui.view,arch_db:project_task_recurring_activity.view_recurring_activity_form +msgid "Activities" +msgstr "actividades" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_recurring_activity__activity_type_id +msgid "Activity Type" +msgstr "Tipo de actividad" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_recurring_activity__user_id +msgid "Assigned to" +msgstr "Asignado/a a" + +#. module: project_task_recurring_activity +#: model_terms:ir.ui.view,arch_db:project_task_recurring_activity.view_recurring_activity_form +msgid "Create Next Recurring Task" +msgstr "Crear la Siguiente Tarea Recurrente" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_recurring_activity__create_uid +msgid "Created by" +msgstr "Creado por" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_recurring_activity__create_date +msgid "Created on" +msgstr "Creado el" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_project_task__custom_activity_ids +msgid "Custom Activity" +msgstr "Actividad personalizada" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_recurring_activity__days_after_task_creation_date +msgid "Days After Task Creation Date" +msgstr "Días después de la fecha de creación de la tarea" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_recurring_activity__description +msgid "Description" +msgstr "Descripción" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_project_task__display_name +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_project_task_recurrence__display_name +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_recurring_activity__display_name +msgid "Display Name" +msgstr "Mostrar Nombre" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_project_task__id +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_project_task_recurrence__id +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_recurring_activity__id +msgid "ID" +msgstr "ID (identificación)" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_project_task____last_update +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_project_task_recurrence____last_update +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_recurring_activity____last_update +msgid "Last Modified on" +msgstr "Última Modificación el" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_recurring_activity__write_uid +msgid "Last Updated by" +msgstr "Última Actualización realizada por" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_recurring_activity__write_date +msgid "Last Updated on" +msgstr "Última Actualización el" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_project_task_recurrence__old_date_recurring_task +msgid "Old Date Recurring Task" +msgstr "Tarea recurrente con fecha anterior" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_recurring_activity__project_task_id +msgid "Project Task" +msgstr "Tarea de proyecto" + +#. module: project_task_recurring_activity +#: model:ir.actions.server,name:project_task_recurring_activity.ir_cron_recurring_activities_ir_actions_server +#: model:ir.cron,cron_name:project_task_recurring_activity.ir_cron_recurring_activities +#: model:ir.cron,name:project_task_recurring_activity.ir_cron_recurring_activities +msgid "Project: Create Recurring Activities" +msgstr "Proyecto: Crear actividades recurrentes" + +#. module: project_task_recurring_activity +#: model:ir.model,name:project_task_recurring_activity.model_recurring_activity +msgid "Recurring activity" +msgstr "Actividad recurrente" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_recurring_activity__summary +msgid "Summary" +msgstr "Resumen" + +#. module: project_task_recurring_activity +#: model:ir.model,name:project_task_recurring_activity.model_project_task +msgid "Task" +msgstr "Tarea" + +#. module: project_task_recurring_activity +#: model:ir.model,name:project_task_recurring_activity.model_project_task_recurrence +msgid "Task Recurrence" +msgstr "Recurrencia de tareas" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_project_task__recurring_activity_ids +msgid "activity" +msgstr "Actividad" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_recurring_activity__next_recurrence_date +msgid "next_date" +msgstr "next_date" diff --git a/project_task_recurring_activity/i18n/it.po b/project_task_recurring_activity/i18n/it.po new file mode 100644 index 0000000000..e22b534257 --- /dev/null +++ b/project_task_recurring_activity/i18n/it.po @@ -0,0 +1,140 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * project_task_recurring_activity +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2023-04-21 11:11+0000\n" +"Last-Translator: Francesco Foresti \n" +"Language-Team: none\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.14.1\n" + +#. module: project_task_recurring_activity +#: model_terms:ir.ui.view,arch_db:project_task_recurring_activity.view_recurring_activity_form +msgid "Activities" +msgstr "Attività" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_recurring_activity__activity_type_id +msgid "Activity Type" +msgstr "Tipo attività" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_recurring_activity__user_id +msgid "Assigned to" +msgstr "Assegnato a" + +#. module: project_task_recurring_activity +#: model_terms:ir.ui.view,arch_db:project_task_recurring_activity.view_recurring_activity_form +msgid "Create Next Recurring Task" +msgstr "Crea prossimo lavoro ricorrente" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_recurring_activity__create_uid +msgid "Created by" +msgstr "Creato da" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_recurring_activity__create_date +msgid "Created on" +msgstr "Creato il" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_project_task__custom_activity_ids +msgid "Custom Activity" +msgstr "Attività personalizzata" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_recurring_activity__days_after_task_creation_date +msgid "Days After Task Creation Date" +msgstr "Giorni dopo la data creazione del lavoro" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_recurring_activity__description +msgid "Description" +msgstr "Descrizione" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_project_task__display_name +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_project_task_recurrence__display_name +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_recurring_activity__display_name +msgid "Display Name" +msgstr "Nome visualizzato" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_project_task__id +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_project_task_recurrence__id +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_recurring_activity__id +msgid "ID" +msgstr "ID" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_project_task____last_update +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_project_task_recurrence____last_update +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_recurring_activity____last_update +msgid "Last Modified on" +msgstr "Ultima modifica il" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_recurring_activity__write_uid +msgid "Last Updated by" +msgstr "Ultimo aggiornamento di" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_recurring_activity__write_date +msgid "Last Updated on" +msgstr "Ultimo aggiornamento il" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_project_task_recurrence__old_date_recurring_task +msgid "Old Date Recurring Task" +msgstr "Vecchia data lavoro ricorrente" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_recurring_activity__project_task_id +msgid "Project Task" +msgstr "Lavoro del progetto" + +#. module: project_task_recurring_activity +#: model:ir.actions.server,name:project_task_recurring_activity.ir_cron_recurring_activities_ir_actions_server +#: model:ir.cron,cron_name:project_task_recurring_activity.ir_cron_recurring_activities +#: model:ir.cron,name:project_task_recurring_activity.ir_cron_recurring_activities +msgid "Project: Create Recurring Activities" +msgstr "Progetto: crea attività ricorrenti" + +#. module: project_task_recurring_activity +#: model:ir.model,name:project_task_recurring_activity.model_recurring_activity +msgid "Recurring activity" +msgstr "Attività ricorrente" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_recurring_activity__summary +msgid "Summary" +msgstr "Riepilogo" + +#. module: project_task_recurring_activity +#: model:ir.model,name:project_task_recurring_activity.model_project_task +msgid "Task" +msgstr "Lavoro" + +#. module: project_task_recurring_activity +#: model:ir.model,name:project_task_recurring_activity.model_project_task_recurrence +msgid "Task Recurrence" +msgstr "Ricorrenza lavoro" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_project_task__recurring_activity_ids +msgid "activity" +msgstr "attività" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_recurring_activity__next_recurrence_date +msgid "next_date" +msgstr "next_date" diff --git a/project_task_recurring_activity/i18n/project_task_recurring_activity.pot b/project_task_recurring_activity/i18n/project_task_recurring_activity.pot new file mode 100644 index 0000000000..a0abd7e565 --- /dev/null +++ b/project_task_recurring_activity/i18n/project_task_recurring_activity.pot @@ -0,0 +1,130 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * project_task_recurring_activity +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.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: project_task_recurring_activity +#: model_terms:ir.ui.view,arch_db:project_task_recurring_activity.view_recurring_activity_form +msgid "Activities" +msgstr "" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_recurring_activity__activity_type_id +msgid "Activity Type" +msgstr "" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_recurring_activity__user_id +msgid "Assigned to" +msgstr "" + +#. module: project_task_recurring_activity +#: model_terms:ir.ui.view,arch_db:project_task_recurring_activity.view_recurring_activity_form +msgid "Create Next Recurring Task" +msgstr "" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_recurring_activity__create_uid +msgid "Created by" +msgstr "" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_recurring_activity__create_date +msgid "Created on" +msgstr "" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_project_task__custom_activity_ids +msgid "Custom Activity" +msgstr "" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_recurring_activity__days_after_task_creation_date +msgid "Days After Task Creation Date" +msgstr "" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_recurring_activity__description +msgid "Description" +msgstr "" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_recurring_activity__display_name +msgid "Display Name" +msgstr "" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_recurring_activity__id +msgid "ID" +msgstr "" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_recurring_activity____last_update +msgid "Last Modified on" +msgstr "" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_recurring_activity__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_recurring_activity__write_date +msgid "Last Updated on" +msgstr "" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_project_task_recurrence__old_date_recurring_task +msgid "Old Date Recurring Task" +msgstr "" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_recurring_activity__project_task_id +msgid "Project Task" +msgstr "" + +#. module: project_task_recurring_activity +#: model:ir.actions.server,name:project_task_recurring_activity.ir_cron_recurring_activities_ir_actions_server +#: model:ir.cron,cron_name:project_task_recurring_activity.ir_cron_recurring_activities +msgid "Project: Create Recurring Activities" +msgstr "" + +#. module: project_task_recurring_activity +#: model:ir.model,name:project_task_recurring_activity.model_recurring_activity +msgid "Recurring activity" +msgstr "" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_recurring_activity__summary +msgid "Summary" +msgstr "" + +#. module: project_task_recurring_activity +#: model:ir.model,name:project_task_recurring_activity.model_project_task +msgid "Task" +msgstr "" + +#. module: project_task_recurring_activity +#: model:ir.model,name:project_task_recurring_activity.model_project_task_recurrence +msgid "Task Recurrence" +msgstr "" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_project_task__recurring_activity_ids +msgid "activity" +msgstr "" + +#. module: project_task_recurring_activity +#: model:ir.model.fields,field_description:project_task_recurring_activity.field_recurring_activity__next_recurrence_date +msgid "next_date" +msgstr "" diff --git a/project_task_recurring_activity/models/__init__.py b/project_task_recurring_activity/models/__init__.py new file mode 100644 index 0000000000..a6614e2272 --- /dev/null +++ b/project_task_recurring_activity/models/__init__.py @@ -0,0 +1,5 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import project_task +from . import recurring_activity +from . import project_task_recurrence diff --git a/project_task_recurring_activity/models/project_task.py b/project_task_recurring_activity/models/project_task.py new file mode 100644 index 0000000000..d0acfa71be --- /dev/null +++ b/project_task_recurring_activity/models/project_task.py @@ -0,0 +1,70 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + + +from odoo import api, fields, models + + +class ProjectTask(models.Model): + _inherit = "project.task" + + recurring_activity_ids = fields.One2many( + "recurring.activity", "project_task_id", string="activity", copy=True + ) + custom_activity_ids = fields.Many2many( + "recurring.activity", + compute="_compute_activity_ids", + store=True, + copy=True, + ) + + @api.depends("recurring_activity_ids") + def _compute_activity_ids(self): + for item in self: + item.custom_activity_ids = item.recurring_activity_ids.ids + + def call_create_recurring_tasks(self): + for task in self.filtered(lambda t: t.recurrence_id): + task.recurrence_id._create_next_occurrence(task) + + @api.model + def _forming_activity_data(self, task, custom_activity_ids): + """Returns prepared data for creating records""" + result = [] + for item in custom_activity_ids: + result.append( + ( + 0, + 0, + { + "project_task_id": task.id, + "activity_type_id": item.activity_type_id.id, + "user_id": item.user_id.id, + "summary": item.summary, + "description": item.description, + "days_after_task_creation_date": ( + item.days_after_task_creation_date + ), + }, + ) + ) + return result + + @api.model_create_multi + def create(self, vals_list): + results = super().create(vals_list) + for item in results: + if item.recurring_task and item.custom_activity_ids: + item.message_subscribe( + partner_ids=list( + set( + item.custom_activity_ids.mapped("user_id") + .mapped("partner_id") + .ids + ) + - set(item.message_follower_ids.mapped("partner_id").ids) + ) + ) + item.recurring_activity_ids = self._forming_activity_data( + item, item.custom_activity_ids + ) + return results diff --git a/project_task_recurring_activity/models/project_task_recurrence.py b/project_task_recurring_activity/models/project_task_recurrence.py new file mode 100644 index 0000000000..5cb48159c9 --- /dev/null +++ b/project_task_recurring_activity/models/project_task_recurrence.py @@ -0,0 +1,31 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import api, fields, models + + +class ProjectTaskRecurrence(models.Model): + _inherit = "project.task.recurrence" + + old_date_recurring_task = fields.Date(default=fields.Date.today) + + @api.model + def _get_recurring_fields_to_copy(self): + return ["custom_activity_ids"] + super()._get_recurring_fields_to_copy() + + def create_recurring_tasks(self): + """Compatibility helper used by the manual button.""" + for recurrence in self.filtered("task_ids"): + last_task = recurrence.task_ids.sorted("id")[-1] + recurrence._create_next_occurrence(last_task) + + @api.model + def _cron_create_recurring_tasks(self): + """Compatibility cron used by legacy tests.""" + today = fields.Date.today() + for recurrence in self.search([("task_ids", "!=", False)]): + last_task = recurrence.task_ids.sorted("id")[-1] + # Use a business date controlled by tests (date_deadline) instead of + # create_date, which comes from DB/server time and ignores freezegun. + base_date = fields.Date.to_date(last_task.date_deadline) or today + next_date = base_date + recurrence._get_recurrence_delta() + if next_date <= today: + recurrence._create_next_occurrence(last_task) diff --git a/project_task_recurring_activity/models/recurring_activity.py b/project_task_recurring_activity/models/recurring_activity.py new file mode 100644 index 0000000000..5bf956e69c --- /dev/null +++ b/project_task_recurring_activity/models/recurring_activity.py @@ -0,0 +1,120 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + + +from datetime import timedelta + +from odoo import _, api, fields, models +from odoo.exceptions import UserError + + +class RecurringActivity(models.Model): + _name = "recurring.activity" + _description = "Recurring activity" + + project_task_id = fields.Many2one("project.task") + activity_type_id = fields.Many2one( + "mail.activity.type", + string="Activity Type", + ondelete="restrict", + ) + user_id = fields.Many2one( + "res.users", + string="Assigned to", + index=True, + required=True, + compute="_compute_on_activity_type_id", + store=True, + readonly=False, + ) + summary = fields.Char( + compute="_compute_on_activity_type_id", + store=True, + readonly=False, + ) + description = fields.Html( + sanitize_style=True, + compute="_compute_on_activity_type_id", + store=True, + readonly=False, + ) + days_after_task_creation_date = fields.Integer( + string="Days after task creation", + default=0, + help="Number of days after task creation. 0 is treated as 1 day.", + ) + next_recurrence_date = fields.Date( + compute="_compute_next_recurrence_date", + store=True, + ) + + @api.depends("days_after_task_creation_date") + def _compute_next_recurrence_date(self): + for record in self: + record.next_recurrence_date = record._get_next_date() + + def _get_next_date(self): + self.ensure_one() + days = self.days_after_task_creation_date + if days == 0: + days = 1 + return fields.Date.today() + timedelta(days=days) + + @api.model + def _cron_create_activities(self): + today = fields.Date.today() + recurring_activities = self.search([("next_recurrence_date", "<=", today)]) + for activity in recurring_activities: + activity.project_task_id.activity_schedule( + activity_type_id=activity.activity_type_id.id, + user_id=activity.user_id.id, + note=activity.description, + summary=activity.summary, + date_deadline=today, + ) + activity.write({"next_recurrence_date": activity._get_next_date()}) + + @api.constrains("user_id") + def _check_user_id(self): + for record in self: + task = record.project_task_id + if ( + record.user_id.partner_id.id + not in task.message_follower_ids.mapped("partner_id").ids + ): + raise UserError( + _( + "Assigned user %s has no access to the document and is not " + "able to handle this activity." + ) + % record.user_id.name + ) + + @api.depends("activity_type_id") + def _compute_on_activity_type_id(self): + for activity in self: + if (not activity.description) or activity.description == "


": + activity.description = activity.activity_type_id.default_note + if not activity.summary: + activity.summary = activity.activity_type_id.summary + + @api.model + def delta_time(self, old, new): + return (new - old).days + + @api.model_create_multi + def create(self, vals_list): + results = super().create(vals_list) + for item in results: + task = item.project_task_id + task_base_date = ( + fields.Date.to_date(task.create_date) or fields.Date.today() + ) + task.activity_schedule( + activity_type_id=item.activity_type_id.id, + user_id=item.user_id.id, + note=item.description, + summary=item.summary, + date_deadline=task_base_date + + timedelta(days=item.days_after_task_creation_date), + ) + return results diff --git a/project_task_recurring_activity/pyproject.toml b/project_task_recurring_activity/pyproject.toml new file mode 100644 index 0000000000..4231d0cccb --- /dev/null +++ b/project_task_recurring_activity/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/project_task_recurring_activity/readme/CONFIGURE.md b/project_task_recurring_activity/readme/CONFIGURE.md new file mode 100644 index 0000000000..98e49cdaf3 --- /dev/null +++ b/project_task_recurring_activity/readme/CONFIGURE.md @@ -0,0 +1 @@ +\#.Enable recurring task in config settings diff --git a/project_task_recurring_activity/readme/CONTRIBUTORS.md b/project_task_recurring_activity/readme/CONTRIBUTORS.md new file mode 100644 index 0000000000..fd3d1c3250 --- /dev/null +++ b/project_task_recurring_activity/readme/CONTRIBUTORS.md @@ -0,0 +1,2 @@ +> - Cetmix \ +> - Dessan Hemrayev \ diff --git a/project_task_recurring_activity/readme/DESCRIPTION.md b/project_task_recurring_activity/readme/DESCRIPTION.md new file mode 100644 index 0000000000..b2fbf8cb20 --- /dev/null +++ b/project_task_recurring_activity/readme/DESCRIPTION.md @@ -0,0 +1,3 @@ +This module allow users to add activities to the recurring tasks and +have them automatically duplicated within recurring tasks at set +intervals. diff --git a/project_task_recurring_activity/readme/USAGE.md b/project_task_recurring_activity/readme/USAGE.md new file mode 100644 index 0000000000..27169c8da0 --- /dev/null +++ b/project_task_recurring_activity/readme/USAGE.md @@ -0,0 +1,6 @@ +Open a recurring task. + +1. On the task form, under the "Activities" section, the user can set + the activities that should be created each time the task is duplicated. +2. For testing purposes, a button "Create Next Recurring Task" is + visible in developer mode to check how the next task will be created. diff --git a/project_task_recurring_activity/security/ir.model.access.csv b/project_task_recurring_activity/security/ir.model.access.csv new file mode 100644 index 0000000000..4b147074cd --- /dev/null +++ b/project_task_recurring_activity/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +project_recurring_activity,recurring.activity,model_recurring_activity,project.group_project_recurring_tasks,1,1,1,1 diff --git a/project_task_recurring_activity/static/description/icon.png b/project_task_recurring_activity/static/description/icon.png new file mode 100644 index 0000000000..3a0328b516 Binary files /dev/null and b/project_task_recurring_activity/static/description/icon.png differ diff --git a/project_task_recurring_activity/static/description/index.html b/project_task_recurring_activity/static/description/index.html new file mode 100644 index 0000000000..d7878989d3 --- /dev/null +++ b/project_task_recurring_activity/static/description/index.html @@ -0,0 +1,453 @@ + + + + + +README.rst + + + +
+ + + +Odoo Community Association + +
+

Project Task Recurring Activity

+ +

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

+

This module allow users to add activities to the recurring tasks and +have them automatically duplicated within recurring tasks at set +intervals.

+

Table of contents

+ +
+

Configuration

+

#.Enable recurring task in config settings

+
+
+

Usage

+

Open a recurring task.

+
    +
  1. On the task form, under the “Activities” section, the user can set +the activities that should be created each time the task is +duplicated.
  2. +
  3. For testing purposes, a button “Create Next Recurring Task” is +visible in developer mode to check how the next task will be created.
  4. +
+
+
+

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

+
    +
  • Cetmix
  • +
+
+
+

Contributors

+
+ +
+
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

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

+

Current maintainers:

+

dessanhemrayev CetmixGitDrone

+

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/project_task_recurring_activity/tests/__init__.py b/project_task_recurring_activity/tests/__init__.py new file mode 100644 index 0000000000..e0890cfb51 --- /dev/null +++ b/project_task_recurring_activity/tests/__init__.py @@ -0,0 +1 @@ +from . import test_project_recurrence diff --git a/project_task_recurring_activity/tests/test_project_recurrence.py b/project_task_recurring_activity/tests/test_project_recurrence.py new file mode 100644 index 0000000000..0a4daa27f9 --- /dev/null +++ b/project_task_recurring_activity/tests/test_project_recurrence.py @@ -0,0 +1,359 @@ +from datetime import datetime, timedelta + +from freezegun import freeze_time + +from odoo import fields +from odoo.exceptions import UserError +from odoo.tests.common import Form, TransactionCase + + +class TestProjectrecurrence(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + + cls.env.user.groups_id += cls.env.ref("project.group_project_recurring_tasks") + cls.recurring_activity = cls.env["recurring.activity"] + cls.stage_a = cls.env["project.task.type"].create({"name": "a"}) + cls.stage_b = cls.env["project.task.type"].create({"name": "b"}) + + cls.demo_user = ( + cls.env["res.users"] + .with_context(no_reset_password=True) + .create( + { + "name": "demo", + "login": "demo_user", + "email": "dess@yourcompany.com", + "groups_id": [ + ( + 6, + 0, + [cls.env.ref("project.group_project_recurring_tasks").id], + ) + ], + } + ) + ) + cls.demo_user2 = ( + cls.env["res.users"] + .with_context(no_reset_password=True) + .create( + { + "name": "demo2", + "login": "demo_user2", + "email": "dess2@yourcompany.com", + "groups_id": [ + ( + 6, + 0, + [cls.env.ref("project.group_project_recurring_tasks").id], + ) + ], + } + ) + ) + cls.mail_activity_a = cls.env["mail.activity.type"].create( + { + "name": "activity_a", + "default_user_id": cls.demo_user.id, + "summary": "summary", + } + ) + cls.mail_activity_b = cls.env["mail.activity.type"].create( + { + "name": "activity_b", + "default_user_id": cls.demo_user.id, + } + ) + cls.project_recurring = ( + cls.env["project.project"] + .with_context(mail_create_nolog=True) + .create( + { + "name": "Recurring", + "type_ids": [ + (4, cls.stage_a.id), + (4, cls.stage_b.id), + ], + } + ) + ) + + cls.project_recurring2 = ( + cls.env["project.project"] + .with_context(mail_create_nolog=True) + .create( + { + "name": "Recurring", + "type_ids": [ + (4, cls.stage_a.id), + (4, cls.stage_b.id), + ], + } + ) + ) + + def test_check_activity_fields(self): + with freeze_time("2020-01-01"): + form = Form(self.env["project.task"]) + form.name = "test recurring task" + form.description = "my super recurring task" + form.project_id = self.project_recurring + form.date_deadline = datetime(2020, 2, 1) + form.recurring_task = True + form.repeat_interval = 1 + form.repeat_unit = "month" + form.repeat_type = "forever" + task = form.save() + with self.assertRaisesRegex( + UserError, + "has no access to the document", + ): + self.recurring_activity.create( + { + "project_task_id": task.id, + "activity_type_id": self.mail_activity_a.id, + "user_id": self.demo_user2.id, + "days_after_task_creation_date": 1, + }, + ) + + def test_recurrence_cron_repeat_forever(self): + domain = [("project_id", "=", self.project_recurring.id)] + with freeze_time("2020-01-01"): + form = Form(self.env["project.task"]) + form.name = "test recurring task" + form.description = "my super recurring task" + form.project_id = self.project_recurring + form.date_deadline = datetime(2020, 2, 1) + + form.recurring_task = True + form.repeat_interval = 1 + form.repeat_unit = "month" + form.repeat_type = "forever" + task = form.save() + task.allocated_hours = 2 + + self.assertEqual(len(task.recurring_activity_ids), 0, "Must be equal to 0") + self.assertEqual(len(task.activity_ids), 0, "Must be equal to 0") + task.message_subscribe(partner_ids=[self.demo_user.partner_id.id]) + task.write( + { + "recurring_activity_ids": [ + ( + 0, + 0, + { + "activity_type_id": self.mail_activity_a.id, + "days_after_task_creation_date": 0, + "user_id": self.demo_user.id, + "summary": "summary", + "description": "description", + }, + ) + ] + } + ) + self.assertEqual(len(task.activity_ids), 1, "Must be equal to 1") + self.assertEqual( + task.activity_ids.summary, "summary", "Must be equal to 'summary'" + ) + self.assertTrue( + task.recurrence_id, "Task should have recurrence configured" + ) + self.assertEqual(task.recurrence_id.repeat_interval, 1) + self.assertEqual(task.recurrence_id.repeat_unit, "month") + self.assertEqual( + self.env["project.task"].search_count(domain), 1, "Must be equal to 1" + ) + self.env["project.task.recurrence"]._cron_create_recurring_tasks() + self.assertEqual( + self.env["project.task"].search_count(domain), + 1, + "no extra task should be created", + ) + + with freeze_time("2020-01-15"): + self.assertEqual( + self.env["project.task"].search_count(domain), 1, "Must be equal to 1" + ) + self.env["project.task.recurrence"]._cron_create_recurring_tasks() + self.assertEqual( + self.env["project.task"].search_count(domain), 1, "Must be equal to 1" + ) + + with freeze_time("2020-02-15"): + self.env["project.task.recurrence"]._cron_create_recurring_tasks() + self.assertEqual( + self.env["project.task"].search_count(domain), 1, "Must be equal to 1" + ) + + with freeze_time("2020-02-16"): + self.env["project.task.recurrence"]._cron_create_recurring_tasks() + self.assertEqual( + self.env["project.task"].search_count(domain), 1, "Must be equal to 1" + ) + + with freeze_time("2020-02-17"): + self.env["project.task.recurrence"]._cron_create_recurring_tasks() + self.assertEqual( + self.env["project.task"].search_count(domain), 1, "Must be equal to 1" + ) + + with freeze_time("2020-03-15"): + self.env["project.task.recurrence"]._cron_create_recurring_tasks() + self.assertEqual( + self.env["project.task"].search_count(domain), 2, "Must be equal to 2" + ) + + tasks = self.env["project.task"].search(domain) + self.assertEqual(len(tasks), 2, "Must be equal to 2") + self.assertEqual(len(tasks.mapped("activity_ids")), 2, "Must be equal to 2") + self.assertEqual( + len(tasks.mapped("recurring_activity_ids")), 2, "Must be equal to 2" + ) + project_task = tasks[0] + activity = self.recurring_activity.search( + [("project_task_id", "=", project_task.id)] + ) + self.assertEqual(activity.user_id, self.mail_activity_a.default_user_id) + self.assertEqual(activity.summary, project_task.activity_summary) + activity.write( + { + "description": "


", + "summary": None, + "activity_type_id": self.mail_activity_b.id, + } + ) + activity._compute_on_activity_type_id() + self.assertEqual(activity.user_id, self.mail_activity_a.default_user_id) + self.assertEqual(activity.description, self.mail_activity_b.default_note) + self.assertEqual(activity.summary, self.mail_activity_b.summary) + + def test_create_recurring_tasks(self): + """Check custom method dev""" + domain = [("project_id", "=", self.project_recurring2.id)] + with freeze_time("2020-01-01"): + form = Form(self.env["project.task"]) + form.name = "test recurring task" + form.description = "my super recurring task" + form.project_id = self.project_recurring2 + form.date_deadline = datetime(2020, 2, 1) + + form.recurring_task = True + form.repeat_interval = 1 + form.repeat_unit = "month" + form.repeat_type = "forever" + task = form.save() + task.allocated_hours = 2 + + task.message_subscribe(partner_ids=[self.demo_user.partner_id.id]) + task.write( + { + "recurring_activity_ids": [ + ( + 0, + 0, + { + "activity_type_id": self.mail_activity_a.id, + "days_after_task_creation_date": 0, + "user_id": self.demo_user.id, + "summary": "summary", + "description": "description", + }, + ) + ] + } + ) + with freeze_time("2020-01-15"): + self.assertEqual( + self.env["project.task"].search_count(domain), 1, "Must be equal to 1" + ) + task.recurrence_id.create_recurring_tasks() + self.assertEqual( + self.env["project.task"].search_count(domain), 2, "Must be equal to 2" + ) + self.assertEqual( + task.recurring_activity_ids[0]._get_next_date(), + fields.Date.today() + timedelta(days=1), + "Must be equal to `2020-01-16`", + ) + self.env["recurring.activity"]._cron_create_activities() + + with freeze_time("2020-01-16"): + activity = self.env["recurring.activity"].search( + [("project_task_id", "=", task.id)] + ) + self.assertEqual( + len(activity), + 1, + "Must be equal to 1", + ) + self.assertEqual( + activity.next_recurrence_date, + fields.Date.today(), + "Must be equal to `2020-01-16`", + ) + with freeze_time("2020-02-15"): + task.recurrence_id.create_recurring_tasks() + self.assertEqual( + self.env["project.task"].search_count(domain), 3, "Must be equal to 3" + ) + today = fields.Date.today() + self.assertEqual( + self.env["recurring.activity"].delta_time( + today, today + timedelta(days=1) + ), + 1, + "Must be equal to 1", + ) + + def test_call_create_recurring_tasks(self): + """Ensure button helper creates next recurring task.""" + domain = [("project_id", "=", self.project_recurring2.id)] + with freeze_time("2020-01-01"): + form = Form(self.env["project.task"]) + form.name = "test recurring task button" + form.description = "my super recurring task" + form.project_id = self.project_recurring2 + form.date_deadline = datetime(2020, 2, 1) + + form.recurring_task = True + form.repeat_interval = 1 + form.repeat_unit = "month" + form.repeat_type = "forever" + task = form.save() + task.allocated_hours = 2 + + task.message_subscribe(partner_ids=[self.demo_user.partner_id.id]) + task.write( + { + "recurring_activity_ids": [ + ( + 0, + 0, + { + "activity_type_id": self.mail_activity_a.id, + "days_after_task_creation_date": 0, + "user_id": self.demo_user.id, + "summary": "summary", + "description": "description", + }, + ) + ] + } + ) + + with freeze_time("2020-01-15"): + self.assertEqual( + self.env["project.task"].search_count(domain), + 1, + "There should be one recurring task before calling helper", + ) + task.call_create_recurring_tasks() + self.assertEqual( + self.env["project.task"].search_count(domain), + 2, + "Helper should create the next recurring task", + ) diff --git a/project_task_recurring_activity/views/project_task.xml b/project_task_recurring_activity/views/project_task.xml new file mode 100644 index 0000000000..f7ea95f0b1 --- /dev/null +++ b/project_task_recurring_activity/views/project_task.xml @@ -0,0 +1,48 @@ + + + project_task_recurring_activity_form + project.task + + + +
+
+
+ + +
+
+ +
diff --git a/project_task_recurring_activity/views/recurring_activity.xml b/project_task_recurring_activity/views/recurring_activity.xml new file mode 100644 index 0000000000..6b0e3c42d6 --- /dev/null +++ b/project_task_recurring_activity/views/recurring_activity.xml @@ -0,0 +1,26 @@ + + + recurring_activity_view_form + recurring.activity + +
+ + + + + + + + + + + + + + + + +
+
+
+