From 3c959771aefeeed68c0cc104cabcf7b7c5129de6 Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Tue, 12 May 2026 18:51:25 -0400 Subject: [PATCH] feat(nexa_coa_setup): pre_init_hook to clear l10n_ca code collisions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bakes the staging-side one-off collision clearing into the module install itself so production install will execute the same sweep automatically. For each of the 29 l10n_ca codes that conflict with Nexa's planned chart: - If the account has zero postings: suffix code with '.OLD', mark inactive, rename to '(l10n_ca LEGACY) ' - If the account has postings (currently 115100 AR control with 240 lines and 511100 Inside Purchases with 1 line): leave alone (Nexa renumbered to 119100 / 511105 in the XML) Idempotent — pre_init_hook re-running has no effect (already-suffixed codes are skipped). Co-Authored-By: Claude Opus 4.7 (1M context) --- nexa_coa_setup/__init__.py | 2 +- nexa_coa_setup/__manifest__.py | 1 + nexa_coa_setup/hooks.py | 66 ++++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 1 deletion(-) diff --git a/nexa_coa_setup/__init__.py b/nexa_coa_setup/__init__.py index cc6b6354..a9b33fa5 100644 --- a/nexa_coa_setup/__init__.py +++ b/nexa_coa_setup/__init__.py @@ -1,2 +1,2 @@ from . import models -from .hooks import post_init_hook +from .hooks import pre_init_hook, post_init_hook diff --git a/nexa_coa_setup/__manifest__.py b/nexa_coa_setup/__manifest__.py index 52b4e87a..65624d9c 100644 --- a/nexa_coa_setup/__manifest__.py +++ b/nexa_coa_setup/__manifest__.py @@ -31,6 +31,7 @@ "data/09_res_partner.xml", "data/10_account_reconcile_model.xml", ], + "pre_init_hook": "pre_init_hook", "post_init_hook": "post_init_hook", "installable": True, "application": False, diff --git a/nexa_coa_setup/hooks.py b/nexa_coa_setup/hooks.py index 4c524c2e..c7dd12d5 100644 --- a/nexa_coa_setup/hooks.py +++ b/nexa_coa_setup/hooks.py @@ -4,6 +4,72 @@ import logging _logger = logging.getLogger(__name__) +# l10n_ca account codes that collide with the Nexa CoA design and that +# l10n_ca pre-loads with 'income_other'/'expense'/etc. types we don't want. +# Each of these is checked at pre_init: if it has zero postings we suffix +# its code with '.OLD' and archive it so our XML can claim the code. +# Codes with postings are LEFT ALONE — we renumbered the Nexa code instead +# (115100 stays as l10n_ca 'Customers Account' AR; Nexa shareholder receivable +# moved to 119100. 511100 stays as l10n_ca 'Inside Purchases'; Nexa Cloud +# Infrastructure moved to 511105). +_L10N_CA_COLLISION_CODES = [ + "118100", "118200", "118300", + "213100", "214100", + "221200", + "311100", "311200", "311300", + "411100", "411200", "411300", + "413100", "413200", "413300", + "511110", "511120", "511130", "511140", "511200", "511210", + "512100", "512110", "512200", + "611100", "611200", "611300", + "612100", "612200", +] + + +def pre_init_hook(env): + """Run BEFORE XML data is loaded. Clear l10n_ca account codes that would + collide with Nexa's chart of accounts.""" + _logger.info("nexa_coa_setup: pre_init_hook starting") + _clear_l10n_ca_collisions(env) + _logger.info("nexa_coa_setup: pre_init_hook complete") + + +def _clear_l10n_ca_collisions(env): + """For each colliding code: if it has zero postings, rename to NNNNNN.OLD + and set inactive. If it has postings, leave alone (Nexa code was renumbered + in the XML to avoid the conflict).""" + cleared = 0 + kept_with_postings = 0 + not_found = 0 + for code in _L10N_CA_COLLISION_CODES: + acc = env["account.account"].search([("code", "=", code)], limit=1) + if not acc: + not_found += 1 + continue + usage = env["account.move.line"].search_count([("account_id", "=", acc.id)]) + if usage > 0: + _logger.info( + "nexa_coa_setup: keeping l10n_ca account %s (%s) — %d postings exist", + code, acc.name, usage, + ) + kept_with_postings += 1 + continue + new_code = f"{code}.OLD" + # Skip if already suffixed (idempotency) + if acc.code.endswith(".OLD"): + continue + acc.write({ + "code": new_code, + "name": f"(l10n_ca LEGACY) {acc.name or acc.display_name}", + "active": False, + }) + cleared += 1 + _logger.info( + "nexa_coa_setup: collision sweep — cleared %d, kept-with-postings %d, not-found %d", + cleared, kept_with_postings, not_found, + ) + + def post_init_hook(env): """Imperative one-shot operations after module data is loaded.