Files
Odoo-Modules/nexa_coa_setup/scripts/convert_to_4digit.py
gsinghpal 86e89ca419 feat(nexa_coa_setup): convert chart of accounts to 4-digit codes
Renumbered all 128 Nexa accounts from 6-digit (l10n_ca style) to clean
4-digit codes for readability:

  1000-1999  Assets
    1120  Due From Shareholder
    1210  HST/GST ITC Receivable
    1510-1750  Capital assets + accumulated depreciation
  2000-2999  Liabilities
    2110  HST/GST Collected
    2510  Due To Shareholder
  3000-3999  Equity
    3010  Common Shares
    3510  Retained Earnings — Current
  4000-4999  Revenue
    4010-4050  Recurring (SaaS, Hosting, Support, ...)
    4110-4160  Project work
    4210-4230  Hourly services
    4310-4320  Reseller
  5000-5999  COGS
    5010-5120  Infrastructure & APIs
    5210-5250  Project direct costs
    5310-5320  Resold goods
  6000-6999  Operating expenses
    6010-6092  Personnel (T4)
    6110-6120  Contract labour
    6210-6960  Office/Tech/Marketing/Professional/Insurance/Travel/Training/Banking
  7000+  Other (bad debt, donations, FX, depreciation)

Applied to prod via scripts/convert_to_4digit.py (now committed). XML
codes updated in 01_account_account.xml; XMLIDs preserved so existing
ir.model.data rows on prod stay valid.

Hook constants updated:
- _TAX_REPARTITION_REMAP targets: 118100 -> 1210, 213100 -> 2110, etc.
- _LEGACY_RENAMES new_name strings: 're-class to NNNN' guidance updated
  to 4-digit targets.

Verified -u on prod completes cleanly + all 4 test invoices still post:
  ON     -> 4010 SaaS, total 113.00
  US     -> 4010 SaaS, total 100.00 (zero-rated)
  QC     -> 4010 SaaS, total 114.98
  Westin -> 4210 Consulting, total 169.50

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 20:09:01 -04:00

172 lines
7.0 KiB
Python

"""Map current Nexa CoA codes (6-digit) to a clean 4-digit scheme.
Run on prod via odoo-shell. Updates account_account.code in place."""
# (current_code, new_code, expected_name_substring_for_safety)
CODE_MAP = [
# 1xxx ASSETS
("115200", "1120", "Due From Shareholder"),
("115900", "1130", "Due From Associated Corporations"),
("118100", "1210", "Input Tax Credit"),
("118200", "1220", "Instalments Paid"),
("118300", "1230", "QST Input Tax Refund"),
("151100", "1510", "Computer Hardware"),
("151200", "1520", "Office Furniture"),
("151300", "1530", "Vehicles"),
("151400", "1540", "Leasehold Improvements"),
("151500", "1550", "Acquired Software"),
("151600", "1560", "Tools"),
("154100", "1710", "Acc. Depreciation — Computer"),
("154200", "1720", "Acc. Depreciation — Office"),
("154300", "1730", "Acc. Depreciation — Vehicles"),
("154400", "1740", "Acc. Depreciation — Leasehold"),
("154500", "1750", "Acc. Depreciation — Acquired"),
# 2xxx LIABILITIES
("213100", "2110", "HST/GST Collected"),
("213500", "2120", "QST Collected"),
("214100", "2130", "Net HST/GST Payable"),
("215100", "2210", "Source Deductions Payable — Federal"),
("215200", "2220", "Source Deductions Payable — CPP"),
("215300", "2230", "Source Deductions Payable — EI"),
("216100", "2310", "Federal Payable"),
("216200", "2320", "Provincial Payable"),
("216300", "2330", "Tax Instalments Paid"),
("221100", "2510", "Due To Shareholder"),
("221200", "2520", "Shareholder Loan"),
("222900", "2590", "Due To Associated Corporations"),
# 3xxx EQUITY
("311100", "3010", "Common Shares"),
("311200", "3020", "Preferred Shares"),
("311300", "3030", "Contributed Surplus"),
("321100", "3510", "Retained Earnings — Current"),
("321200", "3520", "Retained Earnings — Prior"),
("321900", "3590", "Dividends Declared"),
# 4xxx REVENUE
("411100", "4010", "SaaS Subscription"),
("411200", "4020", "Hosting"),
("411300", "4030", "Support"),
("411400", "4040", "Domain/SSL"),
("411500", "4050", "Setup"),
("412100", "4110", "Custom Software Development"),
("412200", "4120", "Custom Web Application"),
("412300", "4130", "Custom Website"),
("412400", "4140", "ERP Implementation"),
("412500", "4150", "Mobile App"),
("412600", "4160", "Business App"),
("413100", "4210", "Consulting"),
("413200", "4220", "Training"),
("413300", "4230", "Technical Support — Per-incident"),
("414100", "4310", "Third-party Software Resale"),
("414200", "4320", "Hardware Resale"),
("419100", "4910", "Sales Discounts"),
("419200", "4920", "Sales Returns"),
("419300", "4930", "Bad Debt Recovery"),
# 5xxx COGS
("511100", "5010", "Cloud Infrastructure"),
("511110", "5020", "CDN"),
("511120", "5030", "Backup"),
("511130", "5040", "Database"),
("511140", "5050", "Monitoring"),
("511150", "5060", "SSL"),
("511160", "5070", "DNS"),
("511200", "5110", "Third-party API"),
("511210", "5120", "Per-customer Licensing"),
("512100", "5210", "Subcontracted Labour — Canadian"),
("512110", "5220", "Subcontracted Labour — Foreign"),
("512200", "5230", "Project-specific Software"),
("512300", "5240", "Project Travel"),
("512400", "5250", "Project Hardware"),
("513100", "5310", "Cost of Software Resold"),
("513200", "5320", "Cost of Hardware Resold"),
("519100", "5910", "COGS Adjustments"),
# 6xxx OPERATING EXPENSES
("611100", "6010", "Salaries & Wages — Development"),
("611200", "6020", "Salaries & Wages — Sales"),
("611300", "6030", "Salaries & Wages — Admin"),
("611400", "6040", "Salary — Shareholder"),
("611500", "6050", "Employer CPP"),
("611600", "6060", "Employer EI"),
("611700", "6070", "Employer Health Tax"),
("611800", "6080", "WCB"),
("611900", "6090", "Employee Benefits"),
("611950", "6091", "Bonuses"),
("611960", "6092", "Vacation Pay"),
("612100", "6110", "Contract Labour — Canadian"),
("612200", "6120", "Contract Labour — Foreign"),
("621100", "6210", "Rent"),
("621200", "6220", "Home Office"),
("621300", "6230", "Utilities"),
("621400", "6240", "Internet"),
("621500", "6250", "Office Supplies"),
("621600", "6260", "Cleaning"),
("621700", "6270", "Office Snacks"),
("631100", "6310", "Software — Productivity"),
("631200", "6320", "Software — Development Tools"),
("631300", "6330", "Software — Internal Infrastructure"),
("631400", "6340", "Software — Security"),
("631500", "6350", "Software — Sales"),
("641100", "6410", "Advertising — Digital"),
("641200", "6420", "Advertising — Content"),
("641300", "6430", "Trade Shows"),
("641400", "6440", "Promotional"),
("641500", "6450", "Website — Own"),
("651100", "6510", "Legal Fees"),
("651200", "6520", "Accounting"),
("651300", "6530", "Tax Preparation"),
("651400", "6540", "Business Consulting"),
("661100", "6610", "Insurance — Commercial General"),
("661200", "6620", "Insurance — Professional Liability"),
("661300", "6630", "Insurance — Cyber"),
("661400", "6640", "Insurance — Property"),
("661500", "6650", "Insurance — Directors"),
("671100", "6710", "Travel"),
("671200", "6720", "Meals"),
("671300", "6730", "Vehicle — Operating"),
("671400", "6740", "Mileage"),
("681100", "6810", "Conferences"),
("681200", "6820", "Courses"),
("681300", "6830", "Books"),
("681400", "6840", "Professional Memberships"),
("691100", "6910", "Bank Service Charges"),
("691200", "6920", "Merchant Processing"),
("691300", "6930", "Wire Transfer"),
("691400", "6940", "Interest Expense — Bank"),
("691500", "6950", "Interest Expense — Credit Cards"),
("691600", "6960", "Late Payment Penalties"),
# 7xxx OTHER (was 699xxx)
("699100", "7010", "Bad Debt Expense"),
("699200", "7020", "Donations"),
("699300", "7030", "Penalties & Fines"),
("699400", "7040", "Realized FX Losses"),
("699500", "7050", "Depreciation"),
]
ok = 0
skipped = 0
missing = 0
for old_code, new_code, expected_name_part in CODE_MAP:
acc = env['account.account'].search([('code', '=', old_code)], limit=1)
if not acc:
print(f"MISS {old_code}{new_code}: not found")
missing += 1
continue
if expected_name_part.lower() not in (acc.name or '').lower():
print(f"SKIP {old_code}{new_code}: name '{acc.name}' doesn't contain '{expected_name_part}'")
skipped += 1
continue
# Check target code is free
conflict = env['account.account'].with_context(active_test=False).search([('code', '=', new_code)], limit=1)
if conflict:
print(f"SKIP {old_code}{new_code}: target occupied by {conflict.name}")
skipped += 1
continue
acc.code = new_code
ok += 1
print(f">>> Renumbered {ok}, skipped {skipped}, missing {missing} of {len(CODE_MAP)}")
env.cr.commit()