Files
gsinghpal 3c959771ae feat(nexa_coa_setup): pre_init_hook to clear l10n_ca code collisions
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) <original>'
- 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) <noreply@anthropic.com>
2026-05-12 18:51:25 -04:00

135 lines
4.9 KiB
Python

# -*- coding: utf-8 -*-
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.
Each helper is idempotent — safe to re-run on -u.
"""
_logger.info("nexa_coa_setup: post_init_hook starting")
_normalize_company_hst_number(env)
_archive_unused_l10n_ca_accounts(env)
_rename_legacy_accounts(env)
_lock_fiscal_year_2025(env)
_logger.info("nexa_coa_setup: post_init_hook complete")
def _normalize_company_hst_number(env):
"""Convert '741224877' to '741224877 RT0001' if not already in full form."""
company = env.ref("base.main_company", raise_if_not_found=False)
if not company:
return
vat = (company.partner_id.vat or "").strip()
if vat == "741224877":
company.partner_id.vat = "741224877 RT0001"
_logger.info("nexa_coa_setup: normalized HST# to '741224877 RT0001'")
def _archive_unused_l10n_ca_accounts(env):
"""Stub — filled in Phase 4. Archives ~370 unused accounts."""
pass
def _rename_legacy_accounts(env):
"""Stub — filled in Phase 4. Renames the 14xx/15xx legacy accounts."""
pass
def _lock_fiscal_year_2025(env):
"""Try to set fiscalyear_lock_date = 2025-12-31 on main company.
If Odoo blocks the lock because unreconciled bank statement lines or other
open items exist in the period, log a clear warning and continue. The user
can set the lock manually via Accounting > Configuration > Settings > Lock
Dates once those items are cleaned up.
"""
from datetime import date
from odoo.exceptions import RedirectWarning, UserError, ValidationError
company = env.ref("base.main_company", raise_if_not_found=False)
if not company:
return
target = date(2025, 12, 31)
if company.fiscalyear_lock_date and company.fiscalyear_lock_date >= target:
_logger.info("nexa_coa_setup: fiscalyear_lock_date already at or after 2025-12-31")
return
try:
company.fiscalyear_lock_date = target
_logger.info("nexa_coa_setup: fiscalyear_lock_date set to 2025-12-31")
except (RedirectWarning, UserError, ValidationError) as exc:
_logger.warning(
"nexa_coa_setup: could not auto-lock fiscal year 2025-12-31. "
"Reason: %s. Set the lock manually via Accounting > Configuration > "
"Settings > Lock Dates after the unreconciled items in the period "
"are cleaned up.",
exc,
)