Skip to content

[16.0][ADD] partner_archive_propagate#2212

Open
ntsirintanis wants to merge 1 commit intoOCA:16.0from
Therp:16.0-partner_archive_propagate
Open

[16.0][ADD] partner_archive_propagate#2212
ntsirintanis wants to merge 1 commit intoOCA:16.0from
Therp:16.0-partner_archive_propagate

Conversation

@ntsirintanis
Copy link
Copy Markdown
Contributor

@ntsirintanis ntsirintanis commented Nov 12, 2025

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.).

@ntsirintanis ntsirintanis force-pushed the 16.0-partner_archive_propagate branch 4 times, most recently from 8d88e95 to aacee86 Compare November 12, 2025 15:00
@ntsirintanis ntsirintanis changed the title [ADD] partner_archive_propagate [16.0][ADD] partner_archive_propagate Nov 12, 2025
@ntsirintanis ntsirintanis marked this pull request as ready for review November 12, 2025 15:10
@ntsirintanis ntsirintanis force-pushed the 16.0-partner_archive_propagate branch from aacee86 to 96daaf3 Compare November 12, 2025 15:16
string="Archive Contact and Children"
class="oe_highlight"
attrs="{
'invisible': ['|', ('active', '=', False), ('show_prop_wizard_button', '=', False)]
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or maybe child_ids = [], and remove show_prop_wizard_button field altogether

@UrzaBlock
Copy link
Copy Markdown

I tested this on runboat, and the button works nicely:

  • Archiving of Partner and its children works. You can easily leave someone out, too.
  • Unarchiving of Partner easily puts back the archived children, as well.

@ntsirintanis ntsirintanis force-pushed the 16.0-partner_archive_propagate branch from 96daaf3 to 818aa69 Compare November 13, 2025 09:21

def write(self, vals):
"""Enable (un)archiving propagation"""
archiving = "active" in vals and vals.get("active") is False
Copy link
Copy Markdown
Contributor

@NL66278 NL66278 Nov 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works, but can be shortened:
archiving = vals.get("active", True) is False

and similar for unarchiving
unarchiving = vals.get("active", False) is True

@ntsirintanis ntsirintanis force-pushed the 16.0-partner_archive_propagate branch from 818aa69 to 19b220a Compare November 13, 2025 10:26
"line_ids": [(0, 0, {"partner_id": p.id}) for p in archivable],
}
)
self._notify_skipped_partners(unarchivable)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this notification comes to early, as the user can still cancel the whole action in the wizard.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ntsirintanis I think you missed this remark...

Copy link
Copy Markdown
Contributor

@NL66278 NL66278 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great in general and very nice to have so many unit tests. There is one remark on statements that can be more concise, and one on a message that I think comes to early.

@ntsirintanis ntsirintanis force-pushed the 16.0-partner_archive_propagate branch from 19b220a to 90baf48 Compare November 13, 2025 14:49
@NL66278
Copy link
Copy Markdown
Contributor

NL66278 commented Nov 13, 2025

The lacking coverage could be solved by creating a wizard for some partners that are unarchivable and then checking that in you get a message in the confirmation window: https://app.codecov.io/gh/OCA/partner-contact/pull/2212?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=checks&utm_campaign=pr+comments&utm_term=OCA

@ntsirintanis ntsirintanis force-pushed the 16.0-partner_archive_propagate branch 3 times, most recently from 6797e5b to 7d8c820 Compare November 13, 2025 15:45
Comment thread partner_archive_propagate/tests/test_archive.py Outdated
Comment thread partner_archive_propagate/wizard/archive_propagate_wizard.py
@ntsirintanis ntsirintanis force-pushed the 16.0-partner_archive_propagate branch from 7d8c820 to 0134223 Compare November 18, 2025 10:43
@NL66278
Copy link
Copy Markdown
Contributor

NL66278 commented Nov 18, 2025

@ntsirintanis Everything green, that is great. You should still delete lines 88 - 97 of the test (and then unindent 98). As this is a branch never taken.

@ntsirintanis ntsirintanis force-pushed the 16.0-partner_archive_propagate branch from 0134223 to 9d3bc2d Compare November 18, 2025 11:37
Copy link
Copy Markdown
Contributor

@NL66278 NL66278 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Great work and fully covered with tests!

class ResPartner(models.Model):
_inherit = "res.partner"

propagated_archive = fields.Boolean(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ntsirintanis It occured to me that unarchiving could be made more efficient (and more traceable), when not having a boolean here, but the id of the partner that was the source of the propagation.

So replace this field with a Many2one to res.partner called propagated_from_id. Now when unarchiving it would be much faster to just find all the partners that where archived because this partners archiving had been propagated (and that still are archived).

The beauty of it would be that with the new module that archives through relations, nothing extra would need to be done to propagate the unarchiving to those partners as well, it would already be covered in this module.

for partner in self:
partner.show_prop_wizard_button = bool(partner.child_ids)

def write(self, vals):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some extra streamlining with partner_multi_relation_archive_propagate(pmrap) is possible.

In pmrap you already selected archiving_companies and unarchiving_companies. You can do the same here, but with no selection on company, you will have archiving_partners or unarchiving partners (never both, often none).
Then from this method:

            if archiving_partners:
                archiving_partners._archive_propagate()
            elif unarchiving_partners:
                unarchiving_partners._unarchive_propagate()

The extra functionality in pmrap to propagate archiving to related partners if the archived partner is a company can be added by overriding the _archive_propagate() method then. Then in pmrap you do not need to overwrite the write() method at all.

If you follow my suggestion to replace the boolean propagated_archive with the id of the archiving source partner), you will need to do nothing for unarchiving in pmrap,

@ntsirintanis ntsirintanis force-pushed the 16.0-partner_archive_propagate branch 2 times, most recently from 155031e to 6c2833a Compare November 27, 2025 11:26
unarchiving_partners = self.browse()
if "active" in vals:
if vals["active"] is False:
archiving_partners = self
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self filtered on only active partners

if vals["active"] is False:
archiving_partners = self
elif vals["active"] is True:
unarchiving_partners = self
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self filtered on only archived partners

unarchiving_partners._unarchive_propagate()
return res

def _archive_propagate(self, from_ui=False):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As the method has two independent branches, just split it in two methods, that then actualy do not need any argument (apart from self).

Only touches partners where propagated_from_id points to the unarchived
parent, and that are currently inactive.
"""
for partner in self:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for a loop. Change the first line in the domain to ("propagated_from_id", "in", self.ids),

Copy link
Copy Markdown
Contributor

@NL66278 NL66278 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, but few changes still needed.

@ntsirintanis ntsirintanis force-pushed the 16.0-partner_archive_propagate branch from 6c2833a to db963e8 Compare December 1, 2025 09:07
Copy link
Copy Markdown
Contributor

@NL66278 NL66278 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 LGTM And well tested both in unit test and IRL.

Copy link
Copy Markdown

@marcos-mendez marcos-mendez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated Review -- Tests Failed

1. Root Cause

The test failure is caused by a database connection error (Connection to the database failed) during the Odoo server startup, which indicates an issue in the test environment setup or configuration, not in the module's logic itself.

2. Suggested Fix

The error occurs before any module code is executed. Ensure the test environment is properly configured with a valid database connection. This is likely due to a misconfiguration in runboat or CI setup, and not a code bug in partner_archive_propagate.

However, to make the module more robust:

  • In res_partner.py, line ~173, _force_non_ui() method should handle potential KeyError or ConfigParameter access errors gracefully.
    def _force_non_ui(self):
        """Read system setting controlling external propagation."""
        try:
            return self.env["ir.config_parameter"].sudo().get_param(
                "partner_archive_propagate.force_outside_ui", default="0"
            ) in ("1", "true", "True")
        except Exception:
            return False  # fallback to disable propagation if config is missing

3. Additional Code Issues

  • Line 130 in res_partner.py: The method _split_archivable_unarchivable_user() is inefficient as it loops through all partners individually. It should be optimized using sudo().read(['user_ids']) or batch queries for better performance.
  • Missing @api.model decorator in _force_non_ui() method — though not causing runtime error, it's semantically incorrect and may mislead developers expecting model-level behavior.

4. Test Improvements

Add the following test cases in a TransactionCase or SavepointCase:

Test Cases:

  1. Test propagation on archive via UI (wizard):

    • Create a parent partner with children (contact and non-contact).
    • Call action_archive_with_contacts() and verify:
      • Non-contact children are archived silently.
      • Contact children appear in wizard.
      • Active users prevent archiving of their linked partners.
      • propagated_from_id is correctly set.
  2. Test propagation on external archive (non-UI):

    • Enable force_outside_ui.
    • Archive a parent partner via write({'active': False}).
    • Verify all descendants are archived with correct propagated_from_id.
  3. Test unarchive propagation:

    • Archive a parent partner with descendants.
    • Unarchive the parent.
    • Verify descendants are also unarchived and propagated_from_id cleared.
  4. Test default behavior (without force):

    • Archive a parent without enabling force_outside_ui.
    • Ensure no propagation happens unless triggered by UI.

Use @tag('post_install') for integration tests and @tag('fast') for unit-level tests.

These tests should be added to tests/test_partner_archive_propagate.py.


⏰ PR Aging Alert

This PR by @ntsirintanis has been open for 123 days (4 months).
💤 Last activity was 33 days ago.

Every ignored PR is a contributor who might not come back. Review time matters. (OCA Aging Report)


Reciprocal Review Request

Hi everyone! I found some test failures on this PR and left detailed feedback above. I am happy to discuss or help debug. In the meantime, if any of you get a chance, I would appreciate a look at my open PR(s):

My open PRs across OCA:

Reviewing each other's work helps the whole community move forward. Thank you!


Environment via OCA Neural Reviewer: Minikube + K8s Job + oca-ci/py3.10-odoo16.0 | Odoo 16.0
Automated review by OCA Neural Reviewer + qwen3-coder:30b

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants