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>
172 lines
7.0 KiB
Python
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()
|