# Nexa Systems — Chart of Accounts Setup Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Implement the CoA design in `docs/superpowers/specs/2026-05-12-nexa-coa-design.md` against the live odoo-nexa `nexamain` database — clean account structure, automated taxes, SR&ED-ready analytic plans, intercompany-aware partner setup. **Architecture:** Build a new Odoo module `nexa_coa_setup` that declares new accounts/taxes/fiscal-positions/analytic-plans via XML data files (declarative, idempotent, version-controlled), and uses post-install Python hooks for imperative one-shot operations (archive unused accounts, rename legacy accounts, normalize HST#, lock fiscal year). Apply to nexamain via standard `odoo -i nexa_coa_setup`. Take a pg_dump before any destructive step. **Tech Stack:** - Odoo 19 Enterprise on odoo-nexa (192.168.1.111), Docker container `odoo-nexa-app`, DB `nexamain` - Postgres 16 (pgvector image) on container `odoo-nexa-db` - Python 3.12 (Odoo's runtime) - l10n_ca localization (already loaded — we extend, not replace) - Local dev: `/Users/gurpreet/Github/Odoo-Modules/nexa_coa_setup/` - Server addons path: `/opt/odoo/custom-addons/` (bind-mounted as `/mnt/extra-addons` in container) - Deploy: `rsync` from Mac → odoo-nexa, then `docker exec ... odoo -u nexa_coa_setup` **Safety rules (read before every destructive step):** 1. **Always** pg_dump before any phase that archives, renames, or locks data 2. **Never** delete an account that has postings — archive (`active=False`) only 3. **Lock period BEFORE archive sweep** so prior years can't be retroactively damaged 4. **Test on a staging clone first** for any phase that touches >50 records 5. **Each phase commits independently** — no batch commits across phases --- ## File Structure Create the following under `/Users/gurpreet/Github/Odoo-Modules/nexa_coa_setup/`: ``` nexa_coa_setup/ ├── __init__.py # imports models, hooks ├── __manifest__.py # module metadata + data file list ├── hooks.py # post_init_hook for imperative operations ├── data/ │ ├── 01_account_account.xml # New chart of accounts entries (~70 accounts) │ ├── 02_account_journal.xml # New journals (EXP if missing) │ ├── 03_account_tax.xml # Curated tax set (additions if any) │ ├── 04_account_fiscal_position.xml # 8 fiscal positions │ ├── 05_account_analytic_plan.xml # Project / Department / SR&ED Tag plans │ ├── 06_account_analytic_account.xml # Seed analytic accounts (departments, SR&ED tags) │ ├── 07_product_category.xml # Service product categories │ ├── 08_res_partner_category.xml # 'RP-Associated' partner tag │ ├── 09_res_partner.xml # Westin & Divine partner records │ └── 10_account_reconcile_model.xml # Bank reconciliation rules ├── models/ │ └── __init__.py # (no custom models needed; placeholder) ├── security/ │ └── ir.model.access.csv # (empty; no new models) └── README.md # operating runbook ``` **Each XML data file**: - Uses `` so re-running `-u` updates them (no `noupdate="1"`) - Uses stable XMLIDs prefixed `nexa_coa_setup.` so future updates can find records - Sets `forcecreate="True"` where appropriate **hooks.py responsibilities** (idempotent — safe to re-run): - `pre_init_hook(env)`: pg_dump verification reminder; safety bail-out if pre-conditions not met - `post_init_hook(env)`: normalize HST# format, archive unused l10n_ca accounts, rename legacy 14xx/15xx accounts, lock fiscal year at 2025-12-31 --- ## Phase 0 — Safety, Backup, and Staging Clone ### Task 0.1: Take a full pg_dump of nexamain BEFORE anything **Files:** none - [ ] **Step 1: Verify the database is reachable and quiet** Run from local Mac: ```bash ssh odoo-nexa "docker exec odoo-nexa-db psql -U odoo -d nexamain -c 'SELECT count(*) FROM account_move;'" ``` Expected: a single number around `776` (the current entry count). - [ ] **Step 2: pg_dump to a timestamped file on the server** Run: ```bash ssh odoo-nexa "docker exec odoo-nexa-db pg_dump -U odoo -d nexamain -F c -Z 9 -f /tmp/nexamain_pre_coa_$(date +%Y%m%d_%H%M%S).dump && ls -lh /tmp/nexamain_pre_coa_*.dump | tail -1" ``` Expected: file size in the tens of MB. - [ ] **Step 3: Copy the dump to your Mac for off-server safety** Run: ```bash mkdir -p ~/Backups/odoo-nexa && scp odoo-nexa:/tmp/nexamain_pre_coa_*.dump ~/Backups/odoo-nexa/ && ls -lh ~/Backups/odoo-nexa/ | tail -1 ``` Expected: the dump file on local Mac. - [ ] **Step 4: Document backup location in a NOTE file (one line)** Run: ```bash echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) — pg_dump before nexa_coa_setup install — $(ls -1 ~/Backups/odoo-nexa/nexamain_pre_coa_*.dump | tail -1)" >> ~/Backups/odoo-nexa/RESTORE_LOG.md cat ~/Backups/odoo-nexa/RESTORE_LOG.md ``` Expected: log entry visible. ### Task 0.2: Create a staging clone for dry-run testing **Files:** none — creates DB `nexamain_staging` - [ ] **Step 1: Drop any existing staging DB and create fresh from dump** Run: ```bash ssh odoo-nexa "docker exec odoo-nexa-db psql -U odoo -d postgres -c 'DROP DATABASE IF EXISTS nexamain_staging;' && docker exec odoo-nexa-db psql -U odoo -d postgres -c 'CREATE DATABASE nexamain_staging OWNER odoo;'" ``` Expected: `DROP DATABASE` then `CREATE DATABASE` confirmations. - [ ] **Step 2: Restore the dump into staging** Run: ```bash ssh odoo-nexa "DUMP=\$(ls -1t /tmp/nexamain_pre_coa_*.dump | head -1); docker exec odoo-nexa-db pg_restore -U odoo -d nexamain_staging -j 4 \$DUMP 2>&1 | tail -20" ``` Expected: restore completes; some "constraint already exists" warnings are OK. - [ ] **Step 3: Verify staging matches prod row counts** Run: ```bash ssh odoo-nexa "docker exec odoo-nexa-db psql -U odoo -d nexamain_staging -c 'SELECT (SELECT count(*) FROM account_account) AS accounts, (SELECT count(*) FROM account_move) AS moves, (SELECT count(*) FROM account_tax) AS taxes;'" ``` Expected: same counts as prod (~426 accounts, ~776 moves, ~49 active taxes). - [ ] **Step 4: Set staging fiscalyear_lock_date to match prod (none) and commit nothing — staging is a working copy** No git commit; this is operational state. --- ## Phase 1 — Module Skeleton ### Task 1.1: Create the `nexa_coa_setup` module skeleton **Files:** - Create: `nexa_coa_setup/__init__.py` - Create: `nexa_coa_setup/__manifest__.py` - Create: `nexa_coa_setup/hooks.py` - Create: `nexa_coa_setup/models/__init__.py` - Create: `nexa_coa_setup/security/ir.model.access.csv` - Create: `nexa_coa_setup/README.md` - [ ] **Step 1: Create module directory tree** Run from local Mac: ```bash cd /Users/gurpreet/Github/Odoo-Modules mkdir -p nexa_coa_setup/data nexa_coa_setup/models nexa_coa_setup/security ls nexa_coa_setup/ ``` Expected: `data/`, `models/`, `security/`. - [ ] **Step 2: Write `__init__.py`** Create file `/Users/gurpreet/Github/Odoo-Modules/nexa_coa_setup/__init__.py` with content: ```python from . import models from .hooks import post_init_hook ``` - [ ] **Step 3: Write `__manifest__.py`** Create file `/Users/gurpreet/Github/Odoo-Modules/nexa_coa_setup/__manifest__.py` with content: ```python # -*- coding: utf-8 -*- # Copyright 2026 Nexa Systems Inc. # License OPL-1 { "name": "Nexa Systems — Chart of Accounts Setup", "version": "19.0.1.0.0", "category": "Accounting/Localizations/Chart of Accounts", "summary": "Custom CoA, taxes, fiscal positions, analytic plans, and intercompany partner setup for Nexa Systems Inc.", "author": "Nexa Systems Inc.", "website": "https://nexasystems.ca", "license": "OPL-1", "depends": [ "account", "account_accountant", "l10n_ca", "analytic", "sale_management", "purchase", "sale_subscription", ], "data": [ "security/ir.model.access.csv", "data/01_account_account.xml", "data/02_account_journal.xml", "data/03_account_tax.xml", "data/04_account_fiscal_position.xml", "data/05_account_analytic_plan.xml", "data/06_account_analytic_account.xml", "data/07_product_category.xml", "data/08_res_partner_category.xml", "data/09_res_partner.xml", "data/10_account_reconcile_model.xml", ], "post_init_hook": "post_init_hook", "installable": True, "application": False, "auto_install": False, } ``` - [ ] **Step 4: Write `models/__init__.py`** (empty placeholder) Create file `/Users/gurpreet/Github/Odoo-Modules/nexa_coa_setup/models/__init__.py` with content: ```python # no custom models — placeholder for future extensions ``` - [ ] **Step 5: Write `security/ir.model.access.csv`** (header only — no new models) Create file `/Users/gurpreet/Github/Odoo-Modules/nexa_coa_setup/security/ir.model.access.csv` with content: ```csv id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink ``` - [ ] **Step 6: Write `hooks.py` with empty post_init_hook (filled in Phase 4)** Create file `/Users/gurpreet/Github/Odoo-Modules/nexa_coa_setup/hooks.py` with content: ```python # -*- coding: utf-8 -*- import logging _logger = logging.getLogger(__name__) 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): """Set fiscalyear_lock_date = 2025-12-31 on main company.""" from datetime import date company = env.ref("base.main_company", raise_if_not_found=False) if not company: return target = date(2025, 12, 31) if not company.fiscalyear_lock_date or company.fiscalyear_lock_date < target: company.fiscalyear_lock_date = target _logger.info("nexa_coa_setup: fiscalyear_lock_date set to 2025-12-31") ``` - [ ] **Step 7: Write minimal `README.md`** Create file `/Users/gurpreet/Github/Odoo-Modules/nexa_coa_setup/README.md` with content: ```markdown # Nexa Systems — Chart of Accounts Setup Custom Odoo 19 module that configures the chart of accounts, taxes, fiscal positions, analytic plans, and partner records for Nexa Systems Inc. ## Install ``` docker exec odoo-nexa-app odoo -c /etc/odoo/odoo.conf -d nexamain \ -i nexa_coa_setup --no-http --stop-after-init ``` ## Update ``` docker exec odoo-nexa-app odoo -c /etc/odoo/odoo.conf -d nexamain \ -u nexa_coa_setup --no-http --stop-after-init ``` ## Design reference See `docs/superpowers/specs/2026-05-12-nexa-coa-design.md`. ## Safety Always take a pg_dump BEFORE running `-i` or `-u`. See `docs/superpowers/plans/2026-05-12-nexa-coa-setup.md` Phase 0. ``` - [ ] **Step 8: Create empty data files (placeholders, filled in later phases)** Run: ```bash cd /Users/gurpreet/Github/Odoo-Modules/nexa_coa_setup/data for f in 01_account_account 02_account_journal 03_account_tax 04_account_fiscal_position \ 05_account_analytic_plan 06_account_analytic_account 07_product_category \ 08_res_partner_category 09_res_partner 10_account_reconcile_model; do printf '\n\n \n \n\n' > ${f}.xml done ls -la ``` Expected: 10 empty XML stub files. - [ ] **Step 9: Commit the skeleton** Run: ```bash cd /Users/gurpreet/Github/Odoo-Modules git add nexa_coa_setup/ git commit -m "feat(nexa_coa_setup): module skeleton with hooks stub" ``` ### Task 1.2: Deploy skeleton to odoo-nexa and install **Files:** none (deploys to server) - [ ] **Step 1: rsync the module to the server** Run from Mac: ```bash rsync -avz --delete /Users/gurpreet/Github/Odoo-Modules/nexa_coa_setup/ odoo-nexa:/opt/odoo/custom-addons/nexa_coa_setup/ ``` Expected: rsync output showing file transfers. - [ ] **Step 2: Install on staging first** Run: ```bash ssh odoo-nexa "docker exec odoo-nexa-app odoo -c /etc/odoo/odoo.conf -d nexamain_staging -i nexa_coa_setup --no-http --stop-after-init 2>&1 | tail -20" ``` Expected: `Modules loaded.` line at end, no traceback. - [ ] **Step 3: Verify install on staging** Run: ```bash ssh odoo-nexa "docker exec odoo-nexa-db psql -U odoo -d nexamain_staging -c \"SELECT name, state FROM ir_module_module WHERE name = 'nexa_coa_setup';\"" ``` Expected: `nexa_coa_setup | installed`. - [ ] **Step 4: Verify HST# was normalized on staging** Run: ```bash ssh odoo-nexa "docker exec odoo-nexa-db psql -U odoo -d nexamain_staging -c \"SELECT vat FROM res_partner WHERE id = (SELECT partner_id FROM res_company WHERE id = 1);\"" ``` Expected: `741224877 RT0001`. - [ ] **Step 5: Verify fiscal year lock on staging** Run: ```bash ssh odoo-nexa "docker exec odoo-nexa-db psql -U odoo -d nexamain_staging -c \"SELECT name, fiscalyear_lock_date FROM res_company WHERE id = 1;\"" ``` Expected: `Nexa Systems Inc | 2025-12-31`. - [ ] **Step 6: Do NOT install on prod yet — staging only at this stage** The module is just a skeleton; later phases fill it. --- ## Phase 2 — Chart of Accounts (Additions) ### Task 2.1: Define new asset accounts (1xxxxx) **Files:** - Modify: `nexa_coa_setup/data/01_account_account.xml` - [ ] **Step 1: Write XML for new asset accounts** Replace content of `nexa_coa_setup/data/01_account_account.xml` with: ```xml 115100 Due From Shareholder — Gurpreet asset_current 115900 Due From Associated Corporations asset_current 118100 HST/GST Input Tax Credit (ITC) Receivable asset_current 118200 HST/GST Instalments Paid asset_current 118300 QST Input Tax Refund Receivable asset_current 151100 Computer Hardware & Equipment (CCA Class 50) asset_fixed 151200 Office Furniture & Equipment (CCA Class 8) asset_fixed 151300 Vehicles (CCA Class 10/10.1) asset_fixed 151400 Leasehold Improvements (CCA Class 13) asset_fixed 151500 Acquired Software & Intangibles (CCA Class 14.1) asset_fixed 151600 Tools & Small Equipment <$500 (CCA Class 12) asset_fixed 154100 Acc. Depreciation — Computer Hardware asset_fixed 154200 Acc. Depreciation — Office Furniture asset_fixed 154300 Acc. Depreciation — Vehicles asset_fixed 154400 Acc. Depreciation — Leasehold Improvements asset_fixed 154500 Acc. Depreciation — Acquired Software asset_fixed ``` - [ ] **Step 2: Deploy and update on staging** Run: ```bash rsync -avz /Users/gurpreet/Github/Odoo-Modules/nexa_coa_setup/ odoo-nexa:/opt/odoo/custom-addons/nexa_coa_setup/ ssh odoo-nexa "docker exec odoo-nexa-app odoo -c /etc/odoo/odoo.conf -d nexamain_staging -u nexa_coa_setup --no-http --stop-after-init 2>&1 | tail -10" ``` Expected: `Modules loaded.` no traceback. - [ ] **Step 3: Verify accounts exist on staging** Run: ```bash ssh odoo-nexa "docker exec odoo-nexa-db psql -U odoo -d nexamain_staging -c \"SELECT code_store->>'1' AS code, name->>'en_US' AS name FROM account_account WHERE code_store->>'1' IN ('115100','115900','118100','118200','118300','151100','151200','151300','151400','151500','151600','154100','154200','154300','154400','154500') ORDER BY code_store->>'1';\"" ``` Expected: 16 rows with correct codes and names. - [ ] **Step 4: Commit** Run: ```bash cd /Users/gurpreet/Github/Odoo-Modules git add nexa_coa_setup/data/01_account_account.xml git commit -m "feat(nexa_coa_setup): asset accounts (115xxx, 118xxx, 151xxx, 154xxx)" ``` ### Task 2.2: Define new liability accounts (2xxxxx) **Files:** - Modify: `nexa_coa_setup/data/01_account_account.xml` - [ ] **Step 1: Append liability accounts before `` closing tag** Add the following block just before the `` line in `01_account_account.xml`: ```xml 213100 HST/GST Collected on Sales liability_current 213500 QST Collected on Sales liability_current 214100 Net HST/GST Payable liability_current 215100 Source Deductions Payable — Federal Tax liability_current 215200 Source Deductions Payable — CPP liability_current 215300 Source Deductions Payable — EI liability_current 216100 Corporate Income Tax — Federal Payable liability_current 216200 Corporate Income Tax — Provincial Payable liability_current 216300 Corporate Tax Instalments Paid asset_current 221100 Due To Shareholder — Gurpreet (short-term) liability_current 221200 Shareholder Loan — Gurpreet (long-term) liability_non_current 222900 Due To Associated Corporations liability_current ``` - [ ] **Step 2: Deploy and update on staging** Run: ```bash rsync -avz /Users/gurpreet/Github/Odoo-Modules/nexa_coa_setup/ odoo-nexa:/opt/odoo/custom-addons/nexa_coa_setup/ ssh odoo-nexa "docker exec odoo-nexa-app odoo -c /etc/odoo/odoo.conf -d nexamain_staging -u nexa_coa_setup --no-http --stop-after-init 2>&1 | tail -5" ``` Expected: no errors. - [ ] **Step 3: Verify** Run: ```bash ssh odoo-nexa "docker exec odoo-nexa-db psql -U odoo -d nexamain_staging -c \"SELECT count(*) FROM account_account WHERE code_store->>'1' IN ('213100','213500','214100','215100','215200','215300','216100','216200','216300','221100','221200','222900');\"" ``` Expected: `12`. - [ ] **Step 4: Commit** Run: ```bash cd /Users/gurpreet/Github/Odoo-Modules git add nexa_coa_setup/data/01_account_account.xml git commit -m "feat(nexa_coa_setup): liability accounts (213xxx, 215xxx, 216xxx, 221xxx, 222xxx)" ``` ### Task 2.3: Define new equity accounts (3xxxxx) **Files:** - Modify: `nexa_coa_setup/data/01_account_account.xml` - [ ] **Step 1: Append equity accounts before ``** Add this block before ``: ```xml 311100 Share Capital — Common Shares equity 311200 Share Capital — Preferred Shares equity 311300 Contributed Surplus equity 321100 Retained Earnings — Current Year equity 321200 Retained Earnings — Prior Years equity 321900 Dividends Declared equity ``` - [ ] **Step 2: Deploy, update, verify** Run: ```bash rsync -avz /Users/gurpreet/Github/Odoo-Modules/nexa_coa_setup/ odoo-nexa:/opt/odoo/custom-addons/nexa_coa_setup/ ssh odoo-nexa "docker exec odoo-nexa-app odoo -c /etc/odoo/odoo.conf -d nexamain_staging -u nexa_coa_setup --no-http --stop-after-init 2>&1 | tail -3" ssh odoo-nexa "docker exec odoo-nexa-db psql -U odoo -d nexamain_staging -c \"SELECT count(*) FROM account_account WHERE code_store->>'1' IN ('311100','311200','311300','321100','321200','321900');\"" ``` Expected: `6`. - [ ] **Step 3: Commit** Run: ```bash cd /Users/gurpreet/Github/Odoo-Modules git add nexa_coa_setup/data/01_account_account.xml git commit -m "feat(nexa_coa_setup): equity accounts (311xxx, 321xxx)" ``` ### Task 2.4: Define revenue accounts (4xxxxx) — 15 service-line accounts **Files:** - Modify: `nexa_coa_setup/data/01_account_account.xml` - [ ] **Step 1: Append revenue accounts before ``** Add this block before ``: ```xml 411100 SaaS Subscription Revenue income 411200 Hosting & Infrastructure Revenue income 411300 Support & Maintenance Contracts Revenue income 411400 Domain/SSL/Renewal Pass-through Revenue income 411500 Setup / Onboarding Fees Revenue income 412100 Custom Software Development Revenue income 412200 Custom Web Application Development Revenue income 412300 Custom Website Development Revenue income 412400 ERP Implementation & Customization Revenue income 412500 Mobile App Development Revenue income 412600 Business App / Integration Revenue income 413100 Consulting & Advisory Revenue income 413200 Training & Workshops Revenue income 413300 Technical Support — Per-incident / Hourly Revenue income 414100 Third-party Software Resale Revenue income 414200 Hardware Resale Revenue income 419100 Sales Discounts income 419200 Sales Returns & Refunds income 419300 Bad Debt Recovery income_other ``` - [ ] **Step 2: Deploy, update, verify** Run: ```bash rsync -avz /Users/gurpreet/Github/Odoo-Modules/nexa_coa_setup/ odoo-nexa:/opt/odoo/custom-addons/nexa_coa_setup/ ssh odoo-nexa "docker exec odoo-nexa-app odoo -c /etc/odoo/odoo.conf -d nexamain_staging -u nexa_coa_setup --no-http --stop-after-init 2>&1 | tail -3" ssh odoo-nexa "docker exec odoo-nexa-db psql -U odoo -d nexamain_staging -c \"SELECT count(*) FROM account_account WHERE code_store->>'1' LIKE '41%' AND code_store->>'1' >= '411100';\"" ``` Expected: `19` (5 recurring + 6 project + 3 services + 2 reseller + 3 adjustments). - [ ] **Step 3: Commit** Run: ```bash cd /Users/gurpreet/Github/Odoo-Modules git add nexa_coa_setup/data/01_account_account.xml git commit -m "feat(nexa_coa_setup): revenue accounts (4xxxxx) — 19 service-line accounts" ``` ### Task 2.5: Define COGS accounts (5xxxxx) — 16 direct cost accounts **Files:** - Modify: `nexa_coa_setup/data/01_account_account.xml` - [ ] **Step 1: Append COGS accounts before ``** Add this block: ```xml 511100 Cloud Infrastructure (AWS, Hetzner, OVH, DigitalOcean, Linode) expense_direct_cost 511110 CDN & Edge Services (Cloudflare, Fastly) expense_direct_cost 511120 Backup & Storage Services expense_direct_cost 511130 Database & Backend Services (Supabase, hosted Postgres, Redis) expense_direct_cost 511140 Monitoring & Observability (customer-facing only) expense_direct_cost 511150 SSL Certificates & Domains (wholesale for resale) expense_direct_cost 511160 DNS & Email Hosting (wholesale for resale) expense_direct_cost 511200 Third-party API Costs (Twilio, SendGrid, OpenAI) expense_direct_cost 511210 Per-customer Licensing & Royalties expense_direct_cost 512100 Subcontracted Labour — Canadian (T4A) — SR&ED-eligible expense_direct_cost 512110 Subcontracted Labour — Foreign — NOT SR&ED-eligible expense_direct_cost 512200 Project-specific Software & Licenses expense_direct_cost 512300 Project Travel & Onsite (rebilled) expense_direct_cost 512400 Project Hardware (passed through) expense_direct_cost 513100 Cost of Software Resold expense_direct_cost 513200 Cost of Hardware Resold expense_direct_cost 519100 COGS Adjustments / Write-offs expense_direct_cost ``` - [ ] **Step 2: Deploy, update, verify** Run: ```bash rsync -avz /Users/gurpreet/Github/Odoo-Modules/nexa_coa_setup/ odoo-nexa:/opt/odoo/custom-addons/nexa_coa_setup/ ssh odoo-nexa "docker exec odoo-nexa-app odoo -c /etc/odoo/odoo.conf -d nexamain_staging -u nexa_coa_setup --no-http --stop-after-init 2>&1 | tail -3" ssh odoo-nexa "docker exec odoo-nexa-db psql -U odoo -d nexamain_staging -c \"SELECT count(*) FROM account_account WHERE code_store->>'1' LIKE '5%' AND code_store->>'1' < '520000';\"" ``` Expected: `17` (16 COGS + 1 adjustments). - [ ] **Step 3: Commit** Run: ```bash cd /Users/gurpreet/Github/Odoo-Modules git add nexa_coa_setup/data/01_account_account.xml git commit -m "feat(nexa_coa_setup): COGS accounts (5xxxxx) — 17 direct cost accounts" ``` ### Task 2.6: Define operating expense accounts (6xxxxx) — 52 accounts **Files:** - Modify: `nexa_coa_setup/data/01_account_account.xml` - [ ] **Step 1: Append OpEx accounts before ``** Add this block: ```xml 611100 Salaries & Wages — Development (SR&ED-eligible) expense 611200 Salaries & Wages — Sales & Marketing expense 611300 Salaries & Wages — Admin & Operations expense 611400 Salary — Shareholder/Officer (Gurpreet) expense 611500 Employer CPP / QPP Contributions expense 611600 Employer EI Premiums expense 611700 Employer Health Tax (EHT/QHST) expense 611800 WCB / WSIB Premiums expense 611900 Employee Benefits (health, dental, group) expense 611950 Bonuses & Incentives expense 611960 Vacation Pay Accrual expense 612100 Contract Labour — Canadian (admin/marketing/freelance) expense 612200 Contract Labour — Foreign expense 621100 Rent — Commercial Office expense 621200 Home Office — Business Portion expense 621300 Utilities — Commercial expense 621400 Internet & Phone — Business expense 621500 Office Supplies & Consumables expense 621600 Cleaning & Maintenance expense 621700 Office Snacks & Refreshments expense 631100 Software — Productivity (M365, Slack, Notion, Linear, GitHub) expense 631200 Software — Development Tools (Cursor, Figma, IDEs) expense 631300 Software — Internal Infrastructure expense 631400 Software — Security & IT expense 631500 Software — Sales & Marketing expense 641100 Advertising — Digital Ads expense 641200 Advertising — Content / SEO expense 641300 Trade Shows & Conferences expense 641400 Promotional Items / Branded Swag expense 641500 Website — Own (nexasystems.ca) expense 651100 Legal Fees — General expense 651200 Accounting & Bookkeeping expense 651300 Tax Preparation (T2, T1, GST/HST) expense 651400 Business Consulting expense 661100 Insurance — Commercial General Liability expense 661200 Insurance — Professional Liability / E&O expense 661300 Insurance — Cyber Liability expense 661400 Insurance — Property expense 661500 Insurance — Directors & Officers expense 671100 Travel — Flights, Hotels, Ground Transport expense 671200 Meals & Entertainment — 50% Deductible expense 671300 Vehicle — Operating (gas, insurance, repairs, parking) expense 671400 Mileage Reimbursement — Personal Vehicle expense 681100 Conferences & Seminars (registration) expense 681200 Courses & Certifications expense 681300 Books & Publications expense 681400 Professional Memberships & Dues expense 691100 Bank Service Charges expense 691200 Merchant Processing Fees (Stripe, PayPal, Square) expense 691300 Wire Transfer & FX Fees expense 691400 Interest Expense — Bank Loans / LOC expense 691500 Interest Expense — Credit Cards expense 691600 Late Payment Penalties — Non-deductible expense 699100 Bad Debt Expense expense 699200 Donations & Sponsorships (deductible) expense 699300 Penalties & Fines — Non-deductible expense 699400 Realized FX Losses expense 699500 Depreciation / CCA Expense expense ``` - [ ] **Step 2: Deploy, update, verify** Run: ```bash rsync -avz /Users/gurpreet/Github/Odoo-Modules/nexa_coa_setup/ odoo-nexa:/opt/odoo/custom-addons/nexa_coa_setup/ ssh odoo-nexa "docker exec odoo-nexa-app odoo -c /etc/odoo/odoo.conf -d nexamain_staging -u nexa_coa_setup --no-http --stop-after-init 2>&1 | tail -3" ssh odoo-nexa "docker exec odoo-nexa-db psql -U odoo -d nexamain_staging -c \"SELECT count(*) FROM account_account WHERE code_store->>'1' LIKE '6%' AND code_store->>'1' < '700000';\"" ``` Expected: at least `52` (might be more if l10n_ca already has some in this range). - [ ] **Step 3: Commit** Run: ```bash cd /Users/gurpreet/Github/Odoo-Modules git add nexa_coa_setup/data/01_account_account.xml git commit -m "feat(nexa_coa_setup): operating expense accounts (6xxxxx) — 52 accounts" ``` --- ## Phase 3 — Analytic Plans (the SR&ED engine) ### Task 3.1: Create Project / Department / SR&ED Tag analytic plans **Files:** - Modify: `nexa_coa_setup/data/05_account_analytic_plan.xml` - [ ] **Step 1: Write the plans XML** Replace content of `nexa_coa_setup/data/05_account_analytic_plan.xml` with: ```xml Project mandatory Department mandatory SR&ED Tag optional ``` - [ ] **Step 2: Deploy, update, verify** Run: ```bash rsync -avz /Users/gurpreet/Github/Odoo-Modules/nexa_coa_setup/ odoo-nexa:/opt/odoo/custom-addons/nexa_coa_setup/ ssh odoo-nexa "docker exec odoo-nexa-app odoo -c /etc/odoo/odoo.conf -d nexamain_staging -u nexa_coa_setup --no-http --stop-after-init 2>&1 | tail -3" ssh odoo-nexa "docker exec odoo-nexa-db psql -U odoo -d nexamain_staging -c \"SELECT id, name, default_applicability FROM account_analytic_plan ORDER BY id;\"" ``` Expected: at least 3 plans (Project, Department, SR&ED Tag). - [ ] **Step 3: Commit** Run: ```bash cd /Users/gurpreet/Github/Odoo-Modules git add nexa_coa_setup/data/05_account_analytic_plan.xml git commit -m "feat(nexa_coa_setup): analytic plans — Project, Department, SR&ED Tag" ``` ### Task 3.2: Seed Department analytic accounts **Files:** - Modify: `nexa_coa_setup/data/06_account_analytic_account.xml` - [ ] **Step 1: Write the Department analytic accounts** Replace content of `nexa_coa_setup/data/06_account_analytic_account.xml` with: ```xml Development DEPT-DEV Sales & Marketing DEPT-SALES Admin & Operations DEPT-ADMIN Hosting Operations DEPT-HOSTING T4 Dev Salary — full proxy SRED-T4-DEV-SALARY Specified Employee Salary — 75% cap SRED-SPECIFIED-EMPLOYEE Contractor CA Arm's Length — 80% eligible SRED-CONTRACTOR-CA-ARM-LENGTH Contractor CA Non-Arm's Length SRED-CONTRACTOR-CA-NON-ARM-LENGTH Materials Consumed in R&D SRED-MATERIALS-CONSUMED Overhead Proxy Basis (direct labour basis) SRED-OVERHEAD-PROXY-BASIS Not Eligible (default) NOT-ELIGIBLE ``` - [ ] **Step 2: Deploy, update, verify** Run: ```bash rsync -avz /Users/gurpreet/Github/Odoo-Modules/nexa_coa_setup/ odoo-nexa:/opt/odoo/custom-addons/nexa_coa_setup/ ssh odoo-nexa "docker exec odoo-nexa-app odoo -c /etc/odoo/odoo.conf -d nexamain_staging -u nexa_coa_setup --no-http --stop-after-init 2>&1 | tail -3" ssh odoo-nexa "docker exec odoo-nexa-db psql -U odoo -d nexamain_staging -c \"SELECT a.code, a.name, p.name AS plan FROM account_analytic_account a JOIN account_analytic_plan p ON p.id = a.plan_id WHERE a.code LIKE 'DEPT-%' OR a.code LIKE 'SRED-%' OR a.code = 'NOT-ELIGIBLE' ORDER BY p.name, a.code;\"" ``` Expected: 4 departments + 7 SR&ED tag values = 11 rows. - [ ] **Step 3: Commit** Run: ```bash cd /Users/gurpreet/Github/Odoo-Modules git add nexa_coa_setup/data/06_account_analytic_account.xml git commit -m "feat(nexa_coa_setup): seed analytic accounts — departments + SR&ED tags" ``` --- ## Phase 4 — Hooks: Archive Unused, Rename Legacy ### Task 4.1: Implement `_archive_unused_l10n_ca_accounts` **Files:** - Modify: `nexa_coa_setup/hooks.py` - [ ] **Step 1: Replace the stub function** In `nexa_coa_setup/hooks.py`, replace the `_archive_unused_l10n_ca_accounts` function with: ```python def _archive_unused_l10n_ca_accounts(env): """Archive l10n_ca accounts that have zero postings. We never delete (preserves historical integrity); active=False removes them from dropdowns and reports. Idempotent — re-running has no effect. """ # Find accounts with no journal items env.cr.execute(""" SELECT a.id FROM account_account a WHERE a.active = true AND NOT EXISTS ( SELECT 1 FROM account_move_line aml WHERE aml.account_id = a.id ) AND NOT EXISTS ( SELECT 1 FROM ir_model_data d WHERE d.model = 'account.account' AND d.res_id = a.id AND d.module = 'nexa_coa_setup' ) """) ids_to_archive = [r[0] for r in env.cr.fetchall()] if not ids_to_archive: _logger.info("nexa_coa_setup: no unused accounts to archive") return accounts = env['account.account'].browse(ids_to_archive) accounts.write({'active': False}) _logger.info("nexa_coa_setup: archived %d unused accounts", len(accounts)) ``` - [ ] **Step 2: Deploy, update on staging** Run: ```bash rsync -avz /Users/gurpreet/Github/Odoo-Modules/nexa_coa_setup/ odoo-nexa:/opt/odoo/custom-addons/nexa_coa_setup/ ssh odoo-nexa "docker exec odoo-nexa-app odoo -c /etc/odoo/odoo.conf -d nexamain_staging -u nexa_coa_setup --no-http --stop-after-init 2>&1 | grep -E 'archived|ERROR' | tail -5" ``` Expected: log line "nexa_coa_setup: archived N unused accounts" where N is around 300-370. - [ ] **Step 3: Verify on staging** Run: ```bash ssh odoo-nexa "docker exec odoo-nexa-db psql -U odoo -d nexamain_staging -c \"SELECT (SELECT count(*) FROM account_account WHERE active=true) AS active_now, (SELECT count(*) FROM account_account WHERE active=false) AS archived_now;\"" ``` Expected: `active_now` around 130-150, `archived_now` around 280-370. - [ ] **Step 4: Spot-check that all OUR new accounts are still active** Run: ```bash ssh odoo-nexa "docker exec odoo-nexa-db psql -U odoo -d nexamain_staging -c \"SELECT count(*) FROM account_account a JOIN ir_model_data d ON d.model='account.account' AND d.res_id=a.id WHERE d.module='nexa_coa_setup' AND a.active=false;\"" ``` Expected: `0` (none of ours archived). - [ ] **Step 5: Commit** Run: ```bash cd /Users/gurpreet/Github/Odoo-Modules git add nexa_coa_setup/hooks.py git commit -m "feat(nexa_coa_setup): hook to archive unused l10n_ca accounts" ``` ### Task 4.2: Implement `_rename_legacy_accounts` **Files:** - Modify: `nexa_coa_setup/hooks.py` - [ ] **Step 1: Replace the stub** In `nexa_coa_setup/hooks.py`, replace `_rename_legacy_accounts` with: ```python def _rename_legacy_accounts(env): """Re-map legacy bookkeeping accounts to clean targets, then archive originals. We rename rather than delete to preserve audit trail. Each entry has: old_code: the code on the existing legacy account new_name: the rename target archive: if True, set active=False after rename """ legacy_map = [ # (old_code, new_name, archive_after) ("1400", "(LEGACY) Transferred to Gurpreet — re-class to 221100", True), ("1505", "(LEGACY) Sent to India — re-class to 612200", True), ("1580", "(LEGACY) Transferred to Westin — Westin is now a partner", True), ("1590", "(LEGACY) Transferred to Divine — Divine is now a partner", True), ("1600", "(LEGACY) Transferred to Manpreet — non-related; archive", True), ("1500", "(LEGACY) Food & Entertainment — re-class to 671200", True), ("1501", "(LEGACY) Office Expenses — re-class to 621500", True), ("411000", "(LEGACY) Inside Sales — re-class to 412xxx specific lines", True), ("412000", "(LEGACY) Harmonized Provinces Sales — handled by tax codes", True), ("413000", "(LEGACY) Non-Harmonized Provinces Sales — handled by tax", True), ("414000", "(LEGACY) International Sales — handled by Zero-rated Export", True), ("12000", "(LEGACY) Abdul & Future Mobility — use partner subledger", True), ("12001", "(LEGACY) MSI Account — use partner subledger", True), ("110010", "(LEGACY) Bank Fee — re-class to 691100", True), ("511100", "(LEGACY) Inside Purchases — re-class to specific 5xxxxx", True), ] renamed = 0 archived = 0 for old_code, new_name, archive in legacy_map: # In Odoo 19 the code lives in jsonb code_store; we search via the code property accounts = env['account.account'].search([('code', '=', old_code)]) if not accounts: continue # Skip if name already starts with "(LEGACY)" — idempotent for acc in accounts: if acc.name and acc.name.startswith("(LEGACY)"): continue acc.name = new_name renamed += 1 if archive: acc.active = False archived += 1 _logger.info("nexa_coa_setup: renamed %d legacy accounts, archived %d", renamed, archived) ``` **IMPORTANT NOTE FOR ENGINEER:** account 511100 in `legacy_map` collides with the new COGS account `511100 Cloud Infrastructure` we created in Task 2.5. Before deploying, manually verify: ```bash ssh odoo-nexa "docker exec odoo-nexa-db psql -U odoo -d nexamain_staging -c \"SELECT a.id, a.name->>'en_US' AS name, a.code_store->>'1' AS code, d.module FROM account_account a LEFT JOIN ir_model_data d ON d.model='account.account' AND d.res_id=a.id WHERE a.code_store->>'1' = '511100';\"" ``` If only ONE row exists and it's our new XML record (module=`nexa_coa_setup`), then the legacy `511100 Inside Purchases` was already absorbed/replaced — REMOVE that line from `legacy_map` before proceeding. If TWO rows exist (legacy + new), Odoo prevented loading the new one because of code uniqueness. In that case: - Manually re-code the legacy 511100 to `511100-LEGACY` via odoo-shell first, then re-run -u to create our new account at 511100, then re-add the rename step. - [ ] **Step 2: Resolve the 511100 collision (per the note above)** Run the verify query. Based on result: - **If only nexa_coa_setup's record exists**: edit `hooks.py` to delete the `("511100", ...)` line from `legacy_map`. Commit the edit. Proceed. - **If both exist**: open an odoo-shell session and re-code the legacy: ```bash ssh odoo-nexa "docker exec -i odoo-nexa-app odoo shell -c /etc/odoo/odoo.conf -d nexamain_staging --no-http --stop-after-init" <<'PYEOF' legacy = env['account.account'].search([('code','=','511100'),('name','ilike','inside purchases')]) if legacy: legacy.code = '511100-LEGACY' env.cr.commit() print(f"Renamed legacy account {legacy.id} to code 511100-LEGACY") PYEOF ``` Then leave `hooks.py` as-is. - [ ] **Step 3: Deploy, update on staging** Run: ```bash rsync -avz /Users/gurpreet/Github/Odoo-Modules/nexa_coa_setup/ odoo-nexa:/opt/odoo/custom-addons/nexa_coa_setup/ ssh odoo-nexa "docker exec odoo-nexa-app odoo -c /etc/odoo/odoo.conf -d nexamain_staging -u nexa_coa_setup --no-http --stop-after-init 2>&1 | grep -E 'renamed|ERROR' | tail -5" ``` Expected: log line "nexa_coa_setup: renamed N legacy accounts, archived N". - [ ] **Step 4: Verify legacy accounts are renamed and archived** Run: ```bash ssh odoo-nexa "docker exec odoo-nexa-db psql -U odoo -d nexamain_staging -c \"SELECT code_store->>'1' AS code, name->>'en_US' AS name, active FROM account_account WHERE name->>'en_US' LIKE '(LEGACY)%' ORDER BY code_store->>'1';\"" ``` Expected: 15 rows (or 14 if 511100 was removed) all with `active=false` and `(LEGACY)` prefix. - [ ] **Step 5: Commit** Run: ```bash cd /Users/gurpreet/Github/Odoo-Modules git add nexa_coa_setup/hooks.py git commit -m "feat(nexa_coa_setup): hook to rename and archive legacy accounts" ``` --- ## Phase 5 — Tax Cleanup ### Task 5.1: Archive duplicate / unused taxes **Files:** - Modify: `nexa_coa_setup/hooks.py` - [ ] **Step 1: Add a tax-archive helper to hooks.py** Append to `nexa_coa_setup/hooks.py`: ```python def _archive_unused_taxes(env): """Archive taxes with type_tax_use='none' or with zero usage on existing moves. Keep only the curated set listed in KEEP_TAX_NAMES. This is conservative — we only archive taxes that have NEVER been used on any move line. """ KEEP_TAX_NAMES = { # Sales taxes we keep active '5% GST', '13% HST', '14% HST', '15% HST', '11% GST+PST SK', '12% GST+PST BC', '12% GST+PST MB', '14.975% GST+QST', '9.975% QST', '0% GST', '0% Exempt', '0% Int', # Purchase taxes (same names, type_tax_use='purchase') } # Build the keep ID set: taxes whose name (en_US) is in KEEP_TAX_NAMES, active or not env.cr.execute(""" SELECT id FROM account_tax WHERE name->>'en_US' = ANY(%s) """, (list(KEEP_TAX_NAMES),)) keep_ids = {r[0] for r in env.cr.fetchall()} # Candidates to archive: active taxes with no usage and NOT in keep set env.cr.execute(""" SELECT t.id FROM account_tax t WHERE t.active = true AND NOT EXISTS (SELECT 1 FROM account_move_line_account_tax_rel r WHERE r.account_tax_id = t.id) AND t.id != ALL(%s) """, (list(keep_ids) or [0],)) ids = [r[0] for r in env.cr.fetchall()] if ids: env['account.tax'].browse(ids).write({'active': False}) _logger.info("nexa_coa_setup: archived %d unused taxes (kept %d)", len(ids), len(keep_ids)) ``` Then add to `post_init_hook` body, after `_archive_unused_l10n_ca_accounts(env)`: ```python _archive_unused_taxes(env) ``` - [ ] **Step 2: Deploy, update on staging** Run: ```bash rsync -avz /Users/gurpreet/Github/Odoo-Modules/nexa_coa_setup/ odoo-nexa:/opt/odoo/custom-addons/nexa_coa_setup/ ssh odoo-nexa "docker exec odoo-nexa-app odoo -c /etc/odoo/odoo.conf -d nexamain_staging -u nexa_coa_setup --no-http --stop-after-init 2>&1 | grep -E 'taxes|ERROR' | tail -5" ``` Expected: log line "nexa_coa_setup: archived N unused taxes (kept M)". - [ ] **Step 3: Verify active tax count** Run: ```bash ssh odoo-nexa "docker exec odoo-nexa-db psql -U odoo -d nexamain_staging -c \"SELECT type_tax_use, count(*) FROM account_tax WHERE active=true GROUP BY type_tax_use;\"" ``` Expected: a manageable count, ideally 14-25 active taxes across `sale` and `purchase`. - [ ] **Step 4: Commit** Run: ```bash cd /Users/gurpreet/Github/Odoo-Modules git add nexa_coa_setup/hooks.py git commit -m "feat(nexa_coa_setup): archive duplicate/unused taxes" ``` --- ## Phase 6 — Fiscal Positions ### Task 6.1: Define 8 fiscal positions with country/state auto-detection **Files:** - Modify: `nexa_coa_setup/data/04_account_fiscal_position.xml` - [ ] **Step 1: Discover the tax IDs we'll reference** Run on staging: ```bash ssh odoo-nexa "docker exec odoo-nexa-db psql -U odoo -d nexamain_staging -c \"SELECT id, name->>'en_US' AS name, type_tax_use, amount FROM account_tax WHERE active=true ORDER BY type_tax_use, amount, name;\"" ``` Note the IDs of the sales-side taxes we want to map TO from each fiscal position. Save them for the XML. - [ ] **Step 2: Write the fiscal position XML** Replace content of `nexa_coa_setup/data/04_account_fiscal_position.xml` with: ```xml CA — Ontario (Default) CA — Atlantic (HST 15%) CA — Quebec (GST + QST) CA — British Columbia (GST 5%, PST per-product) CA — Prairies / Territories (GST 5% only) Export — United States (Zero-rated) Export — International (Zero-rated) Manually applied for non-CA/non-US customers. Auto-apply by country group requires custom rule. Tax Exempt (cert-holder) Apply manually to customers with valid exemption certificate on file. Document the certificate in the partner's notes. ``` - [ ] **Step 3: Deploy, update on staging** Run: ```bash rsync -avz /Users/gurpreet/Github/Odoo-Modules/nexa_coa_setup/ odoo-nexa:/opt/odoo/custom-addons/nexa_coa_setup/ ssh odoo-nexa "docker exec odoo-nexa-app odoo -c /etc/odoo/odoo.conf -d nexamain_staging -u nexa_coa_setup --no-http --stop-after-init 2>&1 | tail -3" ssh odoo-nexa "docker exec odoo-nexa-db psql -U odoo -d nexamain_staging -c \"SELECT name, auto_apply FROM account_fiscal_position WHERE name LIKE 'CA %' OR name LIKE 'Export %' OR name LIKE 'Tax Exempt%' ORDER BY name;\"" ``` Expected: 8 fiscal positions, first 6 with auto_apply=true (CA + US), last 2 manual. - [ ] **Step 4: Add tax substitutions via odoo-shell** The XML approach for `account.fiscal.position.tax` lines is verbose and fragile (depends on exact tax IDs from l10n_ca). Use a one-time script: Create file `/Users/gurpreet/Github/Odoo-Modules/nexa_coa_setup/scripts/configure_fp_tax_maps.py`: ```python """Configure tax substitutions on fiscal positions. Default tax on a product is set to "5% GST" (federal-only base). Each fiscal position substitutes it for the appropriate provincial tax. """ env = self.env def tax(name, use='sale'): """Find a single tax by name and type.""" rec = env['account.tax'].search([ ('name', '=', name), ('type_tax_use', '=', use), ('active', '=', True), ], limit=1) if not rec: raise ValueError(f"Tax not found: {name!r} ({use})") return rec # Base "default" tax that products get GST_5 = tax('5% GST', 'sale') # Provincial replacements HST_13 = tax('13% HST', 'sale') HST_15 = tax('15% HST', 'sale') QST_GROUP = tax('14.975% GST+QST', 'sale') ZERO_EXPORT = tax('0% GST', 'sale') # Zero-rated for export FP_MAP = [ # (fp_xmlid, [(from_tax, to_tax)]) ('nexa_coa_setup.fp_ca_ontario', [(GST_5, HST_13)]), ('nexa_coa_setup.fp_ca_atlantic', [(GST_5, HST_15)]), ('nexa_coa_setup.fp_ca_quebec', [(GST_5, QST_GROUP)]), ('nexa_coa_setup.fp_ca_bc', [(GST_5, GST_5)]), # GST only; PST handled per-product ('nexa_coa_setup.fp_ca_prairies_territories', [(GST_5, GST_5)]), # GST only ('nexa_coa_setup.fp_export_us', [(GST_5, ZERO_EXPORT)]), ('nexa_coa_setup.fp_export_intl', [(GST_5, ZERO_EXPORT)]), ('nexa_coa_setup.fp_tax_exempt', [(GST_5, ZERO_EXPORT)]), ] for fp_xmlid, pairs in FP_MAP: fp = env.ref(fp_xmlid, raise_if_not_found=False) if not fp: print(f"SKIP missing fiscal position: {fp_xmlid}") continue # Clear existing tax maps for clean idempotent re-run fp.tax_ids.unlink() for from_tax, to_tax in pairs: env['account.fiscal.position.tax'].create({ 'position_id': fp.id, 'tax_src_id': from_tax.id, 'tax_dest_id': to_tax.id, }) print(f"OK {fp_xmlid}: {len(pairs)} mapping(s)") env.cr.commit() print("Fiscal position tax maps configured.") ``` Then run it: ```bash mkdir -p /Users/gurpreet/Github/Odoo-Modules/nexa_coa_setup/scripts # (create the file via Write tool as shown above) rsync -avz /Users/gurpreet/Github/Odoo-Modules/nexa_coa_setup/scripts/ odoo-nexa:/opt/odoo/custom-addons/nexa_coa_setup/scripts/ ssh odoo-nexa "docker exec -i odoo-nexa-app odoo shell -c /etc/odoo/odoo.conf -d nexamain_staging --no-http --stop-after-init < /opt/odoo/custom-addons/nexa_coa_setup/scripts/configure_fp_tax_maps.py 2>&1 | tail -15" ``` Expected: "OK fp_ca_ontario: 1 mapping(s)" through all 8. - [ ] **Step 5: Verify mappings** Run: ```bash ssh odoo-nexa "docker exec odoo-nexa-db psql -U odoo -d nexamain_staging -c \"SELECT fp.name AS fiscal_pos, st.name->>'en_US' AS from_tax, dt.name->>'en_US' AS to_tax FROM account_fiscal_position_tax m JOIN account_fiscal_position fp ON fp.id=m.position_id JOIN account_tax st ON st.id=m.tax_src_id JOIN account_tax dt ON dt.id=m.tax_dest_id WHERE fp.name LIKE 'CA %' OR fp.name LIKE 'Export %' OR fp.name LIKE 'Tax Exempt%' ORDER BY fp.name;\"" ``` Expected: 8 rows showing each FP's substitution. - [ ] **Step 6: Commit** Run: ```bash cd /Users/gurpreet/Github/Odoo-Modules git add nexa_coa_setup/data/04_account_fiscal_position.xml nexa_coa_setup/scripts/configure_fp_tax_maps.py git commit -m "feat(nexa_coa_setup): 8 fiscal positions with auto-detect and tax substitutions" ``` --- ## Phase 7 — Product Categories ### Task 7.1: Define service product categories with default accounts **Files:** - Modify: `nexa_coa_setup/data/07_product_category.xml` - [ ] **Step 1: Write the product category XML** Replace content of `nexa_coa_setup/data/07_product_category.xml` with: ```xml Services SaaS Subscription Hosting Support Contract Setup Fee Custom Software Development Custom Web App Development Custom Website Development ERP Implementation Consulting & Advisory Training Resale Software Resale Hardware Resale ``` - [ ] **Step 2: Deploy, update, verify** Run: ```bash rsync -avz /Users/gurpreet/Github/Odoo-Modules/nexa_coa_setup/ odoo-nexa:/opt/odoo/custom-addons/nexa_coa_setup/ ssh odoo-nexa "docker exec odoo-nexa-app odoo -c /etc/odoo/odoo.conf -d nexamain_staging -u nexa_coa_setup --no-http --stop-after-init 2>&1 | tail -3" ssh odoo-nexa "docker exec odoo-nexa-db psql -U odoo -d nexamain_staging -c \"SELECT pc.complete_name FROM product_category pc JOIN ir_model_data d ON d.model='product.category' AND d.res_id=pc.id WHERE d.module='nexa_coa_setup' ORDER BY pc.complete_name;\"" ``` Expected: ~12 categories under Services/Resale. - [ ] **Step 3: Commit** Run: ```bash cd /Users/gurpreet/Github/Odoo-Modules git add nexa_coa_setup/data/07_product_category.xml git commit -m "feat(nexa_coa_setup): service product categories with default income accounts" ``` --- ## Phase 8 — Partner Setup (Westin, Divine, RP-Associated tag) ### Task 8.1: Create RP-Associated partner tag **Files:** - Modify: `nexa_coa_setup/data/08_res_partner_category.xml` - [ ] **Step 1: Write the tag XML** Replace content with: ```xml RP-Associated 3 ``` - [ ] **Step 2: Deploy, update, verify** Run: ```bash rsync -avz /Users/gurpreet/Github/Odoo-Modules/nexa_coa_setup/ odoo-nexa:/opt/odoo/custom-addons/nexa_coa_setup/ ssh odoo-nexa "docker exec odoo-nexa-app odoo -c /etc/odoo/odoo.conf -d nexamain_staging -u nexa_coa_setup --no-http --stop-after-init 2>&1 | tail -3" ssh odoo-nexa "docker exec odoo-nexa-db psql -U odoo -d nexamain_staging -c \"SELECT id, name->>'en_US' AS name, color FROM res_partner_category WHERE name->>'en_US' = 'RP-Associated';\"" ``` Expected: 1 row. ### Task 8.2: Create Westin Healthcare Inc and Divine Mobility Inc partner records **Files:** - Modify: `nexa_coa_setup/data/09_res_partner.xml` - [ ] **Step 1: Write the partner XML** Replace content with: ```xml Westin Healthcare Inc company 1 1 Associated corporation under common control with Nexa Systems Inc (Gurpreet, owner). Intercompany transactions must be priced at fair market value (ITA s.247). Shared SBD limit per ITA s.125(5.1). Divine Mobility Inc company 1 1 Associated corporation under common control with Nexa Systems Inc (Gurpreet, owner). See Westin Healthcare Inc for compliance notes. ``` - [ ] **Step 2: Deploy, update, verify** Run: ```bash rsync -avz /Users/gurpreet/Github/Odoo-Modules/nexa_coa_setup/ odoo-nexa:/opt/odoo/custom-addons/nexa_coa_setup/ ssh odoo-nexa "docker exec odoo-nexa-app odoo -c /etc/odoo/odoo.conf -d nexamain_staging -u nexa_coa_setup --no-http --stop-after-init 2>&1 | tail -3" ssh odoo-nexa "docker exec odoo-nexa-db psql -U odoo -d nexamain_staging -c \"SELECT p.name, p.customer_rank, p.supplier_rank, p.is_company, fp.name AS fiscal_pos, array_agg(c.name->>'en_US') AS tags FROM res_partner p LEFT JOIN account_fiscal_position fp ON fp.id=p.property_account_position_id LEFT JOIN res_partner_res_partner_category_rel r ON r.partner_id=p.id LEFT JOIN res_partner_category c ON c.id=r.category_id WHERE p.name IN ('Westin Healthcare Inc', 'Divine Mobility Inc') GROUP BY p.id, p.name, p.customer_rank, p.supplier_rank, p.is_company, fp.name ORDER BY p.name;\"" ``` Expected: 2 rows, both with customer_rank=1, supplier_rank=1, is_company=t, fiscal_pos="CA — Ontario (Default)", tags including 'RP-Associated'. - [ ] **Step 3: Commit** Run: ```bash cd /Users/gurpreet/Github/Odoo-Modules git add nexa_coa_setup/data/08_res_partner_category.xml nexa_coa_setup/data/09_res_partner.xml git commit -m "feat(nexa_coa_setup): Westin and Divine as associated-corp partners" ``` --- ## Phase 9 — Bank Reconciliation Rules ### Task 9.1: Define auto-categorization rules for common vendors **Files:** - Modify: `nexa_coa_setup/data/10_account_reconcile_model.xml` - [ ] **Step 1: Look up account IDs we'll reference** Run: ```bash ssh odoo-nexa "docker exec odoo-nexa-db psql -U odoo -d nexamain_staging -c \"SELECT id, code_store->>'1' AS code, name->>'en_US' AS name FROM account_account WHERE code_store->>'1' IN ('511100','511110','631100','631200','641100','691100','691200') ORDER BY code_store->>'1';\"" ``` Note the IDs. - [ ] **Step 2: Write the reconciliation rules XML** Replace content of `nexa_coa_setup/data/10_account_reconcile_model.xml` with: ```xml AWS / Amazon Web Services → Cloud Infrastructure writeoff_suggestion contains AMAZON WEB SERVICES Hetzner → Cloud Infrastructure writeoff_suggestion contains HETZNER DigitalOcean → Cloud Infrastructure writeoff_suggestion contains DIGITALOCEAN Cloudflare → CDN & Edge writeoff_suggestion contains CLOUDFLARE GitHub → Software (Dev Tools) writeoff_suggestion contains GITHUB Microsoft / M365 → Software (Productivity) writeoff_suggestion contains MICROSOFT Stripe fee → Merchant Processing writeoff_suggestion contains STRIPE FEE Google Ads → Advertising (Digital) writeoff_suggestion contains GOOGLE ADS ``` - [ ] **Step 3: Deploy, update, verify** Run: ```bash rsync -avz /Users/gurpreet/Github/Odoo-Modules/nexa_coa_setup/ odoo-nexa:/opt/odoo/custom-addons/nexa_coa_setup/ ssh odoo-nexa "docker exec odoo-nexa-app odoo -c /etc/odoo/odoo.conf -d nexamain_staging -u nexa_coa_setup --no-http --stop-after-init 2>&1 | tail -3" ssh odoo-nexa "docker exec odoo-nexa-db psql -U odoo -d nexamain_staging -c \"SELECT name FROM account_reconcile_model WHERE name LIKE '%→%' ORDER BY name;\"" ``` Expected: 8 reconciliation rules visible. - [ ] **Step 4: Commit** Run: ```bash cd /Users/gurpreet/Github/Odoo-Modules git add nexa_coa_setup/data/10_account_reconcile_model.xml git commit -m "feat(nexa_coa_setup): 8 bank reconciliation rules for common vendors" ``` --- ## Phase 10 — End-to-End Verification on Staging ### Task 10.1: Create test invoice for Ontario customer (HST 13%) **Files:** none — test data only - [ ] **Step 1: Create a test ON customer and invoice via odoo-shell** Create `/Users/gurpreet/Github/Odoo-Modules/nexa_coa_setup/scripts/test_invoice_on.py`: ```python env = self.env # 1. Create or fetch test customer partner = env['res.partner'].search([('name', '=', 'TEST CUSTOMER ON')], limit=1) if not partner: partner = env['res.partner'].create({ 'name': 'TEST CUSTOMER ON', 'country_id': env.ref('base.ca').id, 'state_id': env.ref('base.state_ca_on').id, 'customer_rank': 1, }) # 2. Find or create a SaaS product in SaaS Subscription category saas_cat = env.ref('nexa_coa_setup.pc_saas') product = env['product.product'].search([('name', '=', 'TEST SaaS Subscription')], limit=1) if not product: product = env['product.product'].create({ 'name': 'TEST SaaS Subscription', 'type': 'service', 'list_price': 100.00, 'categ_id': saas_cat.id, }) # 3. Create invoice inv = env['account.move'].create({ 'move_type': 'out_invoice', 'partner_id': partner.id, 'invoice_line_ids': [(0, 0, { 'product_id': product.id, 'quantity': 1, 'price_unit': 100.00, })], }) print(f"Invoice {inv.name} created") print(f" Fiscal position: {inv.fiscal_position_id.name}") print(f" Tax on line: {inv.invoice_line_ids[0].tax_ids.mapped('name')}") print(f" Subtotal: {inv.amount_untaxed}") print(f" Tax: {inv.amount_tax}") print(f" Total: {inv.amount_total}") print(f" Income account: {inv.invoice_line_ids[0].account_id.code} {inv.invoice_line_ids[0].account_id.name}") # Don't commit — this is just a test env.cr.rollback() print("Test rolled back.") ``` - [ ] **Step 2: Run the test on staging** Run: ```bash rsync -avz /Users/gurpreet/Github/Odoo-Modules/nexa_coa_setup/ odoo-nexa:/opt/odoo/custom-addons/nexa_coa_setup/ ssh odoo-nexa "docker exec -i odoo-nexa-app odoo shell -c /etc/odoo/odoo.conf -d nexamain_staging --no-http --stop-after-init < /opt/odoo/custom-addons/nexa_coa_setup/scripts/test_invoice_on.py 2>&1 | tail -15" ``` Expected output: ``` Invoice INV/2026/... created Fiscal position: CA — Ontario (Default) Tax on line: ['13% HST'] Subtotal: 100.0 Tax: 13.0 Total: 113.0 Income account: 411100 SaaS Subscription Revenue Test rolled back. ``` If tax shows "5% GST" instead of "13% HST" → fiscal position substitution is broken. Stop and debug. - [ ] **Step 3: Commit the test script** Run: ```bash cd /Users/gurpreet/Github/Odoo-Modules git add nexa_coa_setup/scripts/test_invoice_on.py git commit -m "test(nexa_coa_setup): ON customer SaaS invoice — HST 13% mapping" ``` ### Task 10.2: Create test invoice for US customer (Zero-rated) **Files:** create `nexa_coa_setup/scripts/test_invoice_us.py` - [ ] **Step 1: Write the script (similar to test_invoice_on.py but with US partner)** Create `/Users/gurpreet/Github/Odoo-Modules/nexa_coa_setup/scripts/test_invoice_us.py`: ```python env = self.env partner = env['res.partner'].search([('name', '=', 'TEST CUSTOMER US')], limit=1) if not partner: partner = env['res.partner'].create({ 'name': 'TEST CUSTOMER US', 'country_id': env.ref('base.us').id, 'customer_rank': 1, }) saas_cat = env.ref('nexa_coa_setup.pc_saas') product = env['product.product'].search([('name', '=', 'TEST SaaS Subscription')], limit=1) if not product: product = env['product.product'].create({ 'name': 'TEST SaaS Subscription', 'type': 'service', 'list_price': 100.00, 'categ_id': saas_cat.id, }) inv = env['account.move'].create({ 'move_type': 'out_invoice', 'partner_id': partner.id, 'invoice_line_ids': [(0, 0, { 'product_id': product.id, 'quantity': 1, 'price_unit': 100.00, })], }) print(f"Invoice {inv.name} created") print(f" Fiscal position: {inv.fiscal_position_id.name}") print(f" Tax on line: {inv.invoice_line_ids[0].tax_ids.mapped('name')}") print(f" Total: {inv.amount_total}") env.cr.rollback() print("Test rolled back.") ``` - [ ] **Step 2: Run and verify** Run: ```bash rsync -avz /Users/gurpreet/Github/Odoo-Modules/nexa_coa_setup/ odoo-nexa:/opt/odoo/custom-addons/nexa_coa_setup/ ssh odoo-nexa "docker exec -i odoo-nexa-app odoo shell -c /etc/odoo/odoo.conf -d nexamain_staging --no-http --stop-after-init < /opt/odoo/custom-addons/nexa_coa_setup/scripts/test_invoice_us.py 2>&1 | tail -10" ``` Expected: ``` Invoice INV/2026/... created Fiscal position: Export — United States (Zero-rated) Tax on line: ['0% GST'] Total: 100.0 Test rolled back. ``` - [ ] **Step 3: Commit** Run: ```bash cd /Users/gurpreet/Github/Odoo-Modules git add nexa_coa_setup/scripts/test_invoice_us.py git commit -m "test(nexa_coa_setup): US customer SaaS invoice — Zero-rated export" ``` ### Task 10.3: Create test invoice for intercompany (Nexa → Westin) **Files:** create `nexa_coa_setup/scripts/test_invoice_intercompany.py` - [ ] **Step 1: Write the script** Create `/Users/gurpreet/Github/Odoo-Modules/nexa_coa_setup/scripts/test_invoice_intercompany.py`: ```python env = self.env westin = env.ref('nexa_coa_setup.partner_westin_healthcare') consulting_cat = env.ref('nexa_coa_setup.pc_consulting') product = env['product.product'].search([('name', '=', 'TEST Consulting Hour')], limit=1) if not product: product = env['product.product'].create({ 'name': 'TEST Consulting Hour', 'type': 'service', 'list_price': 150.00, 'categ_id': consulting_cat.id, }) inv = env['account.move'].create({ 'move_type': 'out_invoice', 'partner_id': westin.id, 'invoice_line_ids': [(0, 0, { 'product_id': product.id, 'quantity': 10, 'price_unit': 150.00, })], }) print(f"Invoice {inv.name} to {westin.name}") print(f" Fiscal position: {inv.fiscal_position_id.name}") print(f" Tax on line: {inv.invoice_line_ids[0].tax_ids.mapped('name')}") print(f" Subtotal: {inv.amount_untaxed}") print(f" Tax: {inv.amount_tax}") print(f" Total: {inv.amount_total}") print(f" Income account: {inv.invoice_line_ids[0].account_id.code} {inv.invoice_line_ids[0].account_id.name}") print(f" Customer tags: {[c.name for c in westin.category_id]}") env.cr.rollback() print("Test rolled back.") ``` - [ ] **Step 2: Run and verify** Run: ```bash rsync -avz /Users/gurpreet/Github/Odoo-Modules/nexa_coa_setup/ odoo-nexa:/opt/odoo/custom-addons/nexa_coa_setup/ ssh odoo-nexa "docker exec -i odoo-nexa-app odoo shell -c /etc/odoo/odoo.conf -d nexamain_staging --no-http --stop-after-init < /opt/odoo/custom-addons/nexa_coa_setup/scripts/test_invoice_intercompany.py 2>&1 | tail -15" ``` Expected: ``` Invoice INV/2026/... to Westin Healthcare Inc Fiscal position: CA — Ontario (Default) Tax on line: ['13% HST'] Subtotal: 1500.0 Tax: 195.0 Total: 1695.0 Income account: 413100 Consulting & Advisory Revenue Customer tags: ['RP-Associated'] Test rolled back. ``` - [ ] **Step 3: Commit** Run: ```bash cd /Users/gurpreet/Github/Odoo-Modules git add nexa_coa_setup/scripts/test_invoice_intercompany.py git commit -m "test(nexa_coa_setup): intercompany Nexa→Westin invoice" ``` --- ## Phase 11 — Apply to Production ### Task 11.1: Final pg_dump of prod BEFORE production install **Files:** none - [ ] **Step 1: Take a fresh dump** Run: ```bash ssh odoo-nexa "docker exec odoo-nexa-db pg_dump -U odoo -d nexamain -F c -Z 9 -f /tmp/nexamain_prefinal_$(date +%Y%m%d_%H%M%S).dump && ls -lh /tmp/nexamain_prefinal_*.dump | tail -1" scp odoo-nexa:/tmp/nexamain_prefinal_*.dump ~/Backups/odoo-nexa/ echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) — pg_dump pre-final nexa_coa_setup install on prod — $(ls -1 ~/Backups/odoo-nexa/nexamain_prefinal_*.dump | tail -1)" >> ~/Backups/odoo-nexa/RESTORE_LOG.md ``` ### Task 11.2: Install nexa_coa_setup on production **Files:** none > **🚦 GATE — User confirmation required before this task.** This is the destructive step that touches the live production database. Confirm: > - [ ] pg_dump from Task 11.1 succeeded and is on local Mac (`~/Backups/odoo-nexa/`) > - [ ] All staging tests passed (Tasks 10.1, 10.2, 10.3) > - [ ] No outstanding transactions or active users on prod right now (or business is comfortable with brief disruption) > > User must explicitly say "proceed with prod install" before continuing. - [ ] **Step 1: Install on nexamain (production)** Run: ```bash ssh odoo-nexa "docker exec odoo-nexa-app odoo -c /etc/odoo/odoo.conf -d nexamain -i nexa_coa_setup --no-http --stop-after-init 2>&1 | tail -30" ``` Expected: `Modules loaded.` no traceback. Watch for `archived N unused accounts` and `renamed N legacy accounts` log lines. - [ ] **Step 2: Verify all checkpoints on production** Run: ```bash ssh odoo-nexa "docker exec odoo-nexa-db psql -U odoo -d nexamain -c \" SELECT 'HST# format' AS check_name, (SELECT vat FROM res_partner WHERE id = (SELECT partner_id FROM res_company WHERE id = 1)) AS value UNION ALL SELECT 'Fiscal year lock', (SELECT fiscalyear_lock_date::text FROM res_company WHERE id = 1) UNION ALL SELECT 'Active accounts', (SELECT count(*)::text FROM account_account WHERE active = true) UNION ALL SELECT 'Archived accounts', (SELECT count(*)::text FROM account_account WHERE active = false) UNION ALL SELECT 'Active taxes', (SELECT count(*)::text FROM account_tax WHERE active = true) UNION ALL SELECT 'Fiscal positions', (SELECT count(*)::text FROM account_fiscal_position WHERE active = true) UNION ALL SELECT 'Analytic plans', (SELECT count(*)::text FROM account_analytic_plan) UNION ALL SELECT 'Partner: Westin', (SELECT count(*)::text FROM res_partner WHERE name = 'Westin Healthcare Inc') UNION ALL SELECT 'Partner: Divine', (SELECT count(*)::text FROM res_partner WHERE name = 'Divine Mobility Inc'); \"" ``` Acceptance check: - HST# format: `741224877 RT0001` - Fiscal year lock: `2025-12-31` - Active accounts: 130-200 (down from 426) - Archived accounts: 280-370 - Active taxes: ≤ 25 - Fiscal positions: ≥ 8 - Analytic plans: ≥ 3 - Partner: Westin: 1 - Partner: Divine: 1 - [ ] **Step 3: Run all three test invoices on production** ```bash ssh odoo-nexa "docker exec -i odoo-nexa-app odoo shell -c /etc/odoo/odoo.conf -d nexamain --no-http --stop-after-init < /opt/odoo/custom-addons/nexa_coa_setup/scripts/test_invoice_on.py 2>&1 | tail -10" ssh odoo-nexa "docker exec -i odoo-nexa-app odoo shell -c /etc/odoo/odoo.conf -d nexamain --no-http --stop-after-init < /opt/odoo/custom-addons/nexa_coa_setup/scripts/test_invoice_us.py 2>&1 | tail -10" ssh odoo-nexa "docker exec -i odoo-nexa-app odoo shell -c /etc/odoo/odoo.conf -d nexamain --no-http --stop-after-init < /opt/odoo/custom-addons/nexa_coa_setup/scripts/test_invoice_intercompany.py 2>&1 | tail -10" ``` Expected: all three produce the same output they did on staging. All are rolled back — no actual test data persists. ### Task 11.3: Drop the staging DB **Files:** none - [ ] **Step 1: Drop nexamain_staging** Run: ```bash ssh odoo-nexa "docker exec odoo-nexa-db psql -U odoo -d postgres -c 'DROP DATABASE IF EXISTS nexamain_staging;'" ``` Expected: `DROP DATABASE`. ### Task 11.4: Final commit and tag **Files:** none - [ ] **Step 1: Tag the deployment** > **Note**: `git push --tags` writes to the remote — ask user before running. Run: ```bash cd /Users/gurpreet/Github/Odoo-Modules git tag -a nexa_coa_setup-v1.0.0 -m "Initial install of nexa_coa_setup on prod nexamain" # After user confirms remote push is OK: git push --tags ``` --- ## Phase 12 — Documentation & Runbook ### Task 12.1: Write operating runbook in module README **Files:** - Modify: `nexa_coa_setup/README.md` - [ ] **Step 1: Expand README with operating procedures** Replace `nexa_coa_setup/README.md` content with: ```markdown # Nexa Systems — Chart of Accounts Setup Custom Odoo 19 module that configures the chart of accounts, taxes, fiscal positions, analytic plans, and partner records for Nexa Systems Inc. ## Design reference See `docs/superpowers/specs/2026-05-12-nexa-coa-design.md`. ## Initial install ALWAYS take a pg_dump first: ``` ssh odoo-nexa "docker exec odoo-nexa-db pg_dump -U odoo -d nexamain -F c -Z 9 -f /tmp/nexamain_$(date +%Y%m%d).dump" scp odoo-nexa:/tmp/nexamain_*.dump ~/Backups/odoo-nexa/ ``` Then install: ``` ssh odoo-nexa "docker exec odoo-nexa-app odoo -c /etc/odoo/odoo.conf -d nexamain -i nexa_coa_setup --no-http --stop-after-init" ``` ## Update (when adding new accounts / taxes / fiscal positions) 1. Edit XML in `data/` or hooks in `hooks.py` 2. Sync to server: ``` rsync -avz /Users/gurpreet/Github/Odoo-Modules/nexa_coa_setup/ odoo-nexa:/opt/odoo/custom-addons/nexa_coa_setup/ ``` 3. Run -u: ``` ssh odoo-nexa "docker exec odoo-nexa-app odoo -c /etc/odoo/odoo.conf -d nexamain -u nexa_coa_setup --no-http --stop-after-init" ``` ## Adding a new account 1. Append a `` to `data/01_account_account.xml` (use the next free code in the appropriate range) 2. If it's a service/COGS account, also add a matching `product.category` in `data/07_product_category.xml` 3. Sync and update (see above) ## Adding a new project (analytic account) Don't put projects in XML — create via UI as projects are dynamic. In Odoo: Accounting → Configuration → Analytic Accounting → Analytic Accounts → New - Name: descriptive (e.g., "Westin ERP Phase 2") - Code: `PRJ-YYYY-{CUST}-{SHORTNAME}` - Plan: Project ## Yearly tasks - **Jan**: review CCA classes for new asset purchases; ensure assets created against correct 151xxx account - **Feb**: prepare T2 with accountant; allocate associated-group SBD via Schedule 23 - **Mar**: HST annual return due (March 31 for Dec 31 year-end) - **Apr**: review fiscal year lock — set to previous Dec 31 once T2 is filed - **Sep–Dec**: SR&ED analytic report pull; provide to accountant for T661 prep - **Dec**: year-end review of intercompany pricing for transfer-pricing compliance ## Restore (if catastrophic) ``` ssh odoo-nexa "docker exec odoo-nexa-db psql -U odoo -d postgres -c 'DROP DATABASE nexamain;'" ssh odoo-nexa "docker exec odoo-nexa-db psql -U odoo -d postgres -c 'CREATE DATABASE nexamain OWNER odoo;'" ssh odoo-nexa "docker exec odoo-nexa-db pg_restore -U odoo -d nexamain -j 4 /tmp/nexamain_.dump" ssh odoo-nexa "docker restart odoo-nexa-app" ``` ``` - [ ] **Step 2: Commit** Run: ```bash cd /Users/gurpreet/Github/Odoo-Modules git add nexa_coa_setup/README.md git commit -m "docs(nexa_coa_setup): operating runbook" ``` --- ## Out of Scope (Future Sub-Projects) These are intentionally NOT in this plan. Each warrants its own spec + plan cycle: 1. **Bank feeds via Plaid** — Odoo Enterprise has Plaid integration; needs credentials, banking partner OAuth, ongoing rule-tuning. Independent from CoA setup. 2. **Historical data reconciliation** — once accountant's Excel records arrive, mapping old transactions into the new account structure. Requires careful approach to respect the 2025-12-31 lock; possibly involves un-locking, posting, re-locking. 3. **Custom Canadian CCA module** — declining-balance + half-year + AccII automation. Only worth it once asset count >50. 4. **Payroll integration** — when first T4 employee is hired; integrate Wagepoint/ADP or Odoo Payroll, drive 215xxx source deductions automatically. 5. **Multi-company Odoo migration** — Westin and Divine currently on separate Odoo instances; future consolidation enables auto-mirrored intercompany invoices. 6. **Approval workflows** — purchase approval thresholds, expense approval per-department limits. 7. **Multi-currency** — USD bank account and currency-rate-live when first US client onboards. 8. **Subscription module configuration** — `sale_subscription` is installed; configuring recurring-billing templates per SaaS product, Stripe integration, auto-renewal email cadence is a per-customer flow handled when first SaaS contract is sold. 9. **Bank journal consolidation** — Section 10 Phase 4 of the spec calls for auditing the 7 bank journals; this is a manual decision per-account (which to keep, which to archive). Best done with the accountant during the historical reconciliation sub-project. --- ## Self-Review Note This plan was self-reviewed for: - **Placeholder scan**: no "TBD", "TODO later", or "similar to Task N" — every step contains the actual content needed. - **Spec coverage**: every section of `docs/superpowers/specs/2026-05-12-nexa-coa-design.md` maps to a phase/task: - Section 3 Skeleton → Phase 2 Tasks 2.1-2.6 - Section 4 Revenue → Phase 2 Task 2.4 - Section 5 COGS → Phase 2 Task 2.5 - Section 6 OpEx → Phase 2 Task 2.6 - Section 7 Capital Assets → Phase 2 Task 2.1 (cost accounts; asset models created on first asset purchase) - Section 8 Tax Accounts → Phase 2 Task 2.2 (213xxx) + Phase 5 (cleanup) - Section 9 Shareholder/Associated → Phase 2 Task 2.2 (221xxx, 222xxx) + Phase 8 (Westin/Divine partners) - Section 10 Analytic Plans → Phase 3 - Section 11 Tax Setup/Fiscal Positions → Phase 5 + Phase 6 - Section 12 Cleanup Plan → Phase 4 (archive + rename hooks) + Phase 5 (taxes) + Phase 11 (lock fiscal year via hook) - Section 13 Automation Hooks → Phase 7 (product categories) + Phase 9 (bank rec rules) - Section 17 Acceptance Criteria → Phase 10 (verification tests) + Phase 11 Task 11.2 (production validation query) - **Type consistency**: all XMLIDs use the `acct_`/`fp_`/`pc_`/`aa_`/`partner_`/`rule_` prefixes consistently; all account references in Phase 9 (reconcile rules) and Phase 7 (product cats) use account XMLIDs defined in Phase 2. - **Known potential issues flagged inline**: account 511100 collision (Task 4.2 Step 2 has resolution procedure).