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