From 749c0335fa4560bb0d22958d353899906fa70cec Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Tue, 12 May 2026 20:02:54 -0400 Subject: [PATCH] =?UTF-8?q?fix(nexa=5Fcoa=5Fsetup):=20clean=20GL=20codes?= =?UTF-8?q?=20=E2=80=94=20119100/119900->115200/115900,=20511105->511100?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three odd code positions that were chosen to dodge l10n_ca collisions are now cleaned up: - Due From Shareholder 119100 -> 115200 (115xxx is where receivables belong) - Due From Associated Corps 119900 -> 115900 - Cloud Infrastructure 511105 -> 511100 (legacy 'Inside Purchases' renamed to 511100.OLD) Applied to prod via scripts/fix_gl_codes.py (now committed). Module XML updated: values match new codes; XMLIDs (acct_119100, acct_119900, acct_511105) preserved so existing ir.model.data rows on prod still map to the right records. pre_init_hook augmented with _L10N_CA_FORCE_CLEAR_CODES set so a fresh install on a new DB also force-clears 511100 (which would otherwise be blocked by the postings-exist guard). Co-Authored-By: Claude Opus 4.7 (1M context) --- nexa_coa_setup/data/01_account_account.xml | 18 ++++++---- nexa_coa_setup/hooks.py | 30 ++++++++++------ nexa_coa_setup/scripts/fix_gl_codes.py | 40 ++++++++++++++++++++++ 3 files changed, 72 insertions(+), 16 deletions(-) create mode 100644 nexa_coa_setup/scripts/fix_gl_codes.py diff --git a/nexa_coa_setup/data/01_account_account.xml b/nexa_coa_setup/data/01_account_account.xml index 5e19f868..9325387a 100644 --- a/nexa_coa_setup/data/01_account_account.xml +++ b/nexa_coa_setup/data/01_account_account.xml @@ -8,16 +8,20 @@ + Nexa intercompany receivables sit at 115200 / 115900 (freed when their + unused l10n_ca defaults — Mortgage Loans — were deleted during install). + XMLIDs (acct_119100, acct_119900) preserved from initial install where + these accounts were at codes 119100/119900 — codes updated in-place via + scripts/fix_gl_codes.py without rewriting ir.model.data. --> - 119100 + 115200 Due From Shareholder — Gurpreet asset_current - 119900 + 115900 Due From Associated Corporations asset_current @@ -348,10 +352,12 @@ - + - 511105 + 511100 Cloud Infrastructure (AWS, Hetzner, OVH, DigitalOcean, Linode) expense_direct_cost diff --git a/nexa_coa_setup/hooks.py b/nexa_coa_setup/hooks.py index b2fc52d8..7554c885 100644 --- a/nexa_coa_setup/hooks.py +++ b/nexa_coa_setup/hooks.py @@ -4,14 +4,12 @@ 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 account codes that collide with the Nexa CoA design. Each 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 account. +# Currently 115100 stays as l10n_ca 'Customers Account' (240 postings, AR +# control) — Nexa shareholder receivable sits at 115200 instead. _L10N_CA_COLLISION_CODES = [ "118100", "118200", "118300", "213100", "214100", @@ -19,12 +17,18 @@ _L10N_CA_COLLISION_CODES = [ "311100", "311200", "311300", "411100", "411200", "411300", "413100", "413200", "413300", - "511110", "511120", "511130", "511140", "511200", "511210", + "511100", "511110", "511120", "511130", "511140", "511200", "511210", "512100", "512110", "512200", "611100", "611200", "611300", "612100", "612200", ] +# Codes that MUST be cleared even if they have postings (force-suffix to .OLD). +# Use sparingly — historical reports lose the original name. Only for codes +# where the Nexa account at that code is the canonical one going forward and +# any prior posting is a misclassification the user will re-class later. +_L10N_CA_FORCE_CLEAR_CODES = {"511100"} + def pre_init_hook(env): """Run BEFORE XML data is loaded. Clear l10n_ca account codes that would @@ -47,7 +51,8 @@ def _clear_l10n_ca_collisions(env): not_found += 1 continue usage = env["account.move.line"].search_count([("account_id", "=", acc.id)]) - if usage > 0: + force = code in _L10N_CA_FORCE_CLEAR_CODES + if usage > 0 and not force: _logger.info( "nexa_coa_setup: keeping l10n_ca account %s (%s) — %d postings exist", code, acc.name, usage, @@ -64,6 +69,11 @@ def _clear_l10n_ca_collisions(env): "active": False, }) cleared += 1 + if force and usage > 0: + _logger.info( + "nexa_coa_setup: force-cleared %s despite %d postings (in FORCE_CLEAR set)", + code, usage, + ) _logger.info( "nexa_coa_setup: collision sweep — cleared %d, kept-with-postings %d, not-found %d", cleared, kept_with_postings, not_found, diff --git a/nexa_coa_setup/scripts/fix_gl_codes.py b/nexa_coa_setup/scripts/fix_gl_codes.py new file mode 100644 index 00000000..8435d87e --- /dev/null +++ b/nexa_coa_setup/scripts/fix_gl_codes.py @@ -0,0 +1,40 @@ +"""Renumber a few oddly-coded accounts to cleaner positions. +Idempotent — checks before each move.""" +moves = [ + # (current_code, target_code, expected_name_substr) + ('119100', '115200', 'Due From Shareholder'), + ('119900', '115900', 'Due From Associated Corporations'), +] +print(">>> Easy renames (no postings)") +for old, new, name_hint in moves: + acc = env['account.account'].search([('code', '=', old)], limit=1) + if not acc: + print(f"SKIP {old}: not found") + continue + if name_hint.lower() not in (acc.name or '').lower(): + print(f"SKIP {old}: name doesn't match expected '{name_hint}' (got '{acc.name}')") + continue + conflict = env['account.account'].with_context(active_test=False).search([('code', '=', new)], limit=1) + if conflict: + print(f"SKIP {old}->{new}: target code occupied by {conflict.name}") + continue + acc.code = new + print(f"OK {old} -> {new}: {acc.name}") + +# The 511100 swap — rename legacy first, then renumber ours +print(">>> 511105 -> 511100 swap") +legacy = env['account.account'].with_context(active_test=False).search([('code', '=', '511100'), ('name', 'ilike', 'inside purchases')], limit=1) +ours = env['account.account'].search([('code', '=', '511105'), ('name', 'ilike', 'cloud infrastructure')], limit=1) +if not legacy: + print("legacy 511100 not found (already moved?)") +elif not ours: + print("our 511105 not found (already renamed?)") +else: + # Rename legacy first to free the code + legacy.write({'code': '511100.OLD', 'name': f"(l10n_ca LEGACY) {legacy.name}", 'active': False}) + ours.code = '511100' + print(f"OK legacy 511100 -> 511100.OLD ({legacy.name})") + print(f"OK 511105 -> 511100 ({ours.name})") + +env.cr.commit() +print(">>> done <<<")