diff --git a/agreement_termination/README.rst b/agreement_termination/README.rst new file mode 100644 index 000000000..5f3d808b4 --- /dev/null +++ b/agreement_termination/README.rst @@ -0,0 +1,97 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + +===================== +Agreement Termination +===================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:3f07e0081786446c37b27ce860fc2844361c6dd79e794b9ac80b5976b5ce6175 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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%2Fagreement-lightgray.png?logo=github + :target: https://github.com/OCA/agreement/tree/19.0/agreement_termination + :alt: OCA/agreement +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/agreement-19-0/agreement-19-0-agreement_termination + :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/agreement&target_branch=19.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module adds the "Term Dates" section to legal agreements, grouping +the signature/start/end dates together with the expiration and change +notice periods, the notification address and the termination tracking +dates (termination requested / terminated). + +It also adds a review date to each agreement, computed as the end date +minus the expiration notice. A daily cron schedules a "review" activity +on that date, assigned to the user who last modified the agreement. + +.. image:: https://raw.githubusercontent.com/OCA/agreement/19.0/agreement_termination/static/description/screenshot_form.png + :alt: Agreement Form + +**Table of contents** + +.. contents:: + :local: + +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 +------- + +* Pavlov Media +* Open Source Integrators +* Yves Goldberg (Ygol Internetwork) + +Contributors +------------ + +- Pavlov Media +- Open Source Integrators +- Yves Goldberg (Ygol Internetwork) +- `glueckkanja AG `__: + + - Christopher Rogos + +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/agreement `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/agreement_termination/__init__.py b/agreement_termination/__init__.py new file mode 100644 index 000000000..69f7babdf --- /dev/null +++ b/agreement_termination/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import models diff --git a/agreement_termination/__manifest__.py b/agreement_termination/__manifest__.py new file mode 100644 index 000000000..955922ac8 --- /dev/null +++ b/agreement_termination/__manifest__.py @@ -0,0 +1,23 @@ +# Copyright (C) 2018 - TODAY, Pavlov Media +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Agreement Termination", + "summary": "Term dates, notices, review reminders and termination tracking for " + "agreements", + "author": "Pavlov Media, " + "Open Source Integrators, " + "Yves Goldberg (Ygol Internetwork), " + "Odoo Community Association (OCA)", + "website": "https://github.com/OCA/agreement", + "category": "Partner", + "license": "AGPL-3", + "version": "19.0.1.0.0", + "depends": ["agreement"], + "excludes": ["agreement_legal"], + "data": [ + "data/cron.xml", + "views/agreement.xml", + ], + "development_status": "Beta", +} diff --git a/agreement_termination/data/cron.xml b/agreement_termination/data/cron.xml new file mode 100644 index 000000000..6e73c4f7f --- /dev/null +++ b/agreement_termination/data/cron.xml @@ -0,0 +1,23 @@ + + + + + Agreement needs a review + Review agreement before it ends + default + agreement + fa-tasks + 0 + + + + Agreement: Check Review Date + + code + model._alert_to_review_date() + + 1 + days + + + diff --git a/agreement_termination/models/__init__.py b/agreement_termination/models/__init__.py new file mode 100644 index 000000000..61b20a4ef --- /dev/null +++ b/agreement_termination/models/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import agreement diff --git a/agreement_termination/models/agreement.py b/agreement_termination/models/agreement.py new file mode 100644 index 000000000..54051c107 --- /dev/null +++ b/agreement_termination/models/agreement.py @@ -0,0 +1,76 @@ +# Copyright (C) 2018 - TODAY, Pavlov Media +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from datetime import timedelta + +from odoo import api, fields, models + + +class Agreement(models.Model): + _name = "agreement" + _inherit = ["agreement"] + + expiration_notice = fields.Integer( + string="Exp. Notice (Days)", + tracking=True, + help="Number of Days before expiration to be notified.", + ) + change_notice = fields.Integer( + string="Change Notice (Days)", + tracking=True, + help="Number of Days to be notified before changes.", + ) + termination_requested = fields.Date( + string="Termination Requested Date", + tracking=True, + help="Date that a request for termination was received.", + ) + termination_date = fields.Date( + tracking=True, help="Date that the contract was terminated." + ) + notification_address_id = fields.Many2one( + "res.partner", + string="Notification Address", + help="The address to send notifications to, if different from " + "customer address.(Address Type = Other)", + ) + to_review_date = fields.Date( + compute="_compute_to_review_date", + store=True, + readonly=False, + help="Date on which the last modifier is reminded to review the " + "agreement. Defaults to the end date minus the expiration notice.", + ) + + @api.depends("end_date", "expiration_notice") + def _compute_to_review_date(self): + for record in self: + if record.end_date: + record.to_review_date = record.end_date - timedelta( + days=record.expiration_notice + ) + + @api.model + def _alert_to_review_date(self): + """Cron: schedule a review activity for agreements whose review date + is today, assigned to the user who last modified the agreement.""" + activity_type = self.env.ref( + "agreement_termination.mail_activity_review_agreement" + ) + agreements = self.search( + [ + ("to_review_date", "=", fields.Date.today()), + ("is_template", "=", False), + ] + ) + for agreement in agreements: + if agreement.activity_ids.filtered( + lambda a: a.activity_type_id == activity_type + ): + continue + reviewer = agreement.user_id or agreement.write_uid or agreement.create_uid + agreement.activity_schedule( + "agreement_termination.mail_activity_review_agreement", + user_id=reviewer.id, + note=self.env._("This agreement is going to end soon"), + ) diff --git a/agreement_termination/pyproject.toml b/agreement_termination/pyproject.toml new file mode 100644 index 000000000..4231d0ccc --- /dev/null +++ b/agreement_termination/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/agreement_termination/readme/CONTRIBUTORS.md b/agreement_termination/readme/CONTRIBUTORS.md new file mode 100644 index 000000000..01af77936 --- /dev/null +++ b/agreement_termination/readme/CONTRIBUTORS.md @@ -0,0 +1,5 @@ +- Pavlov Media +- Open Source Integrators +- Yves Goldberg (Ygol Internetwork) +- [glueckkanja AG](https://www.glueckkanja.com/): + - Christopher Rogos \ No newline at end of file diff --git a/agreement_termination/readme/DESCRIPTION.md b/agreement_termination/readme/DESCRIPTION.md new file mode 100644 index 000000000..e47a58c0f --- /dev/null +++ b/agreement_termination/readme/DESCRIPTION.md @@ -0,0 +1,10 @@ +This module adds the "Term Dates" section to legal agreements, grouping the +signature/start/end dates together with the expiration and change notice +periods, the notification address and the termination tracking dates +(termination requested / terminated). + +It also adds a review date to each agreement, computed as the end date minus +the expiration notice. A daily cron schedules a "review" activity on that date, +assigned to the user who last modified the agreement. + +![Agreement Form](../static/description/screenshot_form.png) \ No newline at end of file diff --git a/agreement_termination/static/description/icon.png b/agreement_termination/static/description/icon.png new file mode 100644 index 000000000..3a0328b51 Binary files /dev/null and b/agreement_termination/static/description/icon.png differ diff --git a/agreement_termination/static/description/index.html b/agreement_termination/static/description/index.html new file mode 100644 index 000000000..e50800088 --- /dev/null +++ b/agreement_termination/static/description/index.html @@ -0,0 +1,444 @@ + + + + + +README.rst + + + +
+ + + +Odoo Community Association + +
+

Agreement Termination

+ +

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

+

This module adds the “Term Dates” section to legal agreements, grouping +the signature/start/end dates together with the expiration and change +notice periods, the notification address and the termination tracking +dates (termination requested / terminated).

+

It also adds a review date to each agreement, computed as the end date +minus the expiration notice. A daily cron schedules a “review” activity +on that date, assigned to the user who last modified the agreement.

+Agreement Form +

Table of contents

+ +
+

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

+
    +
  • Pavlov Media
  • +
  • Open Source Integrators
  • +
  • Yves Goldberg (Ygol Internetwork)
  • +
+
+
+

Contributors

+
    +
  • Pavlov Media
  • +
  • Open Source Integrators
  • +
  • Yves Goldberg (Ygol Internetwork)
  • +
  • glueckkanja AG:
      +
    • Christopher Rogos
    • +
    +
  • +
+
+
+

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

+

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

+
+
+
+
+ + diff --git a/agreement_termination/static/description/screenshot_form.png b/agreement_termination/static/description/screenshot_form.png new file mode 100644 index 000000000..278a6f951 Binary files /dev/null and b/agreement_termination/static/description/screenshot_form.png differ diff --git a/agreement_termination/tests/__init__.py b/agreement_termination/tests/__init__.py new file mode 100644 index 000000000..a83fe161b --- /dev/null +++ b/agreement_termination/tests/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html) + +from . import test_agreement_termination diff --git a/agreement_termination/tests/test_agreement_termination.py b/agreement_termination/tests/test_agreement_termination.py new file mode 100644 index 000000000..73a2b4623 --- /dev/null +++ b/agreement_termination/tests/test_agreement_termination.py @@ -0,0 +1,74 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html) + +from datetime import timedelta + +from odoo import fields +from odoo.tests.common import TransactionCase + + +class TestAgreementTermination(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) + cls.activity_type = cls.env.ref( + "agreement_termination.mail_activity_review_agreement" + ) + cls.partner = cls.env["res.partner"].create({"name": "TestCustomer"}) + cls.agreement = cls.env["agreement"].create( + { + "name": "TestAgreement", + "code": "TA001", + "partner_id": cls.partner.id, + "start_date": fields.Date.today(), + "end_date": fields.Date.today() + timedelta(days=365), + "expiration_notice": 30, + } + ) + + def _review_activities(self): + return self.agreement.activity_ids.filtered( + lambda a: a.activity_type_id == self.activity_type + ) + + # The review date is the end date minus the expiration notice + def test_compute_to_review_date(self): + self.assertEqual( + self.agreement.to_review_date, + self.agreement.end_date - timedelta(days=30), + ) + self.agreement.expiration_notice = 10 + self.assertEqual( + self.agreement.to_review_date, + self.agreement.end_date - timedelta(days=10), + ) + + # The cron assigns a review activity to the last modifier, once only + def test_cron_assigns_last_modifier(self): + self.env["agreement"]._alert_to_review_date() + self.assertFalse(self._review_activities()) + + modifier = self.env["res.users"].create( + {"name": "Last Modifier", "login": "last_modifier"} + ) + self.agreement.with_user(modifier).sudo().write( + {"to_review_date": fields.Date.today()} + ) + self.assertEqual(self.agreement.write_uid, modifier) + + self.env["agreement"]._alert_to_review_date() + activities = self._review_activities() + self.assertEqual(len(activities), 1) + self.assertEqual(activities.user_id, modifier) + + # Running the cron again does not duplicate the activity + self.env["agreement"]._alert_to_review_date() + self.assertEqual(len(self._review_activities()), 1) + + # Templates never get a review activity + def test_cron_skips_templates(self): + self.agreement.write( + {"is_template": True, "to_review_date": fields.Date.today()} + ) + self.env["agreement"]._alert_to_review_date() + self.assertFalse(self._review_activities()) diff --git a/agreement_termination/views/agreement.xml b/agreement_termination/views/agreement.xml new file mode 100644 index 000000000..a8c1a9b77 --- /dev/null +++ b/agreement_termination/views/agreement.xml @@ -0,0 +1,44 @@ + + + + agreement.form.termination + agreement + + + + + + + + + + + + + + + + + + + agreement.search.termination + agreement + + + + + + + + diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 000000000..b69ba2b97 --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1 @@ +odoo-addon-agreement @ git+https://github.com/oca/agreement.git@refs/pull/141/head#subdirectory=agreement