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