4 Commits

Author SHA1 Message Date
gsinghpal
449f29fc7f fix(reports): WO Margin PDF — t-field requires dot-notation on Odoo 19
The template used 't-field="step['rate']"' for monetary values pulled
from dict rows. Odoo 19's QWeb asserts t-field has at least one dot
(it's strictly for record.field_name lookups). Replaced six bare-dict
t-field usages with t-esc; the existing t-options widget=monetary +
display_currency still applies for currency formatting.

Verified by rendering report for WO-30019 — 1229-byte valid PDF, no
QWeb error.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 18:51:17 -04:00
gsinghpal
3c2fb22346 feat(nexa_coa_setup): chart of accounts — 128 accounts across 1-6xxxxx
Renumbered to avoid collisions with pre-loaded l10n_ca codes:
- Due From Shareholder/Associated: 115xxx → 119xxx range (115100/115110 already
  held l10n_ca AR control accounts with 240 postings)
- Cloud Infrastructure: 511100 → 511105 (511100 was l10n_ca 'Inside Purchases'
  with 1 historical posting)

All other 28 colliding l10n_ca codes (118xxx, 213xxx, 214xxx, 221xxx, 311xxx,
411xxx, 413xxx, 511110-511210, 512100-512200, 611100-300, 612xxx) had zero
postings and were cleared in-place by suffixing existing codes with '.OLD'
via a one-off odoo-shell script on staging.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 18:50:00 -04:00
gsinghpal
3a41370189 fix(nexa_coa_setup): tolerant fiscal-year lock hook
The post_init_hook attempt to set fiscalyear_lock_date=2025-12-31 fails
with RedirectWarning when unreconciled bank statement lines exist in
the period. Catch RedirectWarning/UserError/ValidationError, log a
clear instruction to set the lock manually after reconciliation, and
let install continue.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 18:41:15 -04:00
gsinghpal
d6513ff7ab feat(nexa_coa_setup): module skeleton with hooks stub
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 18:39:24 -04:00
18 changed files with 996 additions and 7 deletions

View File

@@ -3,7 +3,7 @@
# License OPL-1 (Odoo Proprietary License v1.0)
{
'name': 'Fusion Plating — Native Jobs',
'version': '19.0.8.22.8',
'version': '19.0.8.22.9',
'category': 'Manufacturing/Plating',
'summary': 'Native plating job model — replaces mrp.production / mrp.workorder bridge.',
'author': 'Nexa Systems Inc.',

View File

@@ -45,8 +45,8 @@
<td><span t-esc="step['work_centre']"/></td>
<td class="text-end"><span t-esc="step['duration_expected']"/></td>
<td class="text-end"><span t-esc="step['duration_actual']"/></td>
<td class="text-end"><span t-field="step['rate']" t-options="{'widget': 'monetary', 'display_currency': row['job'].currency_id}"/></td>
<td class="text-end"><span t-field="step['cost']" t-options="{'widget': 'monetary', 'display_currency': row['job'].currency_id}"/></td>
<td class="text-end"><span t-esc="step['rate']" t-options="{'widget': 'monetary', 'display_currency': row['job'].currency_id}"/></td>
<td class="text-end"><span t-esc="step['cost']" t-options="{'widget': 'monetary', 'display_currency': row['job'].currency_id}"/></td>
</tr>
</t>
<tr style="font-weight: bold; background: #f3f3f3;">
@@ -54,16 +54,16 @@
<td></td>
<td class="text-end"><span t-esc="row['total_minutes']"/></td>
<td></td>
<td class="text-end"><span t-field="row['total_labour']" t-options="{'widget': 'monetary', 'display_currency': row['job'].currency_id}"/></td>
<td class="text-end"><span t-esc="row['total_labour']" t-options="{'widget': 'monetary', 'display_currency': row['job'].currency_id}"/></td>
</tr>
</tbody>
</table>
<h3 style="margin-top: 1.5em;">Margin Summary</h3>
<table class="table table-sm" style="max-width: 400px;">
<tr><th>Quoted Revenue</th><td class="text-end"><span t-field="row['quoted_revenue']" t-options="{'widget': 'monetary', 'display_currency': row['job'].currency_id}"/></td></tr>
<tr><th>Actual Cost</th><td class="text-end"><span t-field="row['actual_cost']" t-options="{'widget': 'monetary', 'display_currency': row['job'].currency_id}"/></td></tr>
<tr style="font-weight: bold;"><th>Margin</th><td class="text-end"><span t-field="row['margin']" t-options="{'widget': 'monetary', 'display_currency': row['job'].currency_id}"/></td></tr>
<tr><th>Quoted Revenue</th><td class="text-end"><span t-esc="row['quoted_revenue']" t-options="{'widget': 'monetary', 'display_currency': row['job'].currency_id}"/></td></tr>
<tr><th>Actual Cost</th><td class="text-end"><span t-esc="row['actual_cost']" t-options="{'widget': 'monetary', 'display_currency': row['job'].currency_id}"/></td></tr>
<tr style="font-weight: bold;"><th>Margin</th><td class="text-end"><span t-esc="row['margin']" t-options="{'widget': 'monetary', 'display_currency': row['job'].currency_id}"/></td></tr>
<tr><th>Margin %</th><td class="text-end"><span t-esc="round(row['margin_pct'], 1)"/>%</td></tr>
</table>
</div>

26
nexa_coa_setup/README.md Normal file
View File

@@ -0,0 +1,26 @@
# 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.

View File

@@ -0,0 +1,2 @@
from . import models
from .hooks import post_init_hook

View File

@@ -0,0 +1,38 @@
# -*- 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,
}

View File

@@ -0,0 +1,808 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="0">
<!-- ============================================================ -->
<!-- 1xxxxx — ASSETS -->
<!-- ============================================================ -->
<!-- NOTE: 115100 is l10n_ca "Customers Account" (240 postings — AR control) — kept as l10n_ca.
115110 is l10n_ca "Customers Account (PoS)" — kept.
Nexa intercompany receivables live in the 119xxx range to avoid all collisions. -->
<record id="acct_119100" model="account.account">
<field name="code">119100</field>
<field name="name">Due From Shareholder — Gurpreet</field>
<field name="account_type">asset_current</field>
<field name="reconcile" eval="True"/>
</record>
<record id="acct_119900" model="account.account">
<field name="code">119900</field>
<field name="name">Due From Associated Corporations</field>
<field name="account_type">asset_current</field>
<field name="reconcile" eval="True"/>
</record>
<record id="acct_118100" model="account.account">
<field name="code">118100</field>
<field name="name">HST/GST Input Tax Credit (ITC) Receivable</field>
<field name="account_type">asset_current</field>
</record>
<record id="acct_118200" model="account.account">
<field name="code">118200</field>
<field name="name">HST/GST Instalments Paid</field>
<field name="account_type">asset_current</field>
</record>
<record id="acct_118300" model="account.account">
<field name="code">118300</field>
<field name="name">QST Input Tax Refund Receivable</field>
<field name="account_type">asset_current</field>
</record>
<record id="acct_151100" model="account.account">
<field name="code">151100</field>
<field name="name">Computer Hardware &amp; Equipment (CCA Class 50)</field>
<field name="account_type">asset_fixed</field>
</record>
<record id="acct_151200" model="account.account">
<field name="code">151200</field>
<field name="name">Office Furniture &amp; Equipment (CCA Class 8)</field>
<field name="account_type">asset_fixed</field>
</record>
<record id="acct_151300" model="account.account">
<field name="code">151300</field>
<field name="name">Vehicles (CCA Class 10/10.1)</field>
<field name="account_type">asset_fixed</field>
</record>
<record id="acct_151400" model="account.account">
<field name="code">151400</field>
<field name="name">Leasehold Improvements (CCA Class 13)</field>
<field name="account_type">asset_fixed</field>
</record>
<record id="acct_151500" model="account.account">
<field name="code">151500</field>
<field name="name">Acquired Software &amp; Intangibles (CCA Class 14.1)</field>
<field name="account_type">asset_fixed</field>
</record>
<record id="acct_151600" model="account.account">
<field name="code">151600</field>
<field name="name">Tools &amp; Small Equipment &lt;$500 (CCA Class 12)</field>
<field name="account_type">asset_fixed</field>
</record>
<record id="acct_154100" model="account.account">
<field name="code">154100</field>
<field name="name">Acc. Depreciation — Computer Hardware</field>
<field name="account_type">asset_fixed</field>
</record>
<record id="acct_154200" model="account.account">
<field name="code">154200</field>
<field name="name">Acc. Depreciation — Office Furniture</field>
<field name="account_type">asset_fixed</field>
</record>
<record id="acct_154300" model="account.account">
<field name="code">154300</field>
<field name="name">Acc. Depreciation — Vehicles</field>
<field name="account_type">asset_fixed</field>
</record>
<record id="acct_154400" model="account.account">
<field name="code">154400</field>
<field name="name">Acc. Depreciation — Leasehold Improvements</field>
<field name="account_type">asset_fixed</field>
</record>
<record id="acct_154500" model="account.account">
<field name="code">154500</field>
<field name="name">Acc. Depreciation — Acquired Software</field>
<field name="account_type">asset_fixed</field>
</record>
<!-- ============================================================ -->
<!-- 2xxxxx — LIABILITIES -->
<!-- ============================================================ -->
<record id="acct_213100" model="account.account">
<field name="code">213100</field>
<field name="name">HST/GST Collected on Sales</field>
<field name="account_type">liability_current</field>
</record>
<record id="acct_213500" model="account.account">
<field name="code">213500</field>
<field name="name">QST Collected on Sales</field>
<field name="account_type">liability_current</field>
</record>
<record id="acct_214100" model="account.account">
<field name="code">214100</field>
<field name="name">Net HST/GST Payable</field>
<field name="account_type">liability_current</field>
</record>
<record id="acct_215100" model="account.account">
<field name="code">215100</field>
<field name="name">Source Deductions Payable — Federal Tax</field>
<field name="account_type">liability_current</field>
</record>
<record id="acct_215200" model="account.account">
<field name="code">215200</field>
<field name="name">Source Deductions Payable — CPP</field>
<field name="account_type">liability_current</field>
</record>
<record id="acct_215300" model="account.account">
<field name="code">215300</field>
<field name="name">Source Deductions Payable — EI</field>
<field name="account_type">liability_current</field>
</record>
<record id="acct_216100" model="account.account">
<field name="code">216100</field>
<field name="name">Corporate Income Tax — Federal Payable</field>
<field name="account_type">liability_current</field>
</record>
<record id="acct_216200" model="account.account">
<field name="code">216200</field>
<field name="name">Corporate Income Tax — Provincial Payable</field>
<field name="account_type">liability_current</field>
</record>
<record id="acct_216300" model="account.account">
<field name="code">216300</field>
<field name="name">Corporate Tax Instalments Paid</field>
<field name="account_type">asset_current</field>
</record>
<record id="acct_221100" model="account.account">
<field name="code">221100</field>
<field name="name">Due To Shareholder — Gurpreet (short-term)</field>
<field name="account_type">liability_current</field>
<field name="reconcile" eval="True"/>
</record>
<record id="acct_221200" model="account.account">
<field name="code">221200</field>
<field name="name">Shareholder Loan — Gurpreet (long-term)</field>
<field name="account_type">liability_non_current</field>
<field name="reconcile" eval="True"/>
</record>
<record id="acct_222900" model="account.account">
<field name="code">222900</field>
<field name="name">Due To Associated Corporations</field>
<field name="account_type">liability_current</field>
<field name="reconcile" eval="True"/>
</record>
<!-- ============================================================ -->
<!-- 3xxxxx — EQUITY -->
<!-- ============================================================ -->
<record id="acct_311100" model="account.account">
<field name="code">311100</field>
<field name="name">Share Capital — Common Shares</field>
<field name="account_type">equity</field>
</record>
<record id="acct_311200" model="account.account">
<field name="code">311200</field>
<field name="name">Share Capital — Preferred Shares</field>
<field name="account_type">equity</field>
</record>
<record id="acct_311300" model="account.account">
<field name="code">311300</field>
<field name="name">Contributed Surplus</field>
<field name="account_type">equity</field>
</record>
<record id="acct_321100" model="account.account">
<field name="code">321100</field>
<field name="name">Retained Earnings — Current Year</field>
<field name="account_type">equity</field>
</record>
<record id="acct_321200" model="account.account">
<field name="code">321200</field>
<field name="name">Retained Earnings — Prior Years</field>
<field name="account_type">equity</field>
</record>
<record id="acct_321900" model="account.account">
<field name="code">321900</field>
<field name="name">Dividends Declared</field>
<field name="account_type">equity</field>
</record>
<!-- ============================================================ -->
<!-- 4xxxxx — REVENUE -->
<!-- ============================================================ -->
<record id="acct_411100" model="account.account">
<field name="code">411100</field>
<field name="name">SaaS Subscription Revenue</field>
<field name="account_type">income</field>
</record>
<record id="acct_411200" model="account.account">
<field name="code">411200</field>
<field name="name">Hosting &amp; Infrastructure Revenue</field>
<field name="account_type">income</field>
</record>
<record id="acct_411300" model="account.account">
<field name="code">411300</field>
<field name="name">Support &amp; Maintenance Contracts Revenue</field>
<field name="account_type">income</field>
</record>
<record id="acct_411400" model="account.account">
<field name="code">411400</field>
<field name="name">Domain/SSL/Renewal Pass-through Revenue</field>
<field name="account_type">income</field>
</record>
<record id="acct_411500" model="account.account">
<field name="code">411500</field>
<field name="name">Setup / Onboarding Fees Revenue</field>
<field name="account_type">income</field>
</record>
<record id="acct_412100" model="account.account">
<field name="code">412100</field>
<field name="name">Custom Software Development Revenue</field>
<field name="account_type">income</field>
</record>
<record id="acct_412200" model="account.account">
<field name="code">412200</field>
<field name="name">Custom Web Application Development Revenue</field>
<field name="account_type">income</field>
</record>
<record id="acct_412300" model="account.account">
<field name="code">412300</field>
<field name="name">Custom Website Development Revenue</field>
<field name="account_type">income</field>
</record>
<record id="acct_412400" model="account.account">
<field name="code">412400</field>
<field name="name">ERP Implementation &amp; Customization Revenue</field>
<field name="account_type">income</field>
</record>
<record id="acct_412500" model="account.account">
<field name="code">412500</field>
<field name="name">Mobile App Development Revenue</field>
<field name="account_type">income</field>
</record>
<record id="acct_412600" model="account.account">
<field name="code">412600</field>
<field name="name">Business App / Integration Revenue</field>
<field name="account_type">income</field>
</record>
<record id="acct_413100" model="account.account">
<field name="code">413100</field>
<field name="name">Consulting &amp; Advisory Revenue</field>
<field name="account_type">income</field>
</record>
<record id="acct_413200" model="account.account">
<field name="code">413200</field>
<field name="name">Training &amp; Workshops Revenue</field>
<field name="account_type">income</field>
</record>
<record id="acct_413300" model="account.account">
<field name="code">413300</field>
<field name="name">Technical Support — Per-incident / Hourly Revenue</field>
<field name="account_type">income</field>
</record>
<record id="acct_414100" model="account.account">
<field name="code">414100</field>
<field name="name">Third-party Software Resale Revenue</field>
<field name="account_type">income</field>
</record>
<record id="acct_414200" model="account.account">
<field name="code">414200</field>
<field name="name">Hardware Resale Revenue</field>
<field name="account_type">income</field>
</record>
<record id="acct_419100" model="account.account">
<field name="code">419100</field>
<field name="name">Sales Discounts</field>
<field name="account_type">income</field>
</record>
<record id="acct_419200" model="account.account">
<field name="code">419200</field>
<field name="name">Sales Returns &amp; Refunds</field>
<field name="account_type">income</field>
</record>
<record id="acct_419300" model="account.account">
<field name="code">419300</field>
<field name="name">Bad Debt Recovery</field>
<field name="account_type">income_other</field>
</record>
<!-- ============================================================ -->
<!-- 5xxxxx — DIRECT COSTS (COGS) -->
<!-- ============================================================ -->
<!-- NOTE: 511100 was "Inside Purchases" in l10n_ca (1 posting from legacy bookkeeping) — kept as l10n_ca.
Cloud Infrastructure sits at 511105 to avoid collision. -->
<record id="acct_511105" model="account.account">
<field name="code">511105</field>
<field name="name">Cloud Infrastructure (AWS, Hetzner, OVH, DigitalOcean, Linode)</field>
<field name="account_type">expense_direct_cost</field>
</record>
<record id="acct_511110" model="account.account">
<field name="code">511110</field>
<field name="name">CDN &amp; Edge Services (Cloudflare, Fastly)</field>
<field name="account_type">expense_direct_cost</field>
</record>
<record id="acct_511120" model="account.account">
<field name="code">511120</field>
<field name="name">Backup &amp; Storage Services</field>
<field name="account_type">expense_direct_cost</field>
</record>
<record id="acct_511130" model="account.account">
<field name="code">511130</field>
<field name="name">Database &amp; Backend Services (Supabase, hosted Postgres, Redis)</field>
<field name="account_type">expense_direct_cost</field>
</record>
<record id="acct_511140" model="account.account">
<field name="code">511140</field>
<field name="name">Monitoring &amp; Observability (customer-facing only)</field>
<field name="account_type">expense_direct_cost</field>
</record>
<record id="acct_511150" model="account.account">
<field name="code">511150</field>
<field name="name">SSL Certificates &amp; Domains (wholesale for resale)</field>
<field name="account_type">expense_direct_cost</field>
</record>
<record id="acct_511160" model="account.account">
<field name="code">511160</field>
<field name="name">DNS &amp; Email Hosting (wholesale for resale)</field>
<field name="account_type">expense_direct_cost</field>
</record>
<record id="acct_511200" model="account.account">
<field name="code">511200</field>
<field name="name">Third-party API Costs (Twilio, SendGrid, OpenAI)</field>
<field name="account_type">expense_direct_cost</field>
</record>
<record id="acct_511210" model="account.account">
<field name="code">511210</field>
<field name="name">Per-customer Licensing &amp; Royalties</field>
<field name="account_type">expense_direct_cost</field>
</record>
<record id="acct_512100" model="account.account">
<field name="code">512100</field>
<field name="name">Subcontracted Labour — Canadian (T4A) — SR&amp;ED-eligible</field>
<field name="account_type">expense_direct_cost</field>
</record>
<record id="acct_512110" model="account.account">
<field name="code">512110</field>
<field name="name">Subcontracted Labour — Foreign — NOT SR&amp;ED-eligible</field>
<field name="account_type">expense_direct_cost</field>
</record>
<record id="acct_512200" model="account.account">
<field name="code">512200</field>
<field name="name">Project-specific Software &amp; Licenses</field>
<field name="account_type">expense_direct_cost</field>
</record>
<record id="acct_512300" model="account.account">
<field name="code">512300</field>
<field name="name">Project Travel &amp; Onsite (rebilled)</field>
<field name="account_type">expense_direct_cost</field>
</record>
<record id="acct_512400" model="account.account">
<field name="code">512400</field>
<field name="name">Project Hardware (passed through)</field>
<field name="account_type">expense_direct_cost</field>
</record>
<record id="acct_513100" model="account.account">
<field name="code">513100</field>
<field name="name">Cost of Software Resold</field>
<field name="account_type">expense_direct_cost</field>
</record>
<record id="acct_513200" model="account.account">
<field name="code">513200</field>
<field name="name">Cost of Hardware Resold</field>
<field name="account_type">expense_direct_cost</field>
</record>
<record id="acct_519100" model="account.account">
<field name="code">519100</field>
<field name="name">COGS Adjustments / Write-offs</field>
<field name="account_type">expense_direct_cost</field>
</record>
<!-- ============================================================ -->
<!-- 6xxxxx — OPERATING EXPENSES -->
<!-- ============================================================ -->
<record id="acct_611100" model="account.account">
<field name="code">611100</field>
<field name="name">Salaries &amp; Wages — Development (SR&amp;ED-eligible)</field>
<field name="account_type">expense</field>
</record>
<record id="acct_611200" model="account.account">
<field name="code">611200</field>
<field name="name">Salaries &amp; Wages — Sales &amp; Marketing</field>
<field name="account_type">expense</field>
</record>
<record id="acct_611300" model="account.account">
<field name="code">611300</field>
<field name="name">Salaries &amp; Wages — Admin &amp; Operations</field>
<field name="account_type">expense</field>
</record>
<record id="acct_611400" model="account.account">
<field name="code">611400</field>
<field name="name">Salary — Shareholder/Officer (Gurpreet)</field>
<field name="account_type">expense</field>
</record>
<record id="acct_611500" model="account.account">
<field name="code">611500</field>
<field name="name">Employer CPP / QPP Contributions</field>
<field name="account_type">expense</field>
</record>
<record id="acct_611600" model="account.account">
<field name="code">611600</field>
<field name="name">Employer EI Premiums</field>
<field name="account_type">expense</field>
</record>
<record id="acct_611700" model="account.account">
<field name="code">611700</field>
<field name="name">Employer Health Tax (EHT/QHST)</field>
<field name="account_type">expense</field>
</record>
<record id="acct_611800" model="account.account">
<field name="code">611800</field>
<field name="name">WCB / WSIB Premiums</field>
<field name="account_type">expense</field>
</record>
<record id="acct_611900" model="account.account">
<field name="code">611900</field>
<field name="name">Employee Benefits (health, dental, group)</field>
<field name="account_type">expense</field>
</record>
<record id="acct_611950" model="account.account">
<field name="code">611950</field>
<field name="name">Bonuses &amp; Incentives</field>
<field name="account_type">expense</field>
</record>
<record id="acct_611960" model="account.account">
<field name="code">611960</field>
<field name="name">Vacation Pay Accrual</field>
<field name="account_type">expense</field>
</record>
<record id="acct_612100" model="account.account">
<field name="code">612100</field>
<field name="name">Contract Labour — Canadian (admin/marketing/freelance)</field>
<field name="account_type">expense</field>
</record>
<record id="acct_612200" model="account.account">
<field name="code">612200</field>
<field name="name">Contract Labour — Foreign</field>
<field name="account_type">expense</field>
</record>
<record id="acct_621100" model="account.account">
<field name="code">621100</field>
<field name="name">Rent — Commercial Office</field>
<field name="account_type">expense</field>
</record>
<record id="acct_621200" model="account.account">
<field name="code">621200</field>
<field name="name">Home Office — Business Portion</field>
<field name="account_type">expense</field>
</record>
<record id="acct_621300" model="account.account">
<field name="code">621300</field>
<field name="name">Utilities — Commercial</field>
<field name="account_type">expense</field>
</record>
<record id="acct_621400" model="account.account">
<field name="code">621400</field>
<field name="name">Internet &amp; Phone — Business</field>
<field name="account_type">expense</field>
</record>
<record id="acct_621500" model="account.account">
<field name="code">621500</field>
<field name="name">Office Supplies &amp; Consumables</field>
<field name="account_type">expense</field>
</record>
<record id="acct_621600" model="account.account">
<field name="code">621600</field>
<field name="name">Cleaning &amp; Maintenance</field>
<field name="account_type">expense</field>
</record>
<record id="acct_621700" model="account.account">
<field name="code">621700</field>
<field name="name">Office Snacks &amp; Refreshments</field>
<field name="account_type">expense</field>
</record>
<record id="acct_631100" model="account.account">
<field name="code">631100</field>
<field name="name">Software — Productivity (M365, Slack, Notion, Linear, GitHub)</field>
<field name="account_type">expense</field>
</record>
<record id="acct_631200" model="account.account">
<field name="code">631200</field>
<field name="name">Software — Development Tools (Cursor, Figma, IDEs)</field>
<field name="account_type">expense</field>
</record>
<record id="acct_631300" model="account.account">
<field name="code">631300</field>
<field name="name">Software — Internal Infrastructure</field>
<field name="account_type">expense</field>
</record>
<record id="acct_631400" model="account.account">
<field name="code">631400</field>
<field name="name">Software — Security &amp; IT</field>
<field name="account_type">expense</field>
</record>
<record id="acct_631500" model="account.account">
<field name="code">631500</field>
<field name="name">Software — Sales &amp; Marketing</field>
<field name="account_type">expense</field>
</record>
<record id="acct_641100" model="account.account">
<field name="code">641100</field>
<field name="name">Advertising — Digital Ads</field>
<field name="account_type">expense</field>
</record>
<record id="acct_641200" model="account.account">
<field name="code">641200</field>
<field name="name">Advertising — Content / SEO</field>
<field name="account_type">expense</field>
</record>
<record id="acct_641300" model="account.account">
<field name="code">641300</field>
<field name="name">Trade Shows &amp; Conferences</field>
<field name="account_type">expense</field>
</record>
<record id="acct_641400" model="account.account">
<field name="code">641400</field>
<field name="name">Promotional Items / Branded Swag</field>
<field name="account_type">expense</field>
</record>
<record id="acct_641500" model="account.account">
<field name="code">641500</field>
<field name="name">Website — Own (nexasystems.ca)</field>
<field name="account_type">expense</field>
</record>
<record id="acct_651100" model="account.account">
<field name="code">651100</field>
<field name="name">Legal Fees — General</field>
<field name="account_type">expense</field>
</record>
<record id="acct_651200" model="account.account">
<field name="code">651200</field>
<field name="name">Accounting &amp; Bookkeeping</field>
<field name="account_type">expense</field>
</record>
<record id="acct_651300" model="account.account">
<field name="code">651300</field>
<field name="name">Tax Preparation (T2, T1, GST/HST)</field>
<field name="account_type">expense</field>
</record>
<record id="acct_651400" model="account.account">
<field name="code">651400</field>
<field name="name">Business Consulting</field>
<field name="account_type">expense</field>
</record>
<record id="acct_661100" model="account.account">
<field name="code">661100</field>
<field name="name">Insurance — Commercial General Liability</field>
<field name="account_type">expense</field>
</record>
<record id="acct_661200" model="account.account">
<field name="code">661200</field>
<field name="name">Insurance — Professional Liability / E&amp;O</field>
<field name="account_type">expense</field>
</record>
<record id="acct_661300" model="account.account">
<field name="code">661300</field>
<field name="name">Insurance — Cyber Liability</field>
<field name="account_type">expense</field>
</record>
<record id="acct_661400" model="account.account">
<field name="code">661400</field>
<field name="name">Insurance — Property</field>
<field name="account_type">expense</field>
</record>
<record id="acct_661500" model="account.account">
<field name="code">661500</field>
<field name="name">Insurance — Directors &amp; Officers</field>
<field name="account_type">expense</field>
</record>
<record id="acct_671100" model="account.account">
<field name="code">671100</field>
<field name="name">Travel — Flights, Hotels, Ground Transport</field>
<field name="account_type">expense</field>
</record>
<record id="acct_671200" model="account.account">
<field name="code">671200</field>
<field name="name">Meals &amp; Entertainment — 50% Deductible</field>
<field name="account_type">expense</field>
</record>
<record id="acct_671300" model="account.account">
<field name="code">671300</field>
<field name="name">Vehicle — Operating (gas, insurance, repairs, parking)</field>
<field name="account_type">expense</field>
</record>
<record id="acct_671400" model="account.account">
<field name="code">671400</field>
<field name="name">Mileage Reimbursement — Personal Vehicle</field>
<field name="account_type">expense</field>
</record>
<record id="acct_681100" model="account.account">
<field name="code">681100</field>
<field name="name">Conferences &amp; Seminars (registration)</field>
<field name="account_type">expense</field>
</record>
<record id="acct_681200" model="account.account">
<field name="code">681200</field>
<field name="name">Courses &amp; Certifications</field>
<field name="account_type">expense</field>
</record>
<record id="acct_681300" model="account.account">
<field name="code">681300</field>
<field name="name">Books &amp; Publications</field>
<field name="account_type">expense</field>
</record>
<record id="acct_681400" model="account.account">
<field name="code">681400</field>
<field name="name">Professional Memberships &amp; Dues</field>
<field name="account_type">expense</field>
</record>
<record id="acct_691100" model="account.account">
<field name="code">691100</field>
<field name="name">Bank Service Charges</field>
<field name="account_type">expense</field>
</record>
<record id="acct_691200" model="account.account">
<field name="code">691200</field>
<field name="name">Merchant Processing Fees (Stripe, PayPal, Square)</field>
<field name="account_type">expense</field>
</record>
<record id="acct_691300" model="account.account">
<field name="code">691300</field>
<field name="name">Wire Transfer &amp; FX Fees</field>
<field name="account_type">expense</field>
</record>
<record id="acct_691400" model="account.account">
<field name="code">691400</field>
<field name="name">Interest Expense — Bank Loans / LOC</field>
<field name="account_type">expense</field>
</record>
<record id="acct_691500" model="account.account">
<field name="code">691500</field>
<field name="name">Interest Expense — Credit Cards</field>
<field name="account_type">expense</field>
</record>
<record id="acct_691600" model="account.account">
<field name="code">691600</field>
<field name="name">Late Payment Penalties — Non-deductible</field>
<field name="account_type">expense</field>
</record>
<record id="acct_699100" model="account.account">
<field name="code">699100</field>
<field name="name">Bad Debt Expense</field>
<field name="account_type">expense</field>
</record>
<record id="acct_699200" model="account.account">
<field name="code">699200</field>
<field name="name">Donations &amp; Sponsorships (deductible)</field>
<field name="account_type">expense</field>
</record>
<record id="acct_699300" model="account.account">
<field name="code">699300</field>
<field name="name">Penalties &amp; Fines — Non-deductible</field>
<field name="account_type">expense</field>
</record>
<record id="acct_699400" model="account.account">
<field name="code">699400</field>
<field name="name">Realized FX Losses</field>
<field name="account_type">expense</field>
</record>
<record id="acct_699500" model="account.account">
<field name="code">699500</field>
<field name="name">Depreciation / CCA Expense</field>
<field name="account_type">expense</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="0">
</data>
</odoo>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="0">
</data>
</odoo>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="0">
</data>
</odoo>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="0">
</data>
</odoo>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="0">
</data>
</odoo>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="0">
</data>
</odoo>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="0">
</data>
</odoo>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="0">
</data>
</odoo>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="0">
</data>
</odoo>

68
nexa_coa_setup/hooks.py Normal file
View File

@@ -0,0 +1,68 @@
# -*- 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):
"""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,
)

View File

@@ -0,0 +1 @@
# no custom models — placeholder for future extensions

View File

@@ -0,0 +1 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink