diff --git a/event_stage_cancelled/README.rst b/event_stage_cancelled/README.rst new file mode 100644 index 000000000..6f14e5fe8 --- /dev/null +++ b/event_stage_cancelled/README.rst @@ -0,0 +1,123 @@ +============================ +Event cancellation workflows +============================ + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:2c6facd843a69eb5970fe525b81d5e5364d69baa69bdb84e31580e41f5312782 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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%2Fevent-lightgray.png?logo=github + :target: https://github.com/OCA/event/tree/17.0/event_stage_cancelled + :alt: OCA/event +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/event-17-0/event-17-0-event_stage_cancelled + :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/event&target_branch=17.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module adds the posibility of flagging an event stage as cancelled +so we can hook workflows onto it like cancelling registrations or +scheduling special mail events. + +**Table of contents** + +.. contents:: + :local: + +Use Cases / Context +=================== + +Up to v14, events had an state field instead of configurable stages. A +lost feature with that change was the concept of a cancelled event and +with it the logic associated to it: when we cancelled an event, its +registrations were cancelled along with it. + +Configuration +============= + +To set a stage as cancelled: + +- Go to *Events > Configuration > Event Stages* and choose the one you + want to be the cancelled one. + +To schedule a mail that triggers when the event is cancelled: + +- Go to *Events* and select an event. +- Go to the *Communication* tab and add a new scheduler. +- Choose to trigger it *After the event cancellation*. +- Choose the other parameters like template or the interval. + +When the event is cancelled, the corresponding mail will be sent to the +attendants who where confirmed at the moment of the cancellation. + +Usage +===== + +When you want to cancel an event an its registration, just click on the +*Cancel Event* button from the event itself. + +A confirmation dialog will show up and if you confirm it, the linked +registrations will be cancelled as well. + +Known issues / Roadmap +====================== + +- If you just change the stage to the cancelled one, the registrations + won't be cancelled. This is avoided for the moment on purpose as a + confirmation dialog would require an special ``ir.actions.client`` + implementation. + +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 `__ + + - David Vidal + +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/event `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/event_stage_cancelled/__init__.py b/event_stage_cancelled/__init__.py new file mode 100644 index 000000000..0650744f6 --- /dev/null +++ b/event_stage_cancelled/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/event_stage_cancelled/__manifest__.py b/event_stage_cancelled/__manifest__.py new file mode 100644 index 000000000..be5338bef --- /dev/null +++ b/event_stage_cancelled/__manifest__.py @@ -0,0 +1,17 @@ +# Copyright 2024 Tecnativa - David Vidal +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +{ + "name": "Event cancellation workflows", + "version": "17.0.1.0.0", + "category": "Marketing", + "author": "Tecnativa, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/event", + "license": "AGPL-3", + "depends": ["event"], + "data": [ + "data/event_data.xml", + "data/mail_template_data.xml", + "views/event_stage_views.xml", + "views/event_event_views.xml", + ], +} diff --git a/event_stage_cancelled/data/event_data.xml b/event_stage_cancelled/data/event_data.xml new file mode 100644 index 000000000..f37a3276d --- /dev/null +++ b/event_stage_cancelled/data/event_data.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/event_stage_cancelled/data/mail_template_data.xml b/event_stage_cancelled/data/mail_template_data.xml new file mode 100644 index 000000000..a5a6d1510 --- /dev/null +++ b/event_stage_cancelled/data/mail_template_data.xml @@ -0,0 +1,184 @@ + + + + Event: Cancelled + + {{ object.event_id.name }}: event cancelled + {{ (object.event_id.organizer_id.email_formatted or object.event_id.user_id.email_formatted or '') }} + {{ (object.email and '"%s" <%s>' % (object.name, object.email) or object.partner_id.email_formatted or '') }} + + + + +
+ + + + + + + + + + + + + + + + +
+ + + +
+ Your registration
+ Oscar Morgan +
+ + + View Event + + + + + +
+
+
+
+ + +
+
+ Hello Oscar Morgan,
+ We are sorry to inform you that the + + OpenWood Collection Online Reveal + + + OpenWood Collection Online Reveal + + has been cancelled. +
+ +
+
+
Please do not hesitate to contact the organizer at for further information:
+ +
+
+
+ We understand the inconvenience this may cause and sincerely apologize for any disappointment.
+ + --
+ + YourCompany + + + The OpenWood Collection Online Reveal Team + +
+
+
+
+
+ + +
+ Sent by YourCompany + +
+ Discover all our events. +
+
+
+
+ {{ object.event_id.lang or object.partner_id.lang }} +
+
diff --git a/event_stage_cancelled/i18n/es.po b/event_stage_cancelled/i18n/es.po new file mode 100644 index 000000000..2a32467dd --- /dev/null +++ b/event_stage_cancelled/i18n/es.po @@ -0,0 +1,392 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * event_stage_cancelled +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 15.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-02-19 09:15+0000\n" +"PO-Revision-Date: 2024-02-19 10:29+0100\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 3.4.2\n" + +#. module: event_stage_cancelled +#: model:mail.template,body_html:event_stage_cancelled.event_cancelled +msgid "" +"\n" +"\n" +"\n" +"
\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +" \n" +"\n" +"\n" +"\n" +" \n" +"\n" +"\n" +"
\n" +" \n" +" \n" +" \n" +"
\n" +" Your registration
\n" +" Oscar Morgan\n" +"
\n" +" \n" +" \n" +" View Event\n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +"
\n" +"
\n" +"
\n" +" \n" +" \n" +"
\n" +"
\n" +" Hello Oscar Morgan," +"
\n" +" We are sorry to inform you that the\n" +" \n" +" OpenWood Collection Online Reveal\n" +" \n" +" \n" +" OpenWood Collection Online Reveal\n" +" \n" +" has been cancelled.\n" +"
\n" +" \n" +"
\n" +"
\n" +"
Please do not hesitate to contact the " +"organizer at for further information:
\n" +" \n" +"
\n" +"
\n" +"
\n" +" We understand the inconvenience this may cause and " +"sincerely apologize for any disappointment.
\n" +" \n" +" --
\n" +" \n" +" YourCompany\n" +" \n" +" \n" +" The OpenWood Collection Online Reveal Team\n" +" \n" +"
\n" +"
\n" +"
\n" +"
\n" +"
\n" +"\n" +" \n" +"
\n" +" Sent by YourCompany\n" +" \n" +"
\n" +" Discover all our " +"events.\n" +"
\n" +"
\n" +"
\n" +" " +msgstr "" +"\n" +"\n" +"\n" +"
\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +" \n" +"\n" +"\n" +"\n" +" \n" +"\n" +"\n" +"
\n" +" \n" +" \n" +" \n" +"
\n" +" Su registro
\n" +" Oscar Morgan\n" +"
\n" +" \n" +" \n" +" Ver evento\n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +"
\n" +"
\n" +"
\n" +" \n" +" \n" +"
\n" +"
\n" +" Hola, Oscar Morgan." +"
\n" +" Lamentamos informarle de que el evento \n" +" \n" +" OpenWood Collection Online Reveal\n" +" \n" +" \n" +" OpenWood Collection Online Reveal\n" +" \n" +" ha sido cancelado.\n" +"
\n" +" \n" +"
\n" +"
\n" +"
Por favor, no dude en ponerse en contacto con " +"el organizador para más información:
\n" +" \n" +"
\n" +"
\n" +"
\n" +" Entendemos los inconvenientes que esto pueda causar y " +"pedimos sinceras disculpas por cualquier molestia ocasionada.
\n" +" \n" +" --
\n" +" \n" +" Tu " +"compañía\n" +" \n" +" \n" +" El equipo de OpenWood Collection Online Reveal\n" +" \n" +"
\n" +"
\n" +"
\n" +"
\n" +"
\n" +"\n" +" \n" +"
\n" +" Enviado por YourCompany\n" +" \n" +"
\n" +" Descubra todos nuestros " +"eventos.\n" +"
\n" +"
\n" +"
\n" +" " + +#. module: event_stage_cancelled +#: model:ir.model.fields.selection,name:event_stage_cancelled.selection__event_mail__interval_type__after_cancel +#: model:ir.model.fields.selection,name:event_stage_cancelled.selection__event_type_mail__interval_type__after_cancel +msgid "After the event cancellation" +msgstr "Tras la cancelación del evento" + +#. module: event_stage_cancelled +#: model_terms:ir.ui.view,arch_db:event_stage_cancelled.view_event_form +msgid "" +"Are you sure you want to cancel this event? All the linked attendees will " +"be cancelled as well." +msgstr "" +"¿Está seguro que quiere cancelar este evento? Todos los asistentes " +"vinculados serán cancelados también." + +#. module: event_stage_cancelled +#: model_terms:ir.ui.view,arch_db:event_stage_cancelled.view_event_form +msgid "Cancel Event" +msgstr "Cancelar evento" + +#. module: event_stage_cancelled +#: model:ir.model.fields,field_description:event_stage_cancelled.field_event_registration__cancelled_from_event +msgid "Cancelled From Event" +msgstr "Cancelado desde el evento" + +#. module: event_stage_cancelled +#: model:ir.model,name:event_stage_cancelled.model_event_event +msgid "Event" +msgstr "Evento" + +#. module: event_stage_cancelled +#: model:ir.model,name:event_stage_cancelled.model_event_mail +msgid "Event Automated Mailing" +msgstr "Envío automático de correos de eventos" + +#. module: event_stage_cancelled +#: model:ir.model,name:event_stage_cancelled.model_event_registration +msgid "Event Registration" +msgstr "Registro a evento" + +#. module: event_stage_cancelled +#: model:ir.model,name:event_stage_cancelled.model_event_stage +msgid "Event Stage" +msgstr "Etapa del evento" + +#. module: event_stage_cancelled +#: model:mail.template,name:event_stage_cancelled.event_cancelled +msgid "Event: Cancelled" +msgstr "Evento: Cancelado" + +#. module: event_stage_cancelled +#: model:ir.model.fields,field_description:event_stage_cancelled.field_event_stage__is_cancelled +msgid "Is Cancelled" +msgstr "Está cancelado" + +#. module: event_stage_cancelled +#: model:ir.model,name:event_stage_cancelled.model_event_type_mail +msgid "Mail Scheduling on Event Category" +msgstr "Programación de correo en la categoría de eventos" + +#. module: event_stage_cancelled +#: model:ir.model,name:event_stage_cancelled.model_event_mail_registration +msgid "Registration Mail Scheduler" +msgstr "Programador de correos de inscripciones" + +#. module: event_stage_cancelled +#: model:ir.model.fields,field_description:event_stage_cancelled.field_event_event__show_cancel_button +msgid "Show Cancel Button" +msgstr "Mostrar el botón de cancelación" + +#. module: event_stage_cancelled +#: model:ir.model.fields,help:event_stage_cancelled.field_event_registration__cancelled_from_event +msgid "" +"Technical field to distinguish those registrations which where cancelled " +"from the event so we can, for example send them scheduled mails after the " +"cancellation but not if the were cancelled before that" +msgstr "" +"Campo técnico para distinguir aquellos asistentes que fueron cancelados " +"desde el evento de modo que podamos, por ejemplo, enviarles notificaciones " +"programadas tras dicha cancelación pero no a aquellos asistentes que " +"hubiesen sido cancelados antes." + +#. module: event_stage_cancelled +#: model:ir.model.fields,help:event_stage_cancelled.field_event_stage__is_cancelled +msgid "The event is cancelled" +msgstr "El evento está cancelado" + +#. module: event_stage_cancelled +#: model:ir.model.fields,field_description:event_stage_cancelled.field_event_type_mail__interval_type +msgid "Trigger" +msgstr "Activador" + +#. module: event_stage_cancelled +#: model:ir.model.fields,field_description:event_stage_cancelled.field_event_mail__interval_type +msgid "Trigger " +msgstr "Desencadenar " + +#. module: event_stage_cancelled +#: model:mail.template,subject:event_stage_cancelled.event_cancelled +msgid "{{ object.event_id.name }}: event cancelled" +msgstr "{{ object.event_id.name }}: evento cancelado" diff --git a/event_stage_cancelled/i18n/event_stage_cancelled.pot b/event_stage_cancelled/i18n/event_stage_cancelled.pot new file mode 100644 index 000000000..5d0820a6f --- /dev/null +++ b/event_stage_cancelled/i18n/event_stage_cancelled.pot @@ -0,0 +1,214 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * event_stage_cancelled +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 15.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: event_stage_cancelled +#: model:mail.template,body_html:event_stage_cancelled.event_cancelled +msgid "" +"\n" +"\n" +"\n" +"
\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +" \n" +"\n" +"\n" +"\n" +" \n" +"\n" +"\n" +"
\n" +" \n" +" \n" +" \n" +"
\n" +" Your registration
\n" +" Oscar Morgan\n" +"
\n" +" \n" +" \n" +" View Event\n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +"
\n" +"
\n" +"
\n" +" \n" +" \n" +"
\n" +"
\n" +" Hello Oscar Morgan,
\n" +" We are sorry to inform you that the\n" +" \n" +" OpenWood Collection Online Reveal\n" +" \n" +" \n" +" OpenWood Collection Online Reveal\n" +" \n" +" has been cancelled.\n" +"
\n" +" \n" +"
\n" +"
\n" +"
Please do not hesitate to contact the organizer at for further information:
\n" +" \n" +"
\n" +"
\n" +"
\n" +" We understand the inconvenience this may cause and sincerely apologize for any disappointment.
\n" +" \n" +" --
\n" +" \n" +" YourCompany\n" +" \n" +" \n" +" The OpenWood Collection Online Reveal Team\n" +" \n" +"
\n" +"
\n" +"
\n" +"
\n" +"
\n" +"\n" +" \n" +"
\n" +" Sent by YourCompany\n" +" \n" +"
\n" +" Discover all our events.\n" +"
\n" +"
\n" +"
\n" +" " +msgstr "" + +#. module: event_stage_cancelled +#: model:ir.model.fields.selection,name:event_stage_cancelled.selection__event_mail__interval_type__after_cancel +#: model:ir.model.fields.selection,name:event_stage_cancelled.selection__event_mail_scheduler_template__interval_type__after_cancel +#: model:ir.model.fields.selection,name:event_stage_cancelled.selection__event_type_mail__interval_type__after_cancel +msgid "After the event cancellation" +msgstr "" + +#. module: event_stage_cancelled +#: model_terms:ir.ui.view,arch_db:event_stage_cancelled.view_event_form +msgid "" +"Are you sure you want to cancel this event? All the linked attendees will be" +" cancelled as well." +msgstr "" + +#. module: event_stage_cancelled +#: model_terms:ir.ui.view,arch_db:event_stage_cancelled.view_event_form +msgid "Cancel Event" +msgstr "" + +#. module: event_stage_cancelled +#: model:ir.model.fields,field_description:event_stage_cancelled.field_event_registration__cancelled_from_event +msgid "Cancelled From Event" +msgstr "" + +#. module: event_stage_cancelled +#: model:ir.model,name:event_stage_cancelled.model_event_event +msgid "Event" +msgstr "" + +#. module: event_stage_cancelled +#: model:ir.model,name:event_stage_cancelled.model_event_mail +msgid "Event Automated Mailing" +msgstr "" + +#. module: event_stage_cancelled +#: model:ir.model,name:event_stage_cancelled.model_event_registration +msgid "Event Registration" +msgstr "" + +#. module: event_stage_cancelled +#: model:ir.model,name:event_stage_cancelled.model_event_stage +msgid "Event Stage" +msgstr "" + +#. module: event_stage_cancelled +#: model:mail.template,name:event_stage_cancelled.event_cancelled +msgid "Event: Cancelled" +msgstr "" + +#. module: event_stage_cancelled +#: model:ir.model.fields,field_description:event_stage_cancelled.field_event_stage__is_cancelled +msgid "Is Cancelled" +msgstr "" + +#. module: event_stage_cancelled +#: model:ir.model,name:event_stage_cancelled.model_event_type_mail +msgid "Mail Scheduling on Event Category" +msgstr "" + +#. module: event_stage_cancelled +#: model:ir.model,name:event_stage_cancelled.model_event_mail_registration +msgid "Registration Mail Scheduler" +msgstr "" + +#. module: event_stage_cancelled +#: model:ir.model.fields,field_description:event_stage_cancelled.field_event_event__show_cancel_button +#: model:ir.model.fields,field_description:event_stage_cancelled.field_event_session__show_cancel_button +msgid "Show Cancel Button" +msgstr "" + +#. module: event_stage_cancelled +#: model:ir.model.fields,help:event_stage_cancelled.field_event_registration__cancelled_from_event +msgid "" +"Technical field to distinguish those registrations which where cancelled " +"from the event so we can, for example send them scheduled mails after the " +"cancellation but not if the were cancelled before that" +msgstr "" + +#. module: event_stage_cancelled +#: model:ir.model.fields,help:event_stage_cancelled.field_event_stage__is_cancelled +msgid "The event is cancelled" +msgstr "" + +#. module: event_stage_cancelled +#: model:ir.model.fields,field_description:event_stage_cancelled.field_event_type_mail__interval_type +msgid "Trigger" +msgstr "" + +#. module: event_stage_cancelled +#: model:ir.model.fields,field_description:event_stage_cancelled.field_event_mail__interval_type +#: model:ir.model.fields,field_description:event_stage_cancelled.field_event_mail_scheduler_template__interval_type +#: model:ir.model.fields,field_description:event_stage_cancelled.field_event_mail_session__interval_type +msgid "Trigger " +msgstr "" + +#. module: event_stage_cancelled +#: model:mail.template,subject:event_stage_cancelled.event_cancelled +msgid "{{ object.event_id.name }}: event cancelled" +msgstr "" diff --git a/event_stage_cancelled/models/__init__.py b/event_stage_cancelled/models/__init__.py new file mode 100644 index 000000000..309cea09e --- /dev/null +++ b/event_stage_cancelled/models/__init__.py @@ -0,0 +1,5 @@ +from . import event_event +from . import event_mail +from . import event_registration +from . import event_stage +from . import event_type_mail diff --git a/event_stage_cancelled/models/event_event.py b/event_stage_cancelled/models/event_event.py new file mode 100644 index 000000000..c6e3e3833 --- /dev/null +++ b/event_stage_cancelled/models/event_event.py @@ -0,0 +1,35 @@ +# Copyright 2024 Tecnativa S.L. - David Vidal +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from odoo import api, fields, models + + +class EventEvent(models.Model): + _inherit = "event.event" + + show_cancel_button = fields.Boolean(compute="_compute_show_cancel_button") + + @api.depends("stage_id") + def _compute_show_cancel_button(self): + """Don't show if there aren't cancel stages or if the event is done""" + stage_id = self.env["event.stage"].search( + [("is_cancelled", "=", True)], limit=1 + ) + for event in self: + event.show_cancel_button = ( + not event.stage_id.is_cancelled + and not event.stage_id.pipe_end + and bool(stage_id) + ) + + def button_cancel(self): + """Perform cancellation of the attendees""" + stage_id = self.env["event.stage"].search( + [("is_cancelled", "=", True)], limit=1 + ) + if stage_id: + self.stage_id = stage_id + self.registration_ids.filtered(lambda x: x.state != "cancel").with_context( + cancelled_from_event=True, + # Compatibility with event_registration_cancel_reason + bypass_reason=True, + ).action_cancel() diff --git a/event_stage_cancelled/models/event_mail.py b/event_stage_cancelled/models/event_mail.py new file mode 100644 index 000000000..1a2e9ac7f --- /dev/null +++ b/event_stage_cancelled/models/event_mail.py @@ -0,0 +1,97 @@ +# Copyright 2024 Tecnativa S.L. - David Vidal +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from odoo import api, fields, models + +from odoo.addons.event.models.event_mail import _INTERVALS + + +class EventMail(models.Model): + _inherit = "event.mail" + + interval_type = fields.Selection( + selection_add=[("after_cancel", "After the event cancellation")], + ondelete={"after_cancel": "cascade"}, + ) + + @api.depends("event_id.stage_id") + def _compute_scheduled_date(self): + """When we cancel the event, we set the scheduled mail""" + regular_schedulers = self.filtered(lambda x: x.interval_type != "after_cancel") + res = super(EventMail, regular_schedulers)._compute_scheduled_date() + for scheduler in self.filtered( + lambda x: x.interval_type == "after_cancel" + and x.event_id.stage_id.is_cancelled + ): + date, sign = scheduler.event_id.write_date, 1 + scheduler.scheduled_date = ( + date + + _INTERVALS[scheduler.interval_unit](sign * scheduler.interval_nbr) + if date + else False + ) + return res + + def execute(self): + """Plan the mailings""" + regular_schedulers = self.filtered(lambda x: x.interval_type != "after_cancel") + res = super(EventMail, regular_schedulers).execute() + for scheduler in self.filtered( + lambda x: x.interval_type == "after_cancel" + and x.event_id.stage_id.is_cancelled + ): + # Get only registrations cancelled from the event button + registrations = ( + scheduler.event_id.registration_ids.filtered("cancelled_from_event") + - scheduler.mail_registration_ids.registration_id + ) + scheduler._create_missing_mail_registrations(registrations) + scheduler.mail_registration_ids.execute() + total_sent = len( + scheduler.mail_registration_ids.filtered(lambda reg: reg.mail_sent) + ) + scheduler.update( + { + "mail_done": total_sent >= len(registrations), + "mail_count_done": total_sent, + } + ) + return res + + +class EventMailRegistration(models.Model): + _inherit = "event.mail.registration" + + def execute(self): + """We don't have a very good hooks. This is almost rewritten from the original + just allows to send the mailing to the cancelled registrations""" + now = fields.Datetime.now() + regular = self.filtered( + lambda x: x.scheduler_id.interval_type != "after_cancel" + ) + res = super(EventMailRegistration, regular).execute() + todo = self.filtered( + lambda x: x.scheduler_id.interval_type == "after_cancel" + and not x.mail_sent + and not x.registration_id.state == "draft" + and (x.scheduled_date and x.scheduled_date <= now) + and x.scheduler_id.notification_type == "mail" + ) + for reg_mail in todo: + organizer = reg_mail.scheduler_id.event_id.organizer_id + company = self.env.company + author = self.env.ref("base.user_root").partner_id + if organizer.email: + author = organizer + elif company.email: + author = company.partner_id + elif self.env.user.email: + author = self.env.user.partner_id + email_values = { + "author_id": author.id, + } + template = reg_mail.scheduler_id.template_ref + if not template.email_from: + email_values["email_from"] = author.email_formatted + template.send_mail(reg_mail.registration_id.id, email_values=email_values) + todo.write({"mail_sent": True}) + return res diff --git a/event_stage_cancelled/models/event_registration.py b/event_stage_cancelled/models/event_registration.py new file mode 100644 index 000000000..0dda53aa4 --- /dev/null +++ b/event_stage_cancelled/models/event_registration.py @@ -0,0 +1,19 @@ +# Copyright 2024 Tecnativa S.L. - David Vidal +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from odoo import fields, models + + +class EventRegistration(models.Model): + _inherit = "event.registration" + + cancelled_from_event = fields.Boolean( + help="Technical field to distinguish those registrations which where cancelled " + "from the event so we can, for example send them scheduled mails after the " + "cancellation but not if the were cancelled before that" + ) + + def action_cancel(self): + res = super().action_cancel() + if self.env.context.get("cancelled_from_event"): + self.cancelled_from_event = True + return res diff --git a/event_stage_cancelled/models/event_stage.py b/event_stage_cancelled/models/event_stage.py new file mode 100644 index 000000000..62ebe255a --- /dev/null +++ b/event_stage_cancelled/models/event_stage.py @@ -0,0 +1,9 @@ +# Copyright 2024 Tecnativa S.L. - David Vidal +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from odoo import fields, models + + +class EventStage(models.Model): + _inherit = "event.stage" + + is_cancelled = fields.Boolean(help="The event is cancelled") diff --git a/event_stage_cancelled/models/event_type_mail.py b/event_stage_cancelled/models/event_type_mail.py new file mode 100644 index 000000000..1b4f51296 --- /dev/null +++ b/event_stage_cancelled/models/event_type_mail.py @@ -0,0 +1,12 @@ +# Copyright 2024 Tecnativa S.L. - David Vidal +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from odoo import fields, models + + +class EventTypeMail(models.Model): + _inherit = "event.type.mail" + + interval_type = fields.Selection( + selection_add=[("after_cancel", "After the event cancellation")], + ondelete={"after_cancel": "cascade"}, + ) diff --git a/event_stage_cancelled/pyproject.toml b/event_stage_cancelled/pyproject.toml new file mode 100644 index 000000000..4231d0ccc --- /dev/null +++ b/event_stage_cancelled/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/event_stage_cancelled/readme/CONFIGURE.md b/event_stage_cancelled/readme/CONFIGURE.md new file mode 100644 index 000000000..c64304d85 --- /dev/null +++ b/event_stage_cancelled/readme/CONFIGURE.md @@ -0,0 +1,14 @@ +To set a stage as cancelled: + +- Go to *Events > Configuration > Event Stages* and choose the one you want to be + the cancelled one. + +To schedule a mail that triggers when the event is cancelled: + +- Go to *Events* and select an event. +- Go to the *Communication* tab and add a new scheduler. +- Choose to trigger it *After the event cancellation*. +- Choose the other parameters like template or the interval. + +When the event is cancelled, the corresponding mail will be sent to the attendants +who where confirmed at the moment of the cancellation. diff --git a/event_stage_cancelled/readme/CONTEXT.md b/event_stage_cancelled/readme/CONTEXT.md new file mode 100644 index 000000000..a8d7ecdd1 --- /dev/null +++ b/event_stage_cancelled/readme/CONTEXT.md @@ -0,0 +1,3 @@ +Up to v14, events had an state field instead of configurable stages. A lost feature +with that change was the concept of a cancelled event and with it the logic associated +to it: when we cancelled an event, its registrations were cancelled along with it. diff --git a/event_stage_cancelled/readme/CONTRIBUTORS.md b/event_stage_cancelled/readme/CONTRIBUTORS.md new file mode 100644 index 000000000..3a16ddf04 --- /dev/null +++ b/event_stage_cancelled/readme/CONTRIBUTORS.md @@ -0,0 +1,2 @@ +- [Tecnativa](https://tecnativa.com) + - David Vidal diff --git a/event_stage_cancelled/readme/DESCRIPTION.md b/event_stage_cancelled/readme/DESCRIPTION.md new file mode 100644 index 000000000..1557cdce0 --- /dev/null +++ b/event_stage_cancelled/readme/DESCRIPTION.md @@ -0,0 +1,2 @@ +This module adds the posibility of flagging an event stage as cancelled so we can hook +workflows onto it like cancelling registrations or scheduling special mail events. diff --git a/event_stage_cancelled/readme/ROADMAP.md b/event_stage_cancelled/readme/ROADMAP.md new file mode 100644 index 000000000..aae4bf73d --- /dev/null +++ b/event_stage_cancelled/readme/ROADMAP.md @@ -0,0 +1,3 @@ +- If you just change the stage to the cancelled one, the registrations won't be + cancelled. This is avoided for the moment on purpose as a confirmation dialog would + require an special `ir.actions.client` implementation. diff --git a/event_stage_cancelled/readme/USAGE.md b/event_stage_cancelled/readme/USAGE.md new file mode 100644 index 000000000..16e3f3f0c --- /dev/null +++ b/event_stage_cancelled/readme/USAGE.md @@ -0,0 +1,5 @@ +When you want to cancel an event an its registration, just click on the *Cancel Event* +button from the event itself. + +A confirmation dialog will show up and if you confirm it, the linked registrations will +be cancelled as well. diff --git a/event_stage_cancelled/static/description/icon.png b/event_stage_cancelled/static/description/icon.png new file mode 100644 index 000000000..3a0328b51 Binary files /dev/null and b/event_stage_cancelled/static/description/icon.png differ diff --git a/event_stage_cancelled/static/description/index.html b/event_stage_cancelled/static/description/index.html new file mode 100644 index 000000000..de16f98a4 --- /dev/null +++ b/event_stage_cancelled/static/description/index.html @@ -0,0 +1,472 @@ + + + + + +Event cancellation workflows + + + +
+

Event cancellation workflows

+ + +

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

+

This module adds the posibility of flagging an event stage as cancelled +so we can hook workflows onto it like cancelling registrations or +scheduling special mail events.

+

Table of contents

+ +
+

Use Cases / Context

+

Up to v14, events had an state field instead of configurable stages. A +lost feature with that change was the concept of a cancelled event and +with it the logic associated to it: when we cancelled an event, its +registrations were cancelled along with it.

+
+
+

Configuration

+

To set a stage as cancelled:

+
    +
  • Go to Events > Configuration > Event Stages and choose the one you +want to be the cancelled one.
  • +
+

To schedule a mail that triggers when the event is cancelled:

+
    +
  • Go to Events and select an event.
  • +
  • Go to the Communication tab and add a new scheduler.
  • +
  • Choose to trigger it After the event cancellation.
  • +
  • Choose the other parameters like template or the interval.
  • +
+

When the event is cancelled, the corresponding mail will be sent to the +attendants who where confirmed at the moment of the cancellation.

+
+
+

Usage

+

When you want to cancel an event an its registration, just click on the +Cancel Event button from the event itself.

+

A confirmation dialog will show up and if you confirm it, the linked +registrations will be cancelled as well.

+
+
+

Known issues / Roadmap

+
    +
  • If you just change the stage to the cancelled one, the registrations +won’t be cancelled. This is avoided for the moment on purpose as a +confirmation dialog would require an special ir.actions.client +implementation.
  • +
+
+
+

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

+ +
+
+

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

+

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

+
+
+
+ + diff --git a/event_stage_cancelled/tests/__init__.py b/event_stage_cancelled/tests/__init__.py new file mode 100644 index 000000000..119a4d62f --- /dev/null +++ b/event_stage_cancelled/tests/__init__.py @@ -0,0 +1 @@ +from . import test_event_stage_cancelled diff --git a/event_stage_cancelled/tests/test_event_stage_cancelled.py b/event_stage_cancelled/tests/test_event_stage_cancelled.py new file mode 100644 index 000000000..4f2918c35 --- /dev/null +++ b/event_stage_cancelled/tests/test_event_stage_cancelled.py @@ -0,0 +1,87 @@ +# Copyright 2024 Tecnativa S.L. - David Vidal +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from odoo.addons.base.tests.common import BaseCommon + + +class TestEventCancelCase(BaseCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.event = cls.env["event.event"].create( + { + "date_begin": "2024-05-06 09:00:00", + "date_end": "2024-05-08 18:00:00", + "name": "OCA DAYS", + } + ) + # Let's prevent default schedulers that could distort our test case + cls.event.event_mail_ids.unlink() + cls.mail_template = cls.env["mail.template"].create( + { + "name": "Test event cancelled", + "model_id": cls.env.ref("event.model_event_registration").id, + # Just a test, the event will go perfectly. Join it! + "subject": "The event is cancelled!", + "body_html": "

We're sorry to announce that...

", + } + ) + cls.event_mail = cls.env["event.mail"].create( + [ + { + "event_id": cls.event.id, + "notification_type": "mail", + "interval_unit": "now", + "interval_type": "after_cancel", + "template_ref": f"mail.template, {cls.mail_template.id}", + } + ] + ) + cls.attendees = cls.env["event.registration"].create( + [ + { + "event_id": cls.event.id, + "name": f"Test attendee {reg}", + "email": f"test_attendee_{reg}@test.com", + } + for reg in range(5) + ] + ) + ( + cls.attendee_1, + cls.attendee_2, + cls.attendee_3, + cls.attendee_4, + cls.attendee_5, + ) = cls.attendees + # Let's add some variations + (cls.attendee_1 + cls.attendee_2).state = "draft" + (cls.attendee_3 + cls.attendee_4).state = "open" + cls.attendee_5.state = "cancel" + + def test_event_cancellation(self): + """Test the processes triggered by the event cancellation""" + # Force the scheduler to see no effect (normally is handled by the cron) + self.event_mail.execute() + self.assertFalse(bool(self.event_mail.mail_registration_ids)) + self.assertFalse(self.event_mail.scheduled_date) + self.assertEqual(self.event_mail.mail_state, "running") + # Inject bypass_reason for test compatibility + # with event_registration_cancel_reason + self.event.button_cancel() + self.assertTrue( + all([a.state == "cancel" for a in self.attendees]), + f"Not all the attendees are cancelled: " + f"{' / '.join([str((a.name, a.state)) for a in self.attendees])}", + ) + # One attendee was already cancelled. + self.assertEqual(len(self.attendees.filtered("cancelled_from_event")), 4) + self.assertEqual(self.event_mail.mail_state, "scheduled") + # Force the scheduler. Normally is handled by the cron + self.event_mail.execute() + # Only the attendees that we just cancelled are going to be notified + self.assertEqual( + (self.attendee_1 + self.attendee_2 + self.attendee_3 + self.attendee_4), + self.event_mail.mail_registration_ids.registration_id, + ) + self.assertEqual(self.event_mail.mail_state, "sent") + self.assertTrue(all(self.event_mail.mail_registration_ids.mapped("mail_sent"))) diff --git a/event_stage_cancelled/views/event_event_views.xml b/event_stage_cancelled/views/event_event_views.xml new file mode 100644 index 000000000..1a2651205 --- /dev/null +++ b/event_stage_cancelled/views/event_event_views.xml @@ -0,0 +1,19 @@ + + + + event.event + + +
+ +
+
+
+
diff --git a/event_stage_cancelled/views/event_stage_views.xml b/event_stage_cancelled/views/event_stage_views.xml new file mode 100644 index 000000000..8a4f9d846 --- /dev/null +++ b/event_stage_cancelled/views/event_stage_views.xml @@ -0,0 +1,12 @@ + + + + event.stage + + + + + + + +