From 0151322d3694bc861ebcf5d093a43009ac60cc31 Mon Sep 17 00:00:00 2001 From: Jordi Ballester Alomar Date: Wed, 18 Mar 2026 06:34:25 +0100 Subject: [PATCH] [ADD] automation_oca_activity_note_template: render activity note and summary from mail template Adds a new module that extends automation_oca activity steps with the ability to point to a mail.template. When set, the template's subject and body_html are rendered (using inline_template engine) against the target record and used as the activity summary and note respectively. This allows {{ object.field }} and {{ object.method() }} expressions in activity notes, enabling AI-generated personalized content via method calls on the target record. --- .../README.rst | 151 ++++++ .../__init__.py | 3 + .../__manifest__.py | 15 + .../models/__init__.py | 4 + .../models/automation_configuration_step.py | 16 + .../models/automation_record_step.py | 61 +++ .../pyproject.toml | 3 + .../readme/CONTEXT.md | 10 + .../readme/CONTRIBUTORS.md | 1 + .../readme/CREDITS.md | 3 + .../readme/DESCRIPTION.md | 14 + .../readme/USAGE.md | 26 + .../static/description/icon.png | Bin 0 -> 37729 bytes .../static/description/index.html | 501 ++++++++++++++++++ .../tests/__init__.py | 3 + .../test_automation_activity_note_template.py | 87 +++ .../automation_configuration_step_views.xml | 35 ++ 17 files changed, 933 insertions(+) create mode 100644 automation_oca_activity_note_template/README.rst create mode 100644 automation_oca_activity_note_template/__init__.py create mode 100644 automation_oca_activity_note_template/__manifest__.py create mode 100644 automation_oca_activity_note_template/models/__init__.py create mode 100644 automation_oca_activity_note_template/models/automation_configuration_step.py create mode 100644 automation_oca_activity_note_template/models/automation_record_step.py create mode 100644 automation_oca_activity_note_template/pyproject.toml create mode 100644 automation_oca_activity_note_template/readme/CONTEXT.md create mode 100644 automation_oca_activity_note_template/readme/CONTRIBUTORS.md create mode 100644 automation_oca_activity_note_template/readme/CREDITS.md create mode 100644 automation_oca_activity_note_template/readme/DESCRIPTION.md create mode 100644 automation_oca_activity_note_template/readme/USAGE.md create mode 100644 automation_oca_activity_note_template/static/description/icon.png create mode 100644 automation_oca_activity_note_template/static/description/index.html create mode 100644 automation_oca_activity_note_template/tests/__init__.py create mode 100644 automation_oca_activity_note_template/tests/test_automation_activity_note_template.py create mode 100644 automation_oca_activity_note_template/views/automation_configuration_step_views.xml diff --git a/automation_oca_activity_note_template/README.rst b/automation_oca_activity_note_template/README.rst new file mode 100644 index 00000000..4d272e34 --- /dev/null +++ b/automation_oca_activity_note_template/README.rst @@ -0,0 +1,151 @@ +======================================= +Automation OCA - Activity Note Template +======================================= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:6fac894cded1581e6da7ead1f6d5abe64054691afb727804263b27cb47eb0bff + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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%2Fautomation-lightgray.png?logo=github + :target: https://github.com/OCA/automation/tree/18.0/automation_oca_activity_note_template + :alt: OCA/automation +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/automation-18-0/automation-18-0-automation_oca_activity_note_template + :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/automation&target_branch=18.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module extends the **Activity** step type in ``automation_oca`` +with the ability to select a **mail template** as the source for the +activity's summary and note. + +When a mail template is selected on an automation step: + +- The template **subject** is rendered and used as the **activity + summary**. +- The template **body** is rendered and used as the **activity note**. + +Both fields are rendered using the ``inline_template`` engine, which +supports ``{{ object.field }}`` and ``{{ object.method() }}`` +expressions evaluated against the target record at the moment the +activity is created. + +This makes it straightforward to generate **dynamic, personalised +activity notes** by calling a method on the target model from inside the +template body. + +**Table of contents** + +.. contents:: + :local: + +Use Cases / Context +=================== + +``automation_oca`` activity steps support a static **Note** and +**Summary** field, which are written as plain text or HTML at +configuration time and copied verbatim to every activity created by the +step. + +This is limiting when the note needs to reflect data specific to each +record — for example, a personalised LinkedIn outreach prompt. + +``automation_oca_activity_note_template`` solves this by delegating the +rendering to a **mail template**, which already has a well-established +mechanism for per-record dynamic content and is familiar to Odoo users. + +Usage +===== + +Configure a mail template +------------------------- + +1. Go to **Email** → **Templates** and create a new template. + +2. Set the **Applies To** model to match the model used in your + automation (e.g. *Contact*, *CRM Lead*). + +3. Write the **Subject** — it will become the **activity summary**. Use + ``{{ object.field }}`` expressions to include record-specific data. + +4. Write the **Body** — it will become the **activity note**. You may + call methods on ``object``, for example: + + :: + + {{ object.generate_note() }} + +Configure the automation step +----------------------------- + +1. Open an **Automation Configuration** and add or edit an **Activity** + step. +2. In the **Activity** tab, select the template in the **Note Template** + field. +3. The static **Summary** and **Note** fields are hidden when a template + is selected, as they are replaced by the rendered template output. +4. Save and start the automation as usual. + +When the step runs, the template subject and body are rendered +individually for each target record and set as the activity summary and +note respectively. + +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 +------- + +* ForgeFlow + +Contributors +------------ + +- Jordi Ballester (`ForgeFlow `__) + +Other credits +------------- + +The development of this module has been financially supported by: + +- `ForgeFlow `__ + +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/automation `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/automation_oca_activity_note_template/__init__.py b/automation_oca_activity_note_template/__init__.py new file mode 100644 index 00000000..11414067 --- /dev/null +++ b/automation_oca_activity_note_template/__init__.py @@ -0,0 +1,3 @@ +# Copyright 2026 ForgeFlow +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from . import models diff --git a/automation_oca_activity_note_template/__manifest__.py b/automation_oca_activity_note_template/__manifest__.py new file mode 100644 index 00000000..37d517f2 --- /dev/null +++ b/automation_oca_activity_note_template/__manifest__.py @@ -0,0 +1,15 @@ +# Copyright 2026 ForgeFlow +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +{ + "name": "Automation OCA - Activity Note Template", + "summary": "Render activity notes from a mail template in automation steps", + "version": "18.0.1.0.0", + "category": "Technical", + "author": "ForgeFlow, Odoo Community Association (OCA)", + "license": "AGPL-3", + "depends": ["automation_oca"], + "data": [ + "views/automation_configuration_step_views.xml", + ], + "installable": True, +} diff --git a/automation_oca_activity_note_template/models/__init__.py b/automation_oca_activity_note_template/models/__init__.py new file mode 100644 index 00000000..cc7ded8f --- /dev/null +++ b/automation_oca_activity_note_template/models/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2026 ForgeFlow +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from . import automation_configuration_step +from . import automation_record_step diff --git a/automation_oca_activity_note_template/models/automation_configuration_step.py b/automation_oca_activity_note_template/models/automation_configuration_step.py new file mode 100644 index 00000000..f1ddbfe3 --- /dev/null +++ b/automation_oca_activity_note_template/models/automation_configuration_step.py @@ -0,0 +1,16 @@ +# Copyright 2026 ForgeFlow +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo import fields, models + + +class AutomationConfigurationStep(models.Model): + _inherit = "automation.configuration.step" + + activity_note_template_id = fields.Many2one( + "mail.template", + string="Note Template", + domain="[('model_id', '=', model_id)]", + help="If set, the activity note is rendered from this mail template. " + "The template body may use {{ object.method() }} expressions. " + "Overrides the static Note field.", + ) diff --git a/automation_oca_activity_note_template/models/automation_record_step.py b/automation_oca_activity_note_template/models/automation_record_step.py new file mode 100644 index 00000000..f4f336c9 --- /dev/null +++ b/automation_oca_activity_note_template/models/automation_record_step.py @@ -0,0 +1,61 @@ +# Copyright 2026 ForgeFlow +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from dateutil.relativedelta import relativedelta + +from odoo import fields, models + + +class AutomationRecordStep(models.Model): + _inherit = "automation.record.step" + + def _render_template_for_record( + self, template_src, res_id, engine="inline_template" + ): + rendered = self.env["mail.template"]._render_template( + template_src, + self.record_id.model, + [res_id], + engine=engine, + ) + return rendered.get(res_id, "") + + def _get_activity_summary(self): + template = self.configuration_step_id.activity_note_template_id + if not template: + return self.configuration_step_id.activity_summary or "" + return self._render_template_for_record(template.subject, self.record_id.res_id) + + def _get_activity_note(self): + template = self.configuration_step_id.activity_note_template_id + if not template: + return self.configuration_step_id.activity_note or "" + return self._render_template_for_record( + template.body_html, self.record_id.res_id + ) + + def _run_activity(self): + if not self.configuration_step_id.activity_note_template_id: + return super()._run_activity() + + record = self.env[self.record_id.model].browse(self.record_id.res_id) + step = self.configuration_step_id + vals = { + "summary": self._get_activity_summary(), + "note": self._get_activity_note(), + "activity_type_id": step.activity_type_id.id, + "automation_record_step_id": self.id, + } + if step.activity_date_deadline_range > 0: + range_type = step.activity_date_deadline_range_type + vals["date_deadline"] = fields.Date.context_today(self) + relativedelta( + **{range_type: step.activity_date_deadline_range} + ) + user = False + if step.activity_user_type == "specific": + user = step.activity_user_id + elif step.activity_user_type == "generic": + user = record[step.activity_user_field_id.name] + if user: + vals["user_id"] = user.id + record.activity_schedule(**vals) + return True diff --git a/automation_oca_activity_note_template/pyproject.toml b/automation_oca_activity_note_template/pyproject.toml new file mode 100644 index 00000000..4231d0cc --- /dev/null +++ b/automation_oca_activity_note_template/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/automation_oca_activity_note_template/readme/CONTEXT.md b/automation_oca_activity_note_template/readme/CONTEXT.md new file mode 100644 index 00000000..387a2ad4 --- /dev/null +++ b/automation_oca_activity_note_template/readme/CONTEXT.md @@ -0,0 +1,10 @@ +`automation_oca` activity steps support a static **Note** and **Summary** field, +which are written as plain text or HTML at configuration time and copied verbatim +to every activity created by the step. + +This is limiting when the note needs to reflect data specific to each record — +for example, a personalised LinkedIn outreach prompt. + +`automation_oca_activity_note_template` solves this by delegating the rendering +to a **mail template**, which already has a well-established mechanism for +per-record dynamic content and is familiar to Odoo users. diff --git a/automation_oca_activity_note_template/readme/CONTRIBUTORS.md b/automation_oca_activity_note_template/readme/CONTRIBUTORS.md new file mode 100644 index 00000000..d96eaa58 --- /dev/null +++ b/automation_oca_activity_note_template/readme/CONTRIBUTORS.md @@ -0,0 +1 @@ +- Jordi Ballester ([ForgeFlow](https://www.forgeflow.com/)) diff --git a/automation_oca_activity_note_template/readme/CREDITS.md b/automation_oca_activity_note_template/readme/CREDITS.md new file mode 100644 index 00000000..6c667a9c --- /dev/null +++ b/automation_oca_activity_note_template/readme/CREDITS.md @@ -0,0 +1,3 @@ +The development of this module has been financially supported by: + +- [ForgeFlow](https://www.forgeflow.com/) diff --git a/automation_oca_activity_note_template/readme/DESCRIPTION.md b/automation_oca_activity_note_template/readme/DESCRIPTION.md new file mode 100644 index 00000000..f41d3af4 --- /dev/null +++ b/automation_oca_activity_note_template/readme/DESCRIPTION.md @@ -0,0 +1,14 @@ +This module extends the **Activity** step type in `automation_oca` with the ability +to select a **mail template** as the source for the activity's summary and note. + +When a mail template is selected on an automation step: + +- The template **subject** is rendered and used as the **activity summary**. +- The template **body** is rendered and used as the **activity note**. + +Both fields are rendered using the `inline_template` engine, which supports +`{{ object.field }}` and `{{ object.method() }}` expressions evaluated against +the target record at the moment the activity is created. + +This makes it straightforward to generate **dynamic, personalised activity notes** +by calling a method on the target model from inside the template body. diff --git a/automation_oca_activity_note_template/readme/USAGE.md b/automation_oca_activity_note_template/readme/USAGE.md new file mode 100644 index 00000000..959ba68a --- /dev/null +++ b/automation_oca_activity_note_template/readme/USAGE.md @@ -0,0 +1,26 @@ +Configure a mail template +-------------------------- + +1. Go to **Email** → **Templates** and create a new template. +2. Set the **Applies To** model to match the model used in your automation + (e.g. *Contact*, *CRM Lead*). +3. Write the **Subject** — it will become the **activity summary**. + Use `{{ object.field }}` expressions to include record-specific data. +4. Write the **Body** — it will become the **activity note**. + You may call methods on `object`, for example: + + ``` + {{ object.generate_note() }} + ``` + +Configure the automation step +------------------------------ + +1. Open an **Automation Configuration** and add or edit an **Activity** step. +2. In the **Activity** tab, select the template in the **Note Template** field. +3. The static **Summary** and **Note** fields are hidden when a template is selected, + as they are replaced by the rendered template output. +4. Save and start the automation as usual. + +When the step runs, the template subject and body are rendered individually for +each target record and set as the activity summary and note respectively. diff --git a/automation_oca_activity_note_template/static/description/icon.png b/automation_oca_activity_note_template/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..d4f84f34be04655e290d7239aca471b82d496066 GIT binary patch literal 37729 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�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 L?&se%c>4bV8g6&X literal 0 HcmV?d00001 diff --git a/automation_oca_activity_note_template/static/description/index.html b/automation_oca_activity_note_template/static/description/index.html new file mode 100644 index 00000000..1be667f4 --- /dev/null +++ b/automation_oca_activity_note_template/static/description/index.html @@ -0,0 +1,501 @@ + + + + + +Automation OCA - Activity Note Template + + + +
+

Automation OCA - Activity Note Template

+ + +

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

+

This module extends the Activity step type in automation_oca +with the ability to select a mail template as the source for the +activity’s summary and note.

+

When a mail template is selected on an automation step:

+
    +
  • The template subject is rendered and used as the activity +summary.
  • +
  • The template body is rendered and used as the activity note.
  • +
+

Both fields are rendered using the inline_template engine, which +supports {{ object.field }} and {{ object.method() }} +expressions evaluated against the target record at the moment the +activity is created.

+

This makes it straightforward to generate dynamic, personalised +activity notes by calling a method on the target model from inside the +template body.

+

Table of contents

+ +
+

Use Cases / Context

+

automation_oca activity steps support a static Note and +Summary field, which are written as plain text or HTML at +configuration time and copied verbatim to every activity created by the +step.

+

This is limiting when the note needs to reflect data specific to each +record — for example, a personalised LinkedIn outreach prompt.

+

automation_oca_activity_note_template solves this by delegating the +rendering to a mail template, which already has a well-established +mechanism for per-record dynamic content and is familiar to Odoo users.

+
+
+

Usage

+
+

Configure a mail template

+
    +
  1. Go to EmailTemplates and create a new template.

    +
  2. +
  3. Set the Applies To model to match the model used in your +automation (e.g. Contact, CRM Lead).

    +
  4. +
  5. Write the Subject — it will become the activity summary. Use +{{ object.field }} expressions to include record-specific data.

    +
  6. +
  7. Write the Body — it will become the activity note. You may +call methods on object, for example:

    +
    +{{ object.generate_note() }}
    +
    +
  8. +
+
+
+

Configure the automation step

+
    +
  1. Open an Automation Configuration and add or edit an Activity +step.
  2. +
  3. In the Activity tab, select the template in the Note Template +field.
  4. +
  5. The static Summary and Note fields are hidden when a template +is selected, as they are replaced by the rendered template output.
  6. +
  7. Save and start the automation as usual.
  8. +
+

When the step runs, the template subject and body are rendered +individually for each target record and set as the activity summary and +note respectively.

+
+
+
+

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

+
    +
  • ForgeFlow
  • +
+
+
+

Contributors

+ +
+
+

Other credits

+

The development of this module has been financially supported by:

+ +
+
+

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

+

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

+
+
+
+ + diff --git a/automation_oca_activity_note_template/tests/__init__.py b/automation_oca_activity_note_template/tests/__init__.py new file mode 100644 index 00000000..7e57d6a7 --- /dev/null +++ b/automation_oca_activity_note_template/tests/__init__.py @@ -0,0 +1,3 @@ +# Copyright 2026 ForgeFlow +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from . import test_automation_activity_note_template diff --git a/automation_oca_activity_note_template/tests/test_automation_activity_note_template.py b/automation_oca_activity_note_template/tests/test_automation_activity_note_template.py new file mode 100644 index 00000000..e473cae8 --- /dev/null +++ b/automation_oca_activity_note_template/tests/test_automation_activity_note_template.py @@ -0,0 +1,87 @@ +# Copyright 2026 ForgeFlow +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo.addons.automation_oca.tests.common import AutomationTestCase + + +class TestAutomationActivityNoteTemplate(AutomationTestCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.note_template = cls.env["mail.template"].create( + { + "name": "Activity note template", + "model_id": cls.env.ref("base.model_res_partner").id, + "subject": "Follow up with {{ object.name }}", + "body_html": "

Contact: {{ object.name }}

", + } + ) + + def _run_automation(self, domain): + self.configuration.editable_domain = domain + self.configuration.start_automation() + self.env["automation.configuration"].cron_automation() + self.env["automation.record.step"]._cron_automation_steps() + + def test_summary_and_note_rendered_from_template(self): + """Both subject and body_html of the template are rendered into summary and note.""" + activity = self.create_activity_action( + activity_note_template_id=self.note_template.id, + ) + self._run_automation(f"[('id', '=', {self.partner_01.id})]") + self.assertTrue(self.partner_01.activity_ids) + act = self.partner_01.activity_ids + self.assertIn(self.partner_01.name, act.summary) + self.assertIn(self.partner_01.name, act.note) + activity.unlink() + + def test_static_note_without_template(self): + """Without a note template, static activity_note is used unchanged.""" + activity = self.create_activity_action( + activity_note="

Static note

", + ) + self._run_automation(f"[('id', '=', {self.partner_01.id})]") + self.assertTrue(self.partner_01.activity_ids) + self.assertEqual(self.partner_01.activity_ids.note, "

Static note

") + activity.unlink() + + def test_note_rendered_from_template(self): + """When a note template is set, body_html is rendered against the record.""" + activity = self.create_activity_action( + activity_note_template_id=self.note_template.id, + ) + self._run_automation(f"[('id', '=', {self.partner_01.id})]") + self.assertTrue(self.partner_01.activity_ids) + self.assertIn(self.partner_01.name, self.partner_01.activity_ids.note) + activity.unlink() + + def test_template_overrides_static_note(self): + """When both note template and static note are set, template takes precedence.""" + activity = self.create_activity_action( + activity_note="

Static note

", + activity_note_template_id=self.note_template.id, + ) + self._run_automation(f"[('id', '=', {self.partner_01.id})]") + self.assertTrue(self.partner_01.activity_ids) + note = self.partner_01.activity_ids.note + self.assertIn(self.partner_01.name, note) + self.assertNotIn("Static note", note) + activity.unlink() + + def test_note_rendered_per_record(self): + """Template is rendered individually for each record.""" + activity = self.create_activity_action( + activity_note_template_id=self.note_template.id, + ) + self._run_automation( + f"[('id', 'in', [{self.partner_01.id}, {self.partner_02.id}])]" + ) + self.assertTrue(self.partner_01.activity_ids) + self.assertTrue(self.partner_02.activity_ids) + note_01 = self.partner_01.activity_ids.note + note_02 = self.partner_02.activity_ids.note + self.assertIn(self.partner_01.name, note_01) + self.assertIn(self.partner_02.name, note_02) + self.assertNotEqual(note_01, note_02) + self.assertNotIn(self.partner_02.name, note_01) + activity.unlink() diff --git a/automation_oca_activity_note_template/views/automation_configuration_step_views.xml b/automation_oca_activity_note_template/views/automation_configuration_step_views.xml new file mode 100644 index 00000000..e6a53d1f --- /dev/null +++ b/automation_oca_activity_note_template/views/automation_configuration_step_views.xml @@ -0,0 +1,35 @@ + + + + + automation.configuration.step + + + + + + + activity_note_template_id != False + Summary (static) + + + activity_note_template_id != False + Note (static) + + + +