From 69e55112eec39da55ce83d3336146dea58b503b4 Mon Sep 17 00:00:00 2001 From: Nikos Tsirintanis Date: Wed, 12 Nov 2025 10:29:01 +0100 Subject: [PATCH 1/9] [ADD] partner_archive_propagate --- partner_archive_propagate/README.rst | 122 +++++ partner_archive_propagate/__init__.py | 2 + partner_archive_propagate/__manifest__.py | 21 + partner_archive_propagate/models/__init__.py | 2 + .../models/res_config_settings.py | 15 + .../models/res_partner.py | 190 +++++++ .../readme/CONFIGURE.rst | 3 + .../readme/CONTRIBUTORS.rst | 1 + .../readme/DESCRIPTION.rst | 19 + partner_archive_propagate/readme/USAGE.rst | 9 + .../security/ir.model.access.csv | 3 + .../static/description/icon.png | Bin 0 -> 9455 bytes .../static/description/index.html | 468 ++++++++++++++++++ partner_archive_propagate/tests/__init__.py | 1 + .../tests/test_archive.py | 217 ++++++++ .../views/res_config_settings_views.xml | 33 ++ .../views/res_partner_views.xml | 23 + partner_archive_propagate/wizard/__init__.py | 1 + .../wizard/archive_propagate_wizard.py | 64 +++ .../wizard/archive_propagate_wizard_views.xml | 40 ++ 20 files changed, 1234 insertions(+) create mode 100644 partner_archive_propagate/README.rst create mode 100644 partner_archive_propagate/__init__.py create mode 100644 partner_archive_propagate/__manifest__.py create mode 100644 partner_archive_propagate/models/__init__.py create mode 100644 partner_archive_propagate/models/res_config_settings.py create mode 100644 partner_archive_propagate/models/res_partner.py create mode 100644 partner_archive_propagate/readme/CONFIGURE.rst create mode 100644 partner_archive_propagate/readme/CONTRIBUTORS.rst create mode 100644 partner_archive_propagate/readme/DESCRIPTION.rst create mode 100644 partner_archive_propagate/readme/USAGE.rst create mode 100644 partner_archive_propagate/security/ir.model.access.csv create mode 100644 partner_archive_propagate/static/description/icon.png create mode 100644 partner_archive_propagate/static/description/index.html create mode 100644 partner_archive_propagate/tests/__init__.py create mode 100644 partner_archive_propagate/tests/test_archive.py create mode 100644 partner_archive_propagate/views/res_config_settings_views.xml create mode 100644 partner_archive_propagate/views/res_partner_views.xml create mode 100644 partner_archive_propagate/wizard/__init__.py create mode 100644 partner_archive_propagate/wizard/archive_propagate_wizard.py create mode 100644 partner_archive_propagate/wizard/archive_propagate_wizard_views.xml diff --git a/partner_archive_propagate/README.rst b/partner_archive_propagate/README.rst new file mode 100644 index 00000000000..3944228248d --- /dev/null +++ b/partner_archive_propagate/README.rst @@ -0,0 +1,122 @@ +========================= +Partner Archive Propagate +========================= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:c42eb379dc500d3abd60b7719c69402a64a470aa9aabf2d00446158e080d99d0 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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%2Fpartner--contact-lightgray.png?logo=github + :target: https://github.com/OCA/partner-contact/tree/16.0/partner_archive_propagate + :alt: OCA/partner-contact +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/partner-contact-16-0/partner-contact-16-0-partner_archive_propagate + :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/partner-contact&target_branch=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +=========================== +Partner Archive Propagation +=========================== + +This module extends the native archiving mechanism for partners. + +When archiving a company or parent contact, it will also handle its descendants +according to business rules — with user control and safeguards. + +Features +-------- + +* Adds a new **"Archive Contact and Children"** button on the Partner form. +* Shows a **wizard** listing contact-type descendants before archiving. +* Automatically skips descendants linked to active users. +* Adds a technical Many2one field (propagated_from_id) that records which parent partner caused the automatic archiving, making propagation fully traceable and reversible. +* Ensures automatic unarchive propagation. +* Includes a system setting to enforce propagation even for non-UI actions + (imports, RPC, automated jobs, etc.). + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +* Go to **Settings → Technical → General Settings → Partner Archive Propagate**. +* Enable *"Force propagation outside UI"* to automatically apply propagation + when partners are archived through automation or imports. + +Usage +===== + +1. Open a partner (company or main contact). +2. Click the **"Archive Contact and Children"** button. +3. Review the list of child contacts to be archived. +4. Confirm the action. + * Non-contact types (e.g., invoice/delivery addresses) are archived silently. + * Contact-type descendants appear in the wizard for review. + +Unarchiving also follows propagation rules: +if a parent partner is unarchived, its propagated descendants are unarchived as well. + +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 +~~~~~~~ + +* Therp BV + +Contributors +~~~~~~~~~~~~ + +* Nikos Tsirintanis + +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-ntsirintanis| image:: https://github.com/ntsirintanis.png?size=40px + :target: https://github.com/ntsirintanis + :alt: ntsirintanis + +Current `maintainer `__: + +|maintainer-ntsirintanis| + +This module is part of the `OCA/partner-contact `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/partner_archive_propagate/__init__.py b/partner_archive_propagate/__init__.py new file mode 100644 index 00000000000..9b4296142f4 --- /dev/null +++ b/partner_archive_propagate/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizard diff --git a/partner_archive_propagate/__manifest__.py b/partner_archive_propagate/__manifest__.py new file mode 100644 index 00000000000..b8346c36e73 --- /dev/null +++ b/partner_archive_propagate/__manifest__.py @@ -0,0 +1,21 @@ +# Copyright 2025 Therp BV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +{ + "name": "Partner Archive Propagate", + "summary": "Archive/unarchive partner contacts hierarchically", + "version": "16.0.1.0.0", + "category": "Partner Management", + "author": "Therp BV, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/partner-contact", + "license": "AGPL-3", + "depends": ["base", "mail"], + "data": [ + "security/ir.model.access.csv", + "views/res_config_settings_views.xml", + "views/res_partner_views.xml", + "wizard/archive_propagate_wizard_views.xml", + ], + "installable": True, + "application": False, + "maintainers": ["ntsirintanis"], +} diff --git a/partner_archive_propagate/models/__init__.py b/partner_archive_propagate/models/__init__.py new file mode 100644 index 00000000000..0d7a048a3c9 --- /dev/null +++ b/partner_archive_propagate/models/__init__.py @@ -0,0 +1,2 @@ +from . import res_partner +from . import res_config_settings diff --git a/partner_archive_propagate/models/res_config_settings.py b/partner_archive_propagate/models/res_config_settings.py new file mode 100644 index 00000000000..3095eeac014 --- /dev/null +++ b/partner_archive_propagate/models/res_config_settings.py @@ -0,0 +1,15 @@ +# Copyright 2025 Therp BV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + partner_archive_force_outside_ui = fields.Boolean( + string="Force propagation outside UI", + help="If enabled, archiving a partner via any non-UI method (imports, RPC, automated) " + "will also archive all descendants and mark them as propagated.", + config_parameter="partner_archive_propagate.force_outside_ui", + default=False, + ) diff --git a/partner_archive_propagate/models/res_partner.py b/partner_archive_propagate/models/res_partner.py new file mode 100644 index 00000000000..bb24ebb1590 --- /dev/null +++ b/partner_archive_propagate/models/res_partner.py @@ -0,0 +1,190 @@ +# Copyright 2025 Therp BV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import _, api, fields, models + + +class ResPartner(models.Model): + _inherit = "res.partner" + + propagated_from_id = fields.Many2one( + "res.partner", + string="Archived Due To", + index=True, + help=( + "Technical field. Set when this partner was archived automatically " + "because the source partner was archived. " + "Cleared again when it is unarchived by the opposite propagation." + ), + ) + + show_prop_wizard_button = fields.Boolean( + compute="_compute_show_prop_wizard_button", + help="Show or hide the wizard button, depending on child_ids", + ) + + def _compute_show_prop_wizard_button(self): + for partner in self: + partner.show_prop_wizard_button = bool(partner.child_ids) + + def write(self, vals): + """Enable (un)archiving propagation: + - detect which partners are being (un)archived + - calls _(un)archive_propagate() on them. + """ + archiving_partners = self.browse() + unarchiving_partners = self.browse() + if "active" in vals: + if vals["active"] is False: + archiving_partners = self.filtered(lambda self: self.active) + elif vals["active"] is True: + unarchiving_partners = self.filtered(lambda self: not self.active) + res = super().write(vals) + from_wizard = self.env.context.get("partner_archive_propagate_ui") + non_ui = self._force_non_ui() + # Archiving + if archiving_partners: + if from_wizard: + archiving_partners._archive_propagate_wizard() + elif non_ui: + archiving_partners._archive_propagate_external() + # Unarchiving, always propagate + if unarchiving_partners: + unarchiving_partners._unarchive_propagate() + return res + + def _archive_propagate_wizard(self): + """Archive descendants according to propagation rules.""" + # Wizard: only non-contact descendants are silently archived here. + for partner in self: + non_contacts = ( + partner._get_descendants() + .sudo() + .filtered(lambda p: p.active and p.type != "contact") + ) + if not non_contacts: + continue + ( + archivable, + unarchivable, + ) = non_contacts._split_archivable_unarchivable_user() + archivable.write( + { + "active": False, + "propagated_from_id": partner.id, + } + ) + partner._notify_skipped_partners(unarchivable) + + def _archive_propagate_external(self): + """Archive descendants according to propagation rules.""" + # Non-UI: archive all children + for partner in self: + descendants = partner._get_descendants().sudo().filtered(lambda p: p.active) + if not descendants: + continue + ( + archivable, + unarchivable, + ) = descendants._split_archivable_unarchivable_user() + archivable.write( + { + "active": False, + "propagated_from_id": partner.id, + } + ) + partner._notify_skipped_partners(unarchivable) + + def _unarchive_propagate(self): + """Unarchive descendants archived via propagation. + Only touches partners where propagated_from_id points to the unarchived + parent, and that are currently inactive. + """ + to_unarchive = ( + self.env["res.partner"] + .with_context(active_test=False) + .sudo() + .search( + [ + ("propagated_from_id", "in", self.ids), + ("active", "=", False), + ] + ) + ) + if not to_unarchive: + return + to_unarchive.write( + { + "active": True, + "propagated_from_id": False, + } + ) + + def _get_descendants(self): + """Return only active children.""" + self.ensure_one() + return self.env["res.partner"].search( + [("id", "child_of", self.ids), ("id", "!=", self.id)] + ) + + def _split_archivable_unarchivable_user(self): + """Find which partners are archivable (i.e., no active users) and unarchivable.""" + archivable = self.browse() + unarchivable = self.browse() + for partner in self: + if partner.sudo().user_ids.filtered(lambda u: u.active): + unarchivable |= partner + continue + archivable |= partner + return archivable, unarchivable + + def _notify_skipped_partners(self, unarchivable_partners): + """Message listing partners skipped due to active users.""" + unarchivable_partners = unarchivable_partners or self.browse() + if not unarchivable_partners: + return + self.message_post( + body=_( + "Skipped archiving the following contacts " + "because they are linked to active users: %s" + ) + % ", ".join(unarchivable_partners.mapped("name")) + ) + + @api.model + def _force_non_ui(self): + """Read system setting controlling external propagation.""" + return self.env["ir.config_parameter"].sudo().get_param( + "partner_archive_propagate.force_outside_ui", default="0" + ) in ("1", "true", "True") + + def action_archive_with_contacts(self): + """Show wizard only if there are type contact descendants. + + - If there are no type contact descendants: archive immediately. + - If there are type contact descendants: open wizard listing only those, + allowing the user to exclude some. + """ + self.ensure_one() + descendants = self._get_descendants().filtered(lambda p: p.active) + contact_desc = descendants.filtered(lambda p: p.type == "contact") + if not contact_desc: + # No wizard window opens, archive right away + return self.with_context(partner_archive_propagate_ui=True).write( + {"active": False} + ) + archivable, unarchivable = contact_desc._split_archivable_unarchivable_user() + wiz = self.env["res.partner.archive.propagate.wizard"].create( + { + "partner_id": self.id, + "line_ids": [(0, 0, {"partner_id": p.id}) for p in archivable], + } + ) + return { + "type": "ir.actions.act_window", + "name": _("Archive Contacts"), + "res_model": "res.partner.archive.propagate.wizard", + "view_mode": "form", + "target": "new", + "res_id": wiz.id, + } diff --git a/partner_archive_propagate/readme/CONFIGURE.rst b/partner_archive_propagate/readme/CONFIGURE.rst new file mode 100644 index 00000000000..5b2fedacf53 --- /dev/null +++ b/partner_archive_propagate/readme/CONFIGURE.rst @@ -0,0 +1,3 @@ +* Go to **Settings → Technical → General Settings → Partner Archive Propagate**. +* Enable *"Force propagation outside UI"* to automatically apply propagation + when partners are archived through automation or imports. diff --git a/partner_archive_propagate/readme/CONTRIBUTORS.rst b/partner_archive_propagate/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000000..31fdb1fd5ad --- /dev/null +++ b/partner_archive_propagate/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Nikos Tsirintanis \ No newline at end of file diff --git a/partner_archive_propagate/readme/DESCRIPTION.rst b/partner_archive_propagate/readme/DESCRIPTION.rst new file mode 100644 index 00000000000..4b3feaacd0f --- /dev/null +++ b/partner_archive_propagate/readme/DESCRIPTION.rst @@ -0,0 +1,19 @@ +=========================== +Partner Archive Propagation +=========================== + +This module extends the native archiving mechanism for partners. + +When archiving a company or parent contact, it will also handle its descendants +according to business rules — with user control and safeguards. + +Features +-------- + +* Adds a new **"Archive Contact and Children"** button on the Partner form. +* Shows a **wizard** listing contact-type descendants before archiving. +* Automatically skips descendants linked to active users. +* Adds a technical Many2one field (propagated_from_id) that records which parent partner caused the automatic archiving, making propagation fully traceable and reversible. +* Ensures automatic unarchive propagation. +* Includes a system setting to enforce propagation even for non-UI actions + (imports, RPC, automated jobs, etc.). diff --git a/partner_archive_propagate/readme/USAGE.rst b/partner_archive_propagate/readme/USAGE.rst new file mode 100644 index 00000000000..78c97822e94 --- /dev/null +++ b/partner_archive_propagate/readme/USAGE.rst @@ -0,0 +1,9 @@ +1. Open a partner (company or main contact). +2. Click the **"Archive Contact and Children"** button. +3. Review the list of child contacts to be archived. +4. Confirm the action. + * Non-contact types (e.g., invoice/delivery addresses) are archived silently. + * Contact-type descendants appear in the wizard for review. + +Unarchiving also follows propagation rules: +if a parent partner is unarchived, its propagated descendants are unarchived as well. diff --git a/partner_archive_propagate/security/ir.model.access.csv b/partner_archive_propagate/security/ir.model.access.csv new file mode 100644 index 00000000000..e17837ca69a --- /dev/null +++ b/partner_archive_propagate/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_archive_line,access_archive_line,model_res_partner_archive_propagate_line,base.group_user,1,1,1,1 +access_archive_wizard,access_archive_wizard,model_res_partner_archive_propagate_wizard,base.group_user,1,1,1,1 diff --git a/partner_archive_propagate/static/description/icon.png b/partner_archive_propagate/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3a0328b516c4980e8e44cdb63fd945757ddd132d GIT binary patch literal 9455 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 z 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 Xviia!U7SGha1wx#SCgwmn*{w2TRX*I literal 0 HcmV?d00001 diff --git a/partner_archive_propagate/static/description/index.html b/partner_archive_propagate/static/description/index.html new file mode 100644 index 00000000000..f7e9e467289 --- /dev/null +++ b/partner_archive_propagate/static/description/index.html @@ -0,0 +1,468 @@ + + + + + +README.rst + + + +
+ + +
+

Partner Archive Propagate

+ +

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

+
+
+

Partner Archive Propagation

+

This module extends the native archiving mechanism for partners.

+

When archiving a company or parent contact, it will also handle its descendants +according to business rules — with user control and safeguards.

+
+

Features

+
    +
  • Adds a new “Archive Contact and Children” button on the Partner form.
  • +
  • Shows a wizard listing contact-type descendants before archiving.
  • +
  • Automatically skips descendants linked to active users.
  • +
  • Adds a technical Many2one field (propagated_from_id) that records which parent partner caused the automatic archiving, making propagation fully traceable and reversible.
  • +
  • Ensures automatic unarchive propagation.
  • +
  • Includes a system setting to enforce propagation even for non-UI actions +(imports, RPC, automated jobs, etc.).
  • +
+

Table of contents

+ +
+

Configuration

+
    +
  • Go to Settings → Technical → General Settings → Partner Archive Propagate.
  • +
  • Enable “Force propagation outside UI” to automatically apply propagation +when partners are archived through automation or imports.
  • +
+
+
+

Usage

+
    +
  1. Open a partner (company or main contact).
  2. +
  3. Click the “Archive Contact and Children” button.
  4. +
  5. Review the list of child contacts to be archived.
  6. +
  7. Confirm the action. +* Non-contact types (e.g., invoice/delivery addresses) are archived silently. +* Contact-type descendants appear in the wizard for review.
  8. +
+

Unarchiving also follows propagation rules: +if a parent partner is unarchived, its propagated descendants are unarchived as well.

+
+
+

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

+
    +
  • Therp BV
  • +
+
+
+

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 maintainer:

+

ntsirintanis

+

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

+

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

+
+
+
+
+
+ + diff --git a/partner_archive_propagate/tests/__init__.py b/partner_archive_propagate/tests/__init__.py new file mode 100644 index 00000000000..1b03810aab1 --- /dev/null +++ b/partner_archive_propagate/tests/__init__.py @@ -0,0 +1 @@ +from . import test_archive diff --git a/partner_archive_propagate/tests/test_archive.py b/partner_archive_propagate/tests/test_archive.py new file mode 100644 index 00000000000..0a361025967 --- /dev/null +++ b/partner_archive_propagate/tests/test_archive.py @@ -0,0 +1,217 @@ +# Copyright 2025 Therp BV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from odoo.tests.common import TransactionCase + + +class TestPartnerArchivePropagate(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + # just sudo everywhere + cls.env = cls.env(su=True) + Partners = cls.env["res.partner"] + # Parent: A (company) + # Children: B(contact), C(delivery), D(invoice), E(child of B, contact) + cls.A = Partners.create({"name": "A", "is_company": True}) + cls.B = Partners.create({"name": "B", "parent_id": cls.A.id, "type": "contact"}) + cls.C = Partners.create( + {"name": "C", "parent_id": cls.A.id, "type": "delivery"} + ) + cls.D = Partners.create({"name": "D", "parent_id": cls.A.id, "type": "invoice"}) + cls.E = Partners.create({"name": "E", "parent_id": cls.B.id, "type": "contact"}) + + def _create_active_user_for_partner(self, partner): + """Create an active user linked to given partner to test unarchivable situations""" + return ( + self.env["res.users"] + .sudo() + .create( + { + "name": partner.name, + "login": "test@test.test", + "partner_id": partner.id, + "email": "test@tset.org", + } + ) + ) + + def test_archive_non_contact_type(self): + """If there are no type contact descendants, + the wizard should not open and archiving should proceed immediately. + """ + # turn B/E into non-contact types so only non-contacts exist + self.B.write({"type": "delivery"}) + self.E.write({"type": "invoice"}) + # launch wizard from company, but no children are type contact + res = self.A.action_archive_with_contacts() + self.assertTrue(res in (True, None)) + self.assertFalse(self.A.active) + # All descendants of non-contact type should be archived and flagged + for p in (self.B, self.C, self.D, self.E): + self.assertFalse(p.active) + self.assertEqual( + p.propagated_from_id, + self.A, + "Descendant should be archived with propagated_from_id=A", + ) + # Revert types for other tests + self.B.write({"type": "contact"}) + self.E.write({"type": "contact"}) + # Unarchive company, flagged descendants must be restored + self.A.write({"active": True}) + for p in (self.B, self.C, self.D, self.E): + self.assertTrue(p.active) + self.assertFalse( + bool(p.propagated_from_id), + "propagated_from_id should be cleared on unarchive", + ) + + def test_archive_contact_types_skip_active_user(self): + """Wizard opens for type contact children. + Children linked to active users are skipped. + """ + # Ensure we have contact descendants + self.B.write({"type": "contact"}) + self.E.write({"type": "contact"}) + # Block B by linking an active user + self._create_active_user_for_partner(self.B) + action = self.A.action_archive_with_contacts() + self.assertEqual( + action.get("res_model"), "res.partner.archive.propagate.wizard" + ) + # Load wizard and confirm + Wiz = ( + self.env["res.partner.archive.propagate.wizard"] + .sudo() + .browse(action["res_id"]) + ) + # Only archivable contacts should be in the lines + line_partners = Wiz.line_ids.mapped("partner_id") + self.assertIn(self.E, line_partners) + self.assertNotIn(self.B, line_partners) + result = Wiz.action_confirm() + # The window closes, silently + self.assertEqual(result, {"type": "ir.actions.act_window_close"}) + # Parent should be inactive + self.assertFalse(self.A.active) + # B is linked to active user, partner remains active and unflagged + self.assertTrue(self.B.active) + self.assertFalse(bool(self.B.propagated_from_id)) + # E should be archived and flagged from A + self.assertFalse(self.E.active) + self.assertEqual(self.E.propagated_from_id, self.A) + # C and D (type is not contact) archived silently and flagged from A + for p in (self.C, self.D): + self.assertFalse(p.active) + self.assertEqual(p.propagated_from_id, self.A) + # Unarchive parent: flagged ones E/C/D should become active, B stays active anyway + self.A.write({"active": True}) + for p in (self.E, self.C, self.D): + self.assertTrue(p.active) + self.assertFalse(bool(p.propagated_from_id)) + self.assertTrue(self.B.active) + self.assertFalse(bool(self.B.propagated_from_id)) + + def test_archive_no_wizard_force_setting_propagates(self): + """When force_outside_ui is enabled, + all operations except wizard stuff can archive children + """ + icp = self.env["ir.config_parameter"].sudo() + icp.set_param("partner_archive_propagate.force_outside_ui", "1") + # Ensure clean active state + for p in (self.A, self.B, self.C, self.D, self.E): + p.write({"active": True, "propagated_from_id": False}) + # Archive without UI + self.A.write({"active": False}) + self.assertFalse(self.A.active) + for p in (self.B, self.C, self.D, self.E): + self.assertFalse(p.active) + self.assertEqual(p.propagated_from_id, self.A) + # Unarchive parent restores flagged children + self.A.write({"active": True}) + for p in (self.B, self.C, self.D, self.E): + self.assertTrue(p.active) + self.assertFalse(bool(p.propagated_from_id)) + # Reset the setting + icp.set_param("partner_archive_propagate.force_outside_ui", "0") + + def test_manual_archive_does_not_toggle_flag(self): + """Archiving a partner manually should NOT toggle propagated_from_id on that record.""" + # Ensure clean + self.B.write({"active": True, "propagated_from_id": False}) + # Manual archive of B, force_outside_ui is off + self.env["ir.config_parameter"].sudo().set_param( + "partner_archive_propagate.force_outside_ui", "0" + ) + self.B.write({"active": False}) + self.assertFalse(self.B.active) + self.assertFalse(bool(self.B.propagated_from_id)) + # Manual unarchive keeps it False + self.B.write({"active": True}) + self.assertTrue(self.B.active) + self.assertFalse(bool(self.B.propagated_from_id)) + + def test_notify_skipped_partners_posts_message(self): + """_notify_skipped_partners should post a message listing skipped partners.""" + before = len(self.A.message_ids) + # Calling with empty recordset should do exactly nothing + empty_partners = self.env["res.partner"] + self.A._notify_skipped_partners(empty_partners) + self.assertEqual( + len(self.A.message_ids), + before, + ) + # Calling with some skipped partners should create exactly one message + skipped = self.B | self.E + self.A._notify_skipped_partners(skipped) + self.assertEqual( + len(self.A.message_ids), + before + 1, + ) + msg = self.A.message_ids.sorted("id")[-1] + self.assertIn( + "Skipped archiving the following contacts", + msg.body, + ) + self.assertIn(self.B.name, msg.body) + self.assertIn(self.E.name, msg.body) + + def test_wizard_confirm_mixed_notification(self): + """action_confirm should use split_archivable_unarchivable_user and + return a display_notification when there are unarchivable partners. + """ + self.B.write({"type": "contact", "active": True, "propagated_from_id": False}) + self.E.write({"type": "contact", "active": True, "propagated_from_id": False}) + self._create_active_user_for_partner(self.B) + Wiz = ( + self.env["res.partner.archive.propagate.wizard"] + .sudo() + .create( + { + "partner_id": self.A.id, + "line_ids": [ + (0, 0, {"partner_id": self.B.id}), + (0, 0, {"partner_id": self.E.id}), + ], + } + ) + ) + + result = Wiz.action_confirm() + # Parent should be archived by the wizard + self.assertFalse(self.A.active) + # E must be archived and flagged from A + self.assertFalse(self.E.active) + self.assertEqual(self.E.propagated_from_id, self.A) + # B must stay active and unflagged + self.assertTrue(self.B.active) + self.assertFalse(bool(self.B.propagated_from_id)) + # Result must be a display_notification about skipped contacts + self.assertIsInstance(result, dict) + self.assertEqual(result.get("type"), "ir.actions.client") + self.assertEqual(result.get("tag"), "display_notification") + params = result.get("params", {}) + self.assertIn("message", params) + self.assertIn("Skipped contacts linked to active users", params["message"]) + # for partner B + self.assertIn(self.B.name, params["message"]) diff --git a/partner_archive_propagate/views/res_config_settings_views.xml b/partner_archive_propagate/views/res_config_settings_views.xml new file mode 100644 index 00000000000..f840d0fb315 --- /dev/null +++ b/partner_archive_propagate/views/res_config_settings_views.xml @@ -0,0 +1,33 @@ + + + res.config.settings.view.form.partner.archive + res.config.settings + + + + +
+

Partner Archive Propagate

+
+
+
+ +
+
+
+
+
+
+
+
+
+
diff --git a/partner_archive_propagate/views/res_partner_views.xml b/partner_archive_propagate/views/res_partner_views.xml new file mode 100644 index 00000000000..0610312887c --- /dev/null +++ b/partner_archive_propagate/views/res_partner_views.xml @@ -0,0 +1,23 @@ + + + res.partner.form.archive.propagate + res.partner + + + +
+ +
+
+
+
+
diff --git a/partner_archive_propagate/wizard/__init__.py b/partner_archive_propagate/wizard/__init__.py new file mode 100644 index 00000000000..c888ea59064 --- /dev/null +++ b/partner_archive_propagate/wizard/__init__.py @@ -0,0 +1 @@ +from . import archive_propagate_wizard diff --git a/partner_archive_propagate/wizard/archive_propagate_wizard.py b/partner_archive_propagate/wizard/archive_propagate_wizard.py new file mode 100644 index 00000000000..f7ddcd17546 --- /dev/null +++ b/partner_archive_propagate/wizard/archive_propagate_wizard.py @@ -0,0 +1,64 @@ +# Copyright 2025 Therp BV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import _, fields, models + + +class ArchivePropagateWizard(models.TransientModel): + _name = "res.partner.archive.propagate.wizard" + _description = "Archive partner and children of partner " + + partner_id = fields.Many2one("res.partner", required=True, readonly=True) + line_ids = fields.One2many( + "res.partner.archive.propagate.line", "wizard_id", string="Contacts to archive" + ) + + def action_confirm(self): + """Archive main partner from the ui: + - silently archive NON-contact type descendants (invoice/delivery/etc) + - skip descendants linked to active users and notify + """ + self.ensure_one() + self.partner_id.with_context(partner_archive_propagate_ui=True).write( + {"active": False} + ) + partners = ( + self.line_ids.mapped("partner_id").sudo().filtered(lambda p: p.active) + ) + archivable, unarchivable = partners._split_archivable_unarchivable_user() + archivable.write( + { + "active": False, + "propagated_from_id": self.partner_id.id, + } + ) + self.partner_id._notify_skipped_partners(unarchivable) + if unarchivable: + # return a nice message on ui too + return { + "type": "ir.actions.client", + "tag": "display_notification", + "params": { + "title": _("Some contacts were skipped"), + "message": _("Skipped contacts linked to active users: %s") + % ", ".join(unarchivable.mapped("name")), + "type": "warning", + "sticky": False, + }, + } + return {"type": "ir.actions.act_window_close"} + + +class ArchiveLine(models.TransientModel): + _name = "res.partner.archive.propagate.line" + _description = "Partner Archive Propagate Line" + + wizard_id = fields.Many2one( + "res.partner.archive.propagate.wizard", + required=True, + ondelete="cascade", + ) + partner_id = fields.Many2one("res.partner", required=True, string="Contact") + name = fields.Char(related="partner_id.name") + email = fields.Char(related="partner_id.email") + phone = fields.Char(related="partner_id.phone") diff --git a/partner_archive_propagate/wizard/archive_propagate_wizard_views.xml b/partner_archive_propagate/wizard/archive_propagate_wizard_views.xml new file mode 100644 index 00000000000..eed13366c36 --- /dev/null +++ b/partner_archive_propagate/wizard/archive_propagate_wizard_views.xml @@ -0,0 +1,40 @@ + + + res.partner.archive.propagate.wizard.form + res.partner.archive.propagate.wizard + +
+ + + + + + + + +
+
+
+
+
+
From b377cec9a7602c01eca065345e701f88e51f3671 Mon Sep 17 00:00:00 2001 From: oca-ci Date: Thu, 14 May 2026 09:46:51 +0000 Subject: [PATCH 2/9] [UPD] Update partner_archive_propagate.pot --- .../i18n/partner_archive_propagate.pot | 211 ++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 partner_archive_propagate/i18n/partner_archive_propagate.pot diff --git a/partner_archive_propagate/i18n/partner_archive_propagate.pot b/partner_archive_propagate/i18n/partner_archive_propagate.pot new file mode 100644 index 00000000000..255a5336fd2 --- /dev/null +++ b/partner_archive_propagate/i18n/partner_archive_propagate.pot @@ -0,0 +1,211 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * partner_archive_propagate +# +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: partner_archive_propagate +#: model_terms:ir.ui.view,arch_db:partner_archive_propagate.view_archive_propagate_wizard_form +msgid "Archive" +msgstr "" + +#. module: partner_archive_propagate +#: model_terms:ir.ui.view,arch_db:partner_archive_propagate.view_partner_form_inherit_archive_propagate +msgid "Archive Contact and Children" +msgstr "" + +#. module: partner_archive_propagate +#. odoo-python +#: code:addons/partner_archive_propagate/models/res_partner.py:0 +#: model_terms:ir.ui.view,arch_db:partner_archive_propagate.view_archive_propagate_wizard_form +#, python-format +msgid "Archive Contacts" +msgstr "" + +#. module: partner_archive_propagate +#: model:ir.model,name:partner_archive_propagate.model_res_partner_archive_propagate_wizard +msgid "Archive partner and children of partner " +msgstr "" + +#. module: partner_archive_propagate +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_partner__propagated_from_id +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_users__propagated_from_id +msgid "Archived Due To" +msgstr "" + +#. module: partner_archive_propagate +#: model_terms:ir.ui.view,arch_db:partner_archive_propagate.view_archive_propagate_wizard_form +msgid "Cancel" +msgstr "" + +#. module: partner_archive_propagate +#: model:ir.model,name:partner_archive_propagate.model_res_config_settings +msgid "Config Settings" +msgstr "" + +#. module: partner_archive_propagate +#: model:ir.model,name:partner_archive_propagate.model_res_partner +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_partner_archive_propagate_line__partner_id +msgid "Contact" +msgstr "" + +#. module: partner_archive_propagate +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_partner_archive_propagate_wizard__line_ids +msgid "Contacts to archive" +msgstr "" + +#. module: partner_archive_propagate +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_partner_archive_propagate_line__create_uid +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_partner_archive_propagate_wizard__create_uid +msgid "Created by" +msgstr "" + +#. module: partner_archive_propagate +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_partner_archive_propagate_line__create_date +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_partner_archive_propagate_wizard__create_date +msgid "Created on" +msgstr "" + +#. module: partner_archive_propagate +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_partner_archive_propagate_line__display_name +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_partner_archive_propagate_wizard__display_name +msgid "Display Name" +msgstr "" + +#. module: partner_archive_propagate +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_partner_archive_propagate_line__email +msgid "Email" +msgstr "" + +#. module: partner_archive_propagate +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_config_settings__partner_archive_force_outside_ui +msgid "Force propagation outside UI" +msgstr "" + +#. module: partner_archive_propagate +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_partner_archive_propagate_line__id +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_partner_archive_propagate_wizard__id +msgid "ID" +msgstr "" + +#. module: partner_archive_propagate +#: model:ir.model.fields,help:partner_archive_propagate.field_res_config_settings__partner_archive_force_outside_ui +msgid "" +"If enabled, archiving a partner via any non-UI method (imports, RPC, " +"automated) will also archive all descendants and mark them as propagated." +msgstr "" + +#. module: partner_archive_propagate +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_partner_archive_propagate_line____last_update +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_partner_archive_propagate_wizard____last_update +msgid "Last Modified on" +msgstr "" + +#. module: partner_archive_propagate +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_partner_archive_propagate_line__write_uid +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_partner_archive_propagate_wizard__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: partner_archive_propagate +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_partner_archive_propagate_line__write_date +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_partner_archive_propagate_wizard__write_date +msgid "Last Updated on" +msgstr "" + +#. module: partner_archive_propagate +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_partner_archive_propagate_line__name +msgid "Name" +msgstr "" + +#. module: partner_archive_propagate +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_partner_archive_propagate_wizard__partner_id +msgid "Partner" +msgstr "" + +#. module: partner_archive_propagate +#: model_terms:ir.ui.view,arch_db:partner_archive_propagate.view_res_config_settings_partner_archive +msgid "Partner Archive Propagate" +msgstr "" + +#. module: partner_archive_propagate +#: model:ir.model,name:partner_archive_propagate.model_res_partner_archive_propagate_line +msgid "Partner Archive Propagate Line" +msgstr "" + +#. module: partner_archive_propagate +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_partner_archive_propagate_line__phone +msgid "Phone" +msgstr "" + +#. module: partner_archive_propagate +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_partner__show_prop_wizard_button +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_users__show_prop_wizard_button +msgid "Show Prop Wizard Button" +msgstr "" + +#. module: partner_archive_propagate +#: model:ir.model.fields,help:partner_archive_propagate.field_res_partner__show_prop_wizard_button +#: model:ir.model.fields,help:partner_archive_propagate.field_res_users__show_prop_wizard_button +msgid "Show or hide the wizard button, depending on child_ids" +msgstr "" + +#. module: partner_archive_propagate +#. odoo-python +#: code:addons/partner_archive_propagate/models/res_partner.py:0 +#, python-format +msgid "" +"Skipped archiving the following contacts because they are linked to active " +"users: %s" +msgstr "" + +#. module: partner_archive_propagate +#. odoo-python +#: code:addons/partner_archive_propagate/wizard/archive_propagate_wizard.py:0 +#, python-format +msgid "Skipped contacts linked to active users: %s" +msgstr "" + +#. module: partner_archive_propagate +#. odoo-python +#: code:addons/partner_archive_propagate/wizard/archive_propagate_wizard.py:0 +#, python-format +msgid "Some contacts were skipped" +msgstr "" + +#. module: partner_archive_propagate +#: model:ir.model.fields,help:partner_archive_propagate.field_res_partner__propagated_from_id +#: model:ir.model.fields,help:partner_archive_propagate.field_res_users__propagated_from_id +msgid "" +"Technical field. Set when this partner was archived automatically because " +"the source partner was archived. Cleared again when it is unarchived by the " +"opposite propagation." +msgstr "" + +#. module: partner_archive_propagate +#: model_terms:ir.ui.view,arch_db:partner_archive_propagate.view_archive_propagate_wizard_form +msgid "" +"The following contact-type descendants will be archived as well.\n" +" Remove any you want to keep active." +msgstr "" + +#. module: partner_archive_propagate +#: model_terms:ir.ui.view,arch_db:partner_archive_propagate.view_res_config_settings_partner_archive +msgid "" +"When enabled, archiving a partner via imports/RPC/automation will also\n" +" archive all descendants and mark them as propagated." +msgstr "" + +#. module: partner_archive_propagate +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_partner_archive_propagate_line__wizard_id +msgid "Wizard" +msgstr "" From 3ec4db67ed852e0df7e11ad2ff34c6157387441c Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Thu, 14 May 2026 09:52:38 +0000 Subject: [PATCH 3/9] [BOT] post-merge updates --- partner_archive_propagate/README.rst | 8 ++++++-- partner_archive_propagate/static/description/index.html | 7 +++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/partner_archive_propagate/README.rst b/partner_archive_propagate/README.rst index 3944228248d..c49a3aec872 100644 --- a/partner_archive_propagate/README.rst +++ b/partner_archive_propagate/README.rst @@ -1,3 +1,7 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + ========================= Partner Archive Propagate ========================= @@ -7,13 +11,13 @@ Partner Archive Propagate !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:c42eb379dc500d3abd60b7719c69402a64a470aa9aabf2d00446158e080d99d0 + !! source digest: sha256:0c17b904cc1bf10013f67e301084d9732e4a62d5bc5f32d9934fdad84cadb03e !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png :target: https://odoo-community.org/page/development-status :alt: Beta -.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png +.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fpartner--contact-lightgray.png?logo=github diff --git a/partner_archive_propagate/static/description/index.html b/partner_archive_propagate/static/description/index.html index f7e9e467289..2dd3138d5f7 100644 --- a/partner_archive_propagate/static/description/index.html +++ b/partner_archive_propagate/static/description/index.html @@ -363,15 +363,18 @@
+ +Odoo Community Association +

Partner Archive Propagate

-

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

+

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

Partner Archive Propagation

From 0a75bde798d065ad17af17f43fba56bddf4ab48d Mon Sep 17 00:00:00 2001 From: mymage Date: Thu, 14 May 2026 12:45:48 +0000 Subject: [PATCH 4/9] Added translation using Weblate (Italian) --- partner_archive_propagate/i18n/it.po | 212 +++++++++++++++++++++++++++ 1 file changed, 212 insertions(+) create mode 100644 partner_archive_propagate/i18n/it.po diff --git a/partner_archive_propagate/i18n/it.po b/partner_archive_propagate/i18n/it.po new file mode 100644 index 00000000000..55a18ca7b1a --- /dev/null +++ b/partner_archive_propagate/i18n/it.po @@ -0,0 +1,212 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * partner_archive_propagate +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: Automatically generated\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" + +#. module: partner_archive_propagate +#: model_terms:ir.ui.view,arch_db:partner_archive_propagate.view_archive_propagate_wizard_form +msgid "Archive" +msgstr "" + +#. module: partner_archive_propagate +#: model_terms:ir.ui.view,arch_db:partner_archive_propagate.view_partner_form_inherit_archive_propagate +msgid "Archive Contact and Children" +msgstr "" + +#. module: partner_archive_propagate +#. odoo-python +#: code:addons/partner_archive_propagate/models/res_partner.py:0 +#: model_terms:ir.ui.view,arch_db:partner_archive_propagate.view_archive_propagate_wizard_form +#, python-format +msgid "Archive Contacts" +msgstr "" + +#. module: partner_archive_propagate +#: model:ir.model,name:partner_archive_propagate.model_res_partner_archive_propagate_wizard +msgid "Archive partner and children of partner " +msgstr "" + +#. module: partner_archive_propagate +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_partner__propagated_from_id +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_users__propagated_from_id +msgid "Archived Due To" +msgstr "" + +#. module: partner_archive_propagate +#: model_terms:ir.ui.view,arch_db:partner_archive_propagate.view_archive_propagate_wizard_form +msgid "Cancel" +msgstr "" + +#. module: partner_archive_propagate +#: model:ir.model,name:partner_archive_propagate.model_res_config_settings +msgid "Config Settings" +msgstr "" + +#. module: partner_archive_propagate +#: model:ir.model,name:partner_archive_propagate.model_res_partner +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_partner_archive_propagate_line__partner_id +msgid "Contact" +msgstr "" + +#. module: partner_archive_propagate +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_partner_archive_propagate_wizard__line_ids +msgid "Contacts to archive" +msgstr "" + +#. module: partner_archive_propagate +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_partner_archive_propagate_line__create_uid +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_partner_archive_propagate_wizard__create_uid +msgid "Created by" +msgstr "" + +#. module: partner_archive_propagate +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_partner_archive_propagate_line__create_date +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_partner_archive_propagate_wizard__create_date +msgid "Created on" +msgstr "" + +#. module: partner_archive_propagate +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_partner_archive_propagate_line__display_name +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_partner_archive_propagate_wizard__display_name +msgid "Display Name" +msgstr "" + +#. module: partner_archive_propagate +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_partner_archive_propagate_line__email +msgid "Email" +msgstr "" + +#. module: partner_archive_propagate +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_config_settings__partner_archive_force_outside_ui +msgid "Force propagation outside UI" +msgstr "" + +#. module: partner_archive_propagate +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_partner_archive_propagate_line__id +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_partner_archive_propagate_wizard__id +msgid "ID" +msgstr "" + +#. module: partner_archive_propagate +#: model:ir.model.fields,help:partner_archive_propagate.field_res_config_settings__partner_archive_force_outside_ui +msgid "" +"If enabled, archiving a partner via any non-UI method (imports, RPC, " +"automated) will also archive all descendants and mark them as propagated." +msgstr "" + +#. module: partner_archive_propagate +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_partner_archive_propagate_line____last_update +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_partner_archive_propagate_wizard____last_update +msgid "Last Modified on" +msgstr "" + +#. module: partner_archive_propagate +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_partner_archive_propagate_line__write_uid +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_partner_archive_propagate_wizard__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: partner_archive_propagate +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_partner_archive_propagate_line__write_date +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_partner_archive_propagate_wizard__write_date +msgid "Last Updated on" +msgstr "" + +#. module: partner_archive_propagate +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_partner_archive_propagate_line__name +msgid "Name" +msgstr "" + +#. module: partner_archive_propagate +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_partner_archive_propagate_wizard__partner_id +msgid "Partner" +msgstr "" + +#. module: partner_archive_propagate +#: model_terms:ir.ui.view,arch_db:partner_archive_propagate.view_res_config_settings_partner_archive +msgid "Partner Archive Propagate" +msgstr "" + +#. module: partner_archive_propagate +#: model:ir.model,name:partner_archive_propagate.model_res_partner_archive_propagate_line +msgid "Partner Archive Propagate Line" +msgstr "" + +#. module: partner_archive_propagate +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_partner_archive_propagate_line__phone +msgid "Phone" +msgstr "" + +#. module: partner_archive_propagate +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_partner__show_prop_wizard_button +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_users__show_prop_wizard_button +msgid "Show Prop Wizard Button" +msgstr "" + +#. module: partner_archive_propagate +#: model:ir.model.fields,help:partner_archive_propagate.field_res_partner__show_prop_wizard_button +#: model:ir.model.fields,help:partner_archive_propagate.field_res_users__show_prop_wizard_button +msgid "Show or hide the wizard button, depending on child_ids" +msgstr "" + +#. module: partner_archive_propagate +#. odoo-python +#: code:addons/partner_archive_propagate/models/res_partner.py:0 +#, python-format +msgid "" +"Skipped archiving the following contacts because they are linked to active " +"users: %s" +msgstr "" + +#. module: partner_archive_propagate +#. odoo-python +#: code:addons/partner_archive_propagate/wizard/archive_propagate_wizard.py:0 +#, python-format +msgid "Skipped contacts linked to active users: %s" +msgstr "" + +#. module: partner_archive_propagate +#. odoo-python +#: code:addons/partner_archive_propagate/wizard/archive_propagate_wizard.py:0 +#, python-format +msgid "Some contacts were skipped" +msgstr "" + +#. module: partner_archive_propagate +#: model:ir.model.fields,help:partner_archive_propagate.field_res_partner__propagated_from_id +#: model:ir.model.fields,help:partner_archive_propagate.field_res_users__propagated_from_id +msgid "" +"Technical field. Set when this partner was archived automatically because " +"the source partner was archived. Cleared again when it is unarchived by the " +"opposite propagation." +msgstr "" + +#. module: partner_archive_propagate +#: model_terms:ir.ui.view,arch_db:partner_archive_propagate.view_archive_propagate_wizard_form +msgid "" +"The following contact-type descendants will be archived as well.\n" +" Remove any you want to keep active." +msgstr "" + +#. module: partner_archive_propagate +#: model_terms:ir.ui.view,arch_db:partner_archive_propagate.view_res_config_settings_partner_archive +msgid "" +"When enabled, archiving a partner via imports/RPC/automation will also\n" +" archive all descendants and mark them as propagated." +msgstr "" + +#. module: partner_archive_propagate +#: model:ir.model.fields,field_description:partner_archive_propagate.field_res_partner_archive_propagate_line__wizard_id +msgid "Wizard" +msgstr "" From cf25cca6edb0ccfac1aaae17be7b675761711854 Mon Sep 17 00:00:00 2001 From: Nikos Tsirintanis Date: Fri, 15 May 2026 14:38:16 +0200 Subject: [PATCH 5/9] [FIX] partner_archive_propagate: actually do not archive deleted lines from wizard --- .../tests/test_archive.py | 26 +++++++++++++++++++ .../wizard/archive_propagate_wizard_views.xml | 2 +- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/partner_archive_propagate/tests/test_archive.py b/partner_archive_propagate/tests/test_archive.py index 0a361025967..b8c3a9dcbd6 100644 --- a/partner_archive_propagate/tests/test_archive.py +++ b/partner_archive_propagate/tests/test_archive.py @@ -215,3 +215,29 @@ def test_wizard_confirm_mixed_notification(self): self.assertIn("Skipped contacts linked to active users", params["message"]) # for partner B self.assertIn(self.B.name, params["message"]) + + def test_wizard_deleted_line_not_archived(self): + self.B.write({"type": "contact", "active": True, "propagated_from_id": False}) + self.E.write({"type": "contact", "active": True, "propagated_from_id": False}) + self.A.write({"active": True}) + # Simulate user removing B from the list: only E remains in line_ids. + Wiz = ( + self.env["res.partner.archive.propagate.wizard"] + .sudo() + .create( + { + "partner_id": self.A.id, + "line_ids": [(0, 0, {"partner_id": self.E.id})], + } + ) + ) + result = Wiz.action_confirm() + self.assertEqual(result, {"type": "ir.actions.act_window_close"}) + # Organisation must be archived + self.assertFalse(self.A.active) + # E was in the list and must be archived + self.assertFalse(self.E.active) + self.assertEqual(self.E.propagated_from_id, self.A) + # B was removed from the list and must remain as it was + self.assertTrue(self.B.active) + self.assertFalse(bool(self.B.propagated_from_id)) diff --git a/partner_archive_propagate/wizard/archive_propagate_wizard_views.xml b/partner_archive_propagate/wizard/archive_propagate_wizard_views.xml index eed13366c36..acaacc3b729 100644 --- a/partner_archive_propagate/wizard/archive_propagate_wizard_views.xml +++ b/partner_archive_propagate/wizard/archive_propagate_wizard_views.xml @@ -16,7 +16,7 @@ Remove any you want to keep active.
- + From ab880fd66b4f5dd41e8790bd15f1a7bf96588f43 Mon Sep 17 00:00:00 2001 From: Nikos Tsirintanis Date: Mon, 1 Dec 2025 10:31:11 +0100 Subject: [PATCH 6/9] [ADD] partner_multi_relation_archive_propagate --- .../models/res_partner.py | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/partner_archive_propagate/models/res_partner.py b/partner_archive_propagate/models/res_partner.py index bb24ebb1590..cd84281210e 100644 --- a/partner_archive_propagate/models/res_partner.py +++ b/partner_archive_propagate/models/res_partner.py @@ -27,6 +27,11 @@ def _compute_show_prop_wizard_button(self): for partner in self: partner.show_prop_wizard_button = bool(partner.child_ids) + def _get_archive_propagation_candidates(self): + """To be overidden""" + self.ensure_one() + return self._get_descendants() + def write(self, vals): """Enable (un)archiving propagation: - detect which partners are being (un)archived @@ -58,7 +63,7 @@ def _archive_propagate_wizard(self): # Wizard: only non-contact descendants are silently archived here. for partner in self: non_contacts = ( - partner._get_descendants() + partner._get_archive_propagation_candidates() .sudo() .filtered(lambda p: p.active and p.type != "contact") ) @@ -80,7 +85,11 @@ def _archive_propagate_external(self): """Archive descendants according to propagation rules.""" # Non-UI: archive all children for partner in self: - descendants = partner._get_descendants().sudo().filtered(lambda p: p.active) + descendants = ( + partner._get_archive_propagation_candidates() + .sudo() + .filtered(lambda p: p.active) + ) if not descendants: continue ( @@ -166,20 +175,26 @@ def action_archive_with_contacts(self): allowing the user to exclude some. """ self.ensure_one() - descendants = self._get_descendants().filtered(lambda p: p.active) - contact_desc = descendants.filtered(lambda p: p.type == "contact") - if not contact_desc: + candidates = self._get_archive_propagation_candidates().filtered( + lambda p: p.active + ) + contacts_to_show = candidates.filtered(lambda p: p.type == "contact") + if not contacts_to_show: # No wizard window opens, archive right away return self.with_context(partner_archive_propagate_ui=True).write( {"active": False} ) - archivable, unarchivable = contact_desc._split_archivable_unarchivable_user() + ( + archivable, + unarchivable, + ) = contacts_to_show._split_archivable_unarchivable_user() wiz = self.env["res.partner.archive.propagate.wizard"].create( { "partner_id": self.id, "line_ids": [(0, 0, {"partner_id": p.id}) for p in archivable], } ) + self._notify_skipped_partners(unarchivable) return { "type": "ir.actions.act_window", "name": _("Archive Contacts"), From ef794097161db4db17e9fef8b40c50d902da964d Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Wed, 20 May 2026 14:40:15 +0000 Subject: [PATCH 7/9] [BOT] post-merge updates --- partner_archive_propagate/README.rst | 2 +- partner_archive_propagate/__manifest__.py | 2 +- partner_archive_propagate/static/description/index.html | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/partner_archive_propagate/README.rst b/partner_archive_propagate/README.rst index c49a3aec872..11bfc8ba82c 100644 --- a/partner_archive_propagate/README.rst +++ b/partner_archive_propagate/README.rst @@ -11,7 +11,7 @@ Partner Archive Propagate !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:0c17b904cc1bf10013f67e301084d9732e4a62d5bc5f32d9934fdad84cadb03e + !! source digest: sha256:82494c9a9bbd7ccf2fe16046dff9b9f7966caf9a1adecad44a99ebb59f9a0a6c !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png diff --git a/partner_archive_propagate/__manifest__.py b/partner_archive_propagate/__manifest__.py index b8346c36e73..338cfde0b35 100644 --- a/partner_archive_propagate/__manifest__.py +++ b/partner_archive_propagate/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Partner Archive Propagate", "summary": "Archive/unarchive partner contacts hierarchically", - "version": "16.0.1.0.0", + "version": "16.0.1.1.0", "category": "Partner Management", "author": "Therp BV, Odoo Community Association (OCA)", "website": "https://github.com/OCA/partner-contact", diff --git a/partner_archive_propagate/static/description/index.html b/partner_archive_propagate/static/description/index.html index 2dd3138d5f7..e4ccc47169e 100644 --- a/partner_archive_propagate/static/description/index.html +++ b/partner_archive_propagate/static/description/index.html @@ -372,7 +372,7 @@

Partner Archive Propagate

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -!! source digest: sha256:0c17b904cc1bf10013f67e301084d9732e4a62d5bc5f32d9934fdad84cadb03e +!! source digest: sha256:82494c9a9bbd7ccf2fe16046dff9b9f7966caf9a1adecad44a99ebb59f9a0a6c !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

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

From c68d8a71534ab08b13f417d5d56421eff5b51cec Mon Sep 17 00:00:00 2001 From: Nikos Tsirintanis Date: Fri, 22 May 2026 12:23:31 +0200 Subject: [PATCH 8/9] [IMP] partner_archive_propagate: pre-commit auto fixes --- partner_archive_propagate/README.rst | 72 +++++++++--------- partner_archive_propagate/pyproject.toml | 3 + partner_archive_propagate/readme/CONFIGURE.md | 4 + .../readme/CONFIGURE.rst | 3 - .../readme/CONTRIBUTORS.md | 1 + .../readme/CONTRIBUTORS.rst | 1 - .../readme/DESCRIPTION.md | 20 +++++ .../readme/DESCRIPTION.rst | 19 ----- partner_archive_propagate/readme/USAGE.md | 10 +++ partner_archive_propagate/readme/USAGE.rst | 9 --- .../static/description/index.html | 73 +++++++++---------- 11 files changed, 110 insertions(+), 105 deletions(-) create mode 100644 partner_archive_propagate/pyproject.toml create mode 100644 partner_archive_propagate/readme/CONFIGURE.md delete mode 100644 partner_archive_propagate/readme/CONFIGURE.rst create mode 100644 partner_archive_propagate/readme/CONTRIBUTORS.md delete mode 100644 partner_archive_propagate/readme/CONTRIBUTORS.rst create mode 100644 partner_archive_propagate/readme/DESCRIPTION.md delete mode 100644 partner_archive_propagate/readme/DESCRIPTION.rst create mode 100644 partner_archive_propagate/readme/USAGE.md delete mode 100644 partner_archive_propagate/readme/USAGE.rst diff --git a/partner_archive_propagate/README.rst b/partner_archive_propagate/README.rst index 11bfc8ba82c..d2110937cf1 100644 --- a/partner_archive_propagate/README.rst +++ b/partner_archive_propagate/README.rst @@ -1,7 +1,3 @@ -.. image:: https://odoo-community.org/readme-banner-image - :target: https://odoo-community.org/get-involved?utm_source=readme - :alt: Odoo Community Association - ========================= Partner Archive Propagate ========================= @@ -17,40 +13,43 @@ Partner Archive Propagate .. |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 +.. |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%2Fpartner--contact-lightgray.png?logo=github - :target: https://github.com/OCA/partner-contact/tree/16.0/partner_archive_propagate + :target: https://github.com/OCA/partner-contact/tree/18.0/partner_archive_propagate :alt: OCA/partner-contact .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/partner-contact-16-0/partner-contact-16-0-partner_archive_propagate + :target: https://translation.odoo-community.org/projects/partner-contact-18-0/partner-contact-18-0-partner_archive_propagate :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/partner-contact&target_branch=16.0 + :target: https://runboat.odoo-community.org/builds?repo=OCA/partner-contact&target_branch=18.0 :alt: Try me on Runboat |badge1| |badge2| |badge3| |badge4| |badge5| -=========================== Partner Archive Propagation -=========================== +--------------------------- This module extends the native archiving mechanism for partners. -When archiving a company or parent contact, it will also handle its descendants -according to business rules — with user control and safeguards. +When archiving a company or parent contact, it will also handle its +descendants according to business rules — with user control and +safeguards. Features --------- - -* Adds a new **"Archive Contact and Children"** button on the Partner form. -* Shows a **wizard** listing contact-type descendants before archiving. -* Automatically skips descendants linked to active users. -* Adds a technical Many2one field (propagated_from_id) that records which parent partner caused the automatic archiving, making propagation fully traceable and reversible. -* Ensures automatic unarchive propagation. -* Includes a system setting to enforce propagation even for non-UI actions - (imports, RPC, automated jobs, etc.). +~~~~~~~~ + +- Adds a new **"Archive Contact and Children"** button on the Partner + form. +- Shows a **wizard** listing contact-type descendants before archiving. +- Automatically skips descendants linked to active users. +- Adds a technical Many2one field (propagated_from_id) that records + which parent partner caused the automatic archiving, making + propagation fully traceable and reversible. +- Ensures automatic unarchive propagation. +- Includes a system setting to enforce propagation even for non-UI + actions (imports, RPC, automated jobs, etc.). **Table of contents** @@ -60,9 +59,10 @@ Features Configuration ============= -* Go to **Settings → Technical → General Settings → Partner Archive Propagate**. -* Enable *"Force propagation outside UI"* to automatically apply propagation - when partners are archived through automation or imports. +- Go to **Settings → Technical → General Settings → Partner Archive + Propagate**. +- Enable *"Force propagation outside UI"* to automatically apply + propagation when partners are archived through automation or imports. Usage ===== @@ -70,12 +70,14 @@ Usage 1. Open a partner (company or main contact). 2. Click the **"Archive Contact and Children"** button. 3. Review the list of child contacts to be archived. -4. Confirm the action. - * Non-contact types (e.g., invoice/delivery addresses) are archived silently. - * Contact-type descendants appear in the wizard for review. +4. Confirm the action. + + - Non-contact types (e.g., invoice/delivery addresses) are archived + silently. + - Contact-type descendants appear in the wizard for review. -Unarchiving also follows propagation rules: -if a parent partner is unarchived, its propagated descendants are unarchived as well. +Unarchiving also follows propagation rules: if a parent partner is +unarchived, its propagated descendants are unarchived as well. Bug Tracker =========== @@ -83,7 +85,7 @@ Bug Tracker Bugs are tracked on `GitHub Issues `_. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed -`feedback `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -91,17 +93,17 @@ Credits ======= Authors -~~~~~~~ +------- * Therp BV Contributors -~~~~~~~~~~~~ +------------ -* Nikos Tsirintanis +- Nikos Tsirintanis Maintainers -~~~~~~~~~~~ +----------- This module is maintained by the OCA. @@ -121,6 +123,6 @@ Current `maintainer `__: |maintainer-ntsirintanis| -This module is part of the `OCA/partner-contact `_ project on GitHub. +This module is part of the `OCA/partner-contact `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/partner_archive_propagate/pyproject.toml b/partner_archive_propagate/pyproject.toml new file mode 100644 index 00000000000..4231d0cccb3 --- /dev/null +++ b/partner_archive_propagate/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/partner_archive_propagate/readme/CONFIGURE.md b/partner_archive_propagate/readme/CONFIGURE.md new file mode 100644 index 00000000000..3912d7eb64f --- /dev/null +++ b/partner_archive_propagate/readme/CONFIGURE.md @@ -0,0 +1,4 @@ +- Go to **Settings → Technical → General Settings → Partner Archive + Propagate**. +- Enable *"Force propagation outside UI"* to automatically apply + propagation when partners are archived through automation or imports. diff --git a/partner_archive_propagate/readme/CONFIGURE.rst b/partner_archive_propagate/readme/CONFIGURE.rst deleted file mode 100644 index 5b2fedacf53..00000000000 --- a/partner_archive_propagate/readme/CONFIGURE.rst +++ /dev/null @@ -1,3 +0,0 @@ -* Go to **Settings → Technical → General Settings → Partner Archive Propagate**. -* Enable *"Force propagation outside UI"* to automatically apply propagation - when partners are archived through automation or imports. diff --git a/partner_archive_propagate/readme/CONTRIBUTORS.md b/partner_archive_propagate/readme/CONTRIBUTORS.md new file mode 100644 index 00000000000..2d66b8d1486 --- /dev/null +++ b/partner_archive_propagate/readme/CONTRIBUTORS.md @@ -0,0 +1 @@ +- Nikos Tsirintanis \<\> diff --git a/partner_archive_propagate/readme/CONTRIBUTORS.rst b/partner_archive_propagate/readme/CONTRIBUTORS.rst deleted file mode 100644 index 31fdb1fd5ad..00000000000 --- a/partner_archive_propagate/readme/CONTRIBUTORS.rst +++ /dev/null @@ -1 +0,0 @@ -* Nikos Tsirintanis \ No newline at end of file diff --git a/partner_archive_propagate/readme/DESCRIPTION.md b/partner_archive_propagate/readme/DESCRIPTION.md new file mode 100644 index 00000000000..0f27995f775 --- /dev/null +++ b/partner_archive_propagate/readme/DESCRIPTION.md @@ -0,0 +1,20 @@ +## Partner Archive Propagation + +This module extends the native archiving mechanism for partners. + +When archiving a company or parent contact, it will also handle its +descendants according to business rules — with user control and +safeguards. + +### Features + +- Adds a new **"Archive Contact and Children"** button on the Partner + form. +- Shows a **wizard** listing contact-type descendants before archiving. +- Automatically skips descendants linked to active users. +- Adds a technical Many2one field (propagated_from_id) that records + which parent partner caused the automatic archiving, making + propagation fully traceable and reversible. +- Ensures automatic unarchive propagation. +- Includes a system setting to enforce propagation even for non-UI + actions (imports, RPC, automated jobs, etc.). diff --git a/partner_archive_propagate/readme/DESCRIPTION.rst b/partner_archive_propagate/readme/DESCRIPTION.rst deleted file mode 100644 index 4b3feaacd0f..00000000000 --- a/partner_archive_propagate/readme/DESCRIPTION.rst +++ /dev/null @@ -1,19 +0,0 @@ -=========================== -Partner Archive Propagation -=========================== - -This module extends the native archiving mechanism for partners. - -When archiving a company or parent contact, it will also handle its descendants -according to business rules — with user control and safeguards. - -Features --------- - -* Adds a new **"Archive Contact and Children"** button on the Partner form. -* Shows a **wizard** listing contact-type descendants before archiving. -* Automatically skips descendants linked to active users. -* Adds a technical Many2one field (propagated_from_id) that records which parent partner caused the automatic archiving, making propagation fully traceable and reversible. -* Ensures automatic unarchive propagation. -* Includes a system setting to enforce propagation even for non-UI actions - (imports, RPC, automated jobs, etc.). diff --git a/partner_archive_propagate/readme/USAGE.md b/partner_archive_propagate/readme/USAGE.md new file mode 100644 index 00000000000..8c6eac7761e --- /dev/null +++ b/partner_archive_propagate/readme/USAGE.md @@ -0,0 +1,10 @@ +1. Open a partner (company or main contact). +2. Click the **"Archive Contact and Children"** button. +3. Review the list of child contacts to be archived. +4. Confirm the action. + - Non-contact types (e.g., invoice/delivery addresses) are archived + silently. + - Contact-type descendants appear in the wizard for review. + +Unarchiving also follows propagation rules: if a parent partner is +unarchived, its propagated descendants are unarchived as well. diff --git a/partner_archive_propagate/readme/USAGE.rst b/partner_archive_propagate/readme/USAGE.rst deleted file mode 100644 index 78c97822e94..00000000000 --- a/partner_archive_propagate/readme/USAGE.rst +++ /dev/null @@ -1,9 +0,0 @@ -1. Open a partner (company or main contact). -2. Click the **"Archive Contact and Children"** button. -3. Review the list of child contacts to be archived. -4. Confirm the action. - * Non-contact types (e.g., invoice/delivery addresses) are archived silently. - * Contact-type descendants appear in the wizard for review. - -Unarchiving also follows propagation rules: -if a parent partner is unarchived, its propagated descendants are unarchived as well. diff --git a/partner_archive_propagate/static/description/index.html b/partner_archive_propagate/static/description/index.html index e4ccc47169e..2fd3d66719b 100644 --- a/partner_archive_propagate/static/description/index.html +++ b/partner_archive_propagate/static/description/index.html @@ -3,7 +3,7 @@ -README.rst +Partner Archive Propagate -
+
+

Partner Archive Propagate

- - -Odoo Community Association - -
-

Partner Archive Propagate

-

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

-
+

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

Partner Archive Propagation

This module extends the native archiving mechanism for partners.

-

When archiving a company or parent contact, it will also handle its descendants -according to business rules — with user control and safeguards.

+

When archiving a company or parent contact, it will also handle its +descendants according to business rules — with user control and +safeguards.

Features

    -
  • Adds a new “Archive Contact and Children” button on the Partner form.
  • +
  • Adds a new “Archive Contact and Children” button on the Partner +form.
  • Shows a wizard listing contact-type descendants before archiving.
  • Automatically skips descendants linked to active users.
  • -
  • Adds a technical Many2one field (propagated_from_id) that records which parent partner caused the automatic archiving, making propagation fully traceable and reversible.
  • +
  • Adds a technical Many2one field (propagated_from_id) that records +which parent partner caused the automatic archiving, making +propagation fully traceable and reversible.
  • Ensures automatic unarchive propagation.
  • -
  • Includes a system setting to enforce propagation even for non-UI actions -(imports, RPC, automated jobs, etc.).
  • +
  • Includes a system setting to enforce propagation even for non-UI +actions (imports, RPC, automated jobs, etc.).

Table of contents

@@ -398,20 +396,16 @@

Features

  • Configuration
  • Usage
  • Bug Tracker
  • -
  • Credits -
  • +
  • Credits
  • Configuration

      -
    • Go to Settings → Technical → General Settings → Partner Archive Propagate.
    • -
    • Enable “Force propagation outside UI” to automatically apply propagation -when partners are archived through automation or imports.
    • +
    • Go to Settings → Technical → General Settings → Partner Archive +Propagate.
    • +
    • Enable “Force propagation outside UI” to automatically apply +propagation when partners are archived through automation or imports.
    @@ -420,37 +414,43 @@

    Usage

  • Open a partner (company or main contact).
  • Click the “Archive Contact and Children” button.
  • Review the list of child contacts to be archived.
  • -
  • Confirm the action. -* Non-contact types (e.g., invoice/delivery addresses) are archived silently. -* Contact-type descendants appear in the wizard for review.
  • +
  • Confirm the action.
      +
    • Non-contact types (e.g., invoice/delivery addresses) are archived +silently.
    • +
    • Contact-type descendants appear in the wizard for review.
    • +
    +
  • -

    Unarchiving also follows propagation rules: -if a parent partner is unarchived, its propagated descendants are unarchived as well.

    +

    Unarchiving also follows propagation rules: if a parent partner is +unarchived, its propagated descendants are unarchived as well.

    Bug Tracker

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

    +feedback.

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

    +
    +
    -

    Authors

    +

    Authors

    • Therp BV
    -

    Contributors

    +

    Contributors

    -

    Maintainers

    +

    Maintainers

    This module is maintained by the OCA.

    Odoo Community Association @@ -460,12 +460,9 @@

    Maintainers

    promote its widespread use.

    Current maintainer:

    ntsirintanis

    -

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

    +

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

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

    -
    - - From ac105fc664331fa13b48269571896e42241df654 Mon Sep 17 00:00:00 2001 From: Nikos Tsirintanis Date: Fri, 22 May 2026 12:28:18 +0200 Subject: [PATCH 9/9] [MIG] partner_archive_propagate: Migration to 18.0 --- partner_archive_propagate/__manifest__.py | 4 +- .../models/res_config_settings.py | 3 +- .../models/res_partner.py | 5 ++- .../tests/test_archive.py | 37 +++++++++++++++++-- .../views/res_config_settings_views.xml | 32 ++++------------ .../views/res_partner_views.xml | 4 +- .../wizard/archive_propagate_wizard_views.xml | 5 +-- 7 files changed, 53 insertions(+), 37 deletions(-) diff --git a/partner_archive_propagate/__manifest__.py b/partner_archive_propagate/__manifest__.py index 338cfde0b35..9618e78a70a 100644 --- a/partner_archive_propagate/__manifest__.py +++ b/partner_archive_propagate/__manifest__.py @@ -3,12 +3,12 @@ { "name": "Partner Archive Propagate", "summary": "Archive/unarchive partner contacts hierarchically", - "version": "16.0.1.1.0", + "version": "18.0.1.0.0", "category": "Partner Management", "author": "Therp BV, Odoo Community Association (OCA)", "website": "https://github.com/OCA/partner-contact", "license": "AGPL-3", - "depends": ["base", "mail"], + "depends": ["base_setup", "mail"], "data": [ "security/ir.model.access.csv", "views/res_config_settings_views.xml", diff --git a/partner_archive_propagate/models/res_config_settings.py b/partner_archive_propagate/models/res_config_settings.py index 3095eeac014..ea699d9bbda 100644 --- a/partner_archive_propagate/models/res_config_settings.py +++ b/partner_archive_propagate/models/res_config_settings.py @@ -8,7 +8,8 @@ class ResConfigSettings(models.TransientModel): partner_archive_force_outside_ui = fields.Boolean( string="Force propagation outside UI", - help="If enabled, archiving a partner via any non-UI method (imports, RPC, automated) " + help="If enabled, archiving a partner via any non-UI method" + " (imports, RPC, automated) " "will also archive all descendants and mark them as propagated.", config_parameter="partner_archive_propagate.force_outside_ui", default=False, diff --git a/partner_archive_propagate/models/res_partner.py b/partner_archive_propagate/models/res_partner.py index cd84281210e..63793269709 100644 --- a/partner_archive_propagate/models/res_partner.py +++ b/partner_archive_propagate/models/res_partner.py @@ -137,7 +137,10 @@ def _get_descendants(self): ) def _split_archivable_unarchivable_user(self): - """Find which partners are archivable (i.e., no active users) and unarchivable.""" + """ + Find which partners are archivable + (i.e., no active users) and unarchivable. + """ archivable = self.browse() unarchivable = self.browse() for partner in self: diff --git a/partner_archive_propagate/tests/test_archive.py b/partner_archive_propagate/tests/test_archive.py index b8c3a9dcbd6..7a1a418581b 100644 --- a/partner_archive_propagate/tests/test_archive.py +++ b/partner_archive_propagate/tests/test_archive.py @@ -21,7 +21,10 @@ def setUpClass(cls): cls.E = Partners.create({"name": "E", "parent_id": cls.B.id, "type": "contact"}) def _create_active_user_for_partner(self, partner): - """Create an active user linked to given partner to test unarchivable situations""" + """ + Create an active user linked to given + partner to test unarchivable situations + """ return ( self.env["res.users"] .sudo() @@ -104,7 +107,8 @@ def test_archive_contact_types_skip_active_user(self): for p in (self.C, self.D): self.assertFalse(p.active) self.assertEqual(p.propagated_from_id, self.A) - # Unarchive parent: flagged ones E/C/D should become active, B stays active anyway + # Unarchive parent: flagged ones E/C/D should + # become active, B stays active anyway self.A.write({"active": True}) for p in (self.E, self.C, self.D): self.assertTrue(p.active) @@ -136,7 +140,10 @@ def test_archive_no_wizard_force_setting_propagates(self): icp.set_param("partner_archive_propagate.force_outside_ui", "0") def test_manual_archive_does_not_toggle_flag(self): - """Archiving a partner manually should NOT toggle propagated_from_id on that record.""" + """ + Archiving a partner manually should NOT toggle + propagated_from_id on that record. + """ # Ensure clean self.B.write({"active": True, "propagated_from_id": False}) # Manual archive of B, force_outside_ui is off @@ -241,3 +248,27 @@ def test_wizard_deleted_line_not_archived(self): # B was removed from the list and must remain as it was self.assertTrue(self.B.active) self.assertFalse(bool(self.B.propagated_from_id)) + + def test_unarchive_skips_already_active_partners(self): + """When writing active=True on a mixed recordset, only currently + inactive partners are processed — the filtered() branch is exercised.""" + p1 = self.env["res.partner"].create({"name": "P1 parent"}) + p2 = self.env["res.partner"].create({"name": "P2 child"}) + # Archive both, p2 flagged as propagated from p1 + p1.write({"active": False}) + p2.write({"active": False, "propagated_from_id": p1.id}) + + # p3 is a red herring — already active, should be skipped by filtered() + p3 = self.env["res.partner"].create({"name": "P3 already active"}) + + # Write active=True on a mix: p3 (already active) + p1 (inactive) + # filtered() produces only p1 -> _unarchive_propagate runs on p1 -> restores p2 + (p3 | p1).write({"active": True}) + + p1.invalidate_recordset() + p2.invalidate_recordset() + self.assertTrue(p1.active) + self.assertTrue(p2.active) + self.assertFalse(bool(p2.propagated_from_id)) + # p3 remains active and untouched + self.assertTrue(p3.active) diff --git a/partner_archive_propagate/views/res_config_settings_views.xml b/partner_archive_propagate/views/res_config_settings_views.xml index f840d0fb315..ac0ecd6fa85 100644 --- a/partner_archive_propagate/views/res_config_settings_views.xml +++ b/partner_archive_propagate/views/res_config_settings_views.xml @@ -2,32 +2,16 @@ res.config.settings.view.form.partner.archive res.config.settings - - + - -
    + -

    Partner Archive Propagate

    -
    -
    -
    - -
    -
    -
    -
    -
    -
    -
    + + +
    diff --git a/partner_archive_propagate/views/res_partner_views.xml b/partner_archive_propagate/views/res_partner_views.xml index 0610312887c..ac4b38f97e2 100644 --- a/partner_archive_propagate/views/res_partner_views.xml +++ b/partner_archive_propagate/views/res_partner_views.xml @@ -12,9 +12,7 @@ type="object" string="Archive Contact and Children" class="oe_highlight" - attrs="{ - 'invisible': ['|', ('active', '=', False), ('show_prop_wizard_button', '=', False)] - }" + invisible="not active or not show_prop_wizard_button" /> diff --git a/partner_archive_propagate/wizard/archive_propagate_wizard_views.xml b/partner_archive_propagate/wizard/archive_propagate_wizard_views.xml index acaacc3b729..8cf751ec7af 100644 --- a/partner_archive_propagate/wizard/archive_propagate_wizard_views.xml +++ b/partner_archive_propagate/wizard/archive_propagate_wizard_views.xml @@ -9,19 +9,18 @@ -