feat(fusion_accounting_l10n_ca): Canadian reports + tax return tracking
Replaces Enterprise's l10n_ca_reports with Fusion-native equivalents: - ca_balance_sheet, ca_profit_loss as fusion.report definitions - fusion.tax.return model for GST/HST/PST/T4/T5018 filing tracking - Auto-installs when l10n_ca + fusion_accounting_reports both present Removes Westin's last Canadian-compliance dependency on Enterprise's account_reports. Made-with: Cursor
This commit is contained in:
1
fusion_accounting_l10n_ca/__init__.py
Normal file
1
fusion_accounting_l10n_ca/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from . import models
|
||||||
31
fusion_accounting_l10n_ca/__manifest__.py
Normal file
31
fusion_accounting_l10n_ca/__manifest__.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
'name': 'Fusion Accounting — Canadian Reports',
|
||||||
|
'version': '19.0.1.0.0',
|
||||||
|
'category': 'Accounting/Localizations/Reporting',
|
||||||
|
'summary': 'Canadian-specific report definitions and tax return templates for Fusion Accounting.',
|
||||||
|
'description': """
|
||||||
|
Replaces Enterprise's l10n_ca_reports module with Fusion-native equivalents:
|
||||||
|
- Canadian Balance Sheet (report definition for fusion_accounting_reports engine)
|
||||||
|
- Canadian Profit & Loss (report definition)
|
||||||
|
- Tax return tracking templates (GST/HST/PST periods)
|
||||||
|
|
||||||
|
Auto-installs when l10n_ca + fusion_accounting_reports are both present.
|
||||||
|
""",
|
||||||
|
'depends': [
|
||||||
|
'fusion_accounting_core',
|
||||||
|
'fusion_accounting_reports',
|
||||||
|
'l10n_ca',
|
||||||
|
],
|
||||||
|
'data': [
|
||||||
|
'security/ir.model.access.csv',
|
||||||
|
'data/fusion_tax_return_data.xml',
|
||||||
|
'data/report_ca_balance_sheet.xml',
|
||||||
|
'data/report_ca_profit_loss.xml',
|
||||||
|
],
|
||||||
|
'auto_install': ['l10n_ca', 'fusion_accounting_reports'],
|
||||||
|
'installable': True,
|
||||||
|
'application': False,
|
||||||
|
'license': 'LGPL-3',
|
||||||
|
'author': 'Westin / Fusion Suite',
|
||||||
|
'icon': '/fusion_accounting_l10n_ca/static/description/icon.png',
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo noupdate="1">
|
||||||
|
<record id="seq_fusion_tax_return" model="ir.sequence">
|
||||||
|
<field name="name">Fusion Tax Return</field>
|
||||||
|
<field name="code">fusion.tax.return</field>
|
||||||
|
<field name="prefix">TAX/%(year)s/</field>
|
||||||
|
<field name="padding">4</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
45
fusion_accounting_l10n_ca/data/report_ca_balance_sheet.xml
Normal file
45
fusion_accounting_l10n_ca/data/report_ca_balance_sheet.xml
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo noupdate="1">
|
||||||
|
<record id="report_ca_balance_sheet" model="fusion.report">
|
||||||
|
<field name="name">Balance Sheet (Canada)</field>
|
||||||
|
<field name="code">ca_balance_sheet</field>
|
||||||
|
<field name="report_type">balance_sheet</field>
|
||||||
|
<field name="sequence">21</field>
|
||||||
|
<field name="default_comparison_mode">previous_period</field>
|
||||||
|
<field name="description">Canadian-formatted balance sheet aligned to GAAP/IFRS classifications used in Canada.</field>
|
||||||
|
<field name="line_specs" eval="[
|
||||||
|
{'label': 'ASSETS', 'level': 0},
|
||||||
|
{'label': 'Current Assets', 'level': 1},
|
||||||
|
{'label': 'Cash and Bank', 'account_type_prefix': 'asset_cash', 'sign': 1, 'level': 2},
|
||||||
|
{'label': 'Accounts Receivable', 'account_type_prefix': 'asset_receivable', 'sign': 1, 'level': 2},
|
||||||
|
{'label': 'Inventory', 'account_type_prefix': 'asset_current', 'sign': 1, 'level': 2},
|
||||||
|
{'label': 'Prepaid Expenses', 'account_type_prefix': 'asset_prepayments', 'sign': 1, 'level': 2},
|
||||||
|
{'label': 'Total Current Assets', 'compute': 'subtotal', 'above': 4, 'sign': 1, 'level': 1},
|
||||||
|
|
||||||
|
{'label': 'Non-Current Assets', 'level': 1},
|
||||||
|
{'label': 'Property, Plant and Equipment', 'account_type_prefix': 'asset_fixed', 'sign': 1, 'level': 2},
|
||||||
|
{'label': 'Intangible Assets', 'account_type_prefix': 'asset_non_current', 'sign': 1, 'level': 2},
|
||||||
|
{'label': 'Total Non-Current Assets', 'compute': 'subtotal', 'above': 2, 'sign': 1, 'level': 1},
|
||||||
|
|
||||||
|
{'label': 'TOTAL ASSETS', 'compute': 'subtotal', 'above': 2, 'sign': 1, 'level': 0},
|
||||||
|
|
||||||
|
{'label': 'LIABILITIES', 'level': 0},
|
||||||
|
{'label': 'Current Liabilities', 'level': 1},
|
||||||
|
{'label': 'Accounts Payable', 'account_type_prefix': 'liability_payable', 'sign': -1, 'level': 2},
|
||||||
|
{'label': 'Tax Payable (GST/HST/PST)', 'account_type_prefix': 'liability_current', 'sign': -1, 'level': 2},
|
||||||
|
{'label': 'Total Current Liabilities', 'compute': 'subtotal', 'above': 2, 'sign': 1, 'level': 1},
|
||||||
|
|
||||||
|
{'label': 'Long-Term Liabilities', 'account_type_prefix': 'liability_non_current', 'sign': -1, 'level': 1},
|
||||||
|
|
||||||
|
{'label': 'TOTAL LIABILITIES', 'compute': 'subtotal', 'above': 2, 'sign': 1, 'level': 0},
|
||||||
|
|
||||||
|
{'label': 'EQUITY', 'level': 0},
|
||||||
|
{'label': 'Share Capital', 'account_type_prefix': 'equity', 'sign': -1, 'level': 1},
|
||||||
|
{'label': 'Retained Earnings', 'account_type_prefix': 'equity_unaffected', 'sign': -1, 'level': 1},
|
||||||
|
{'label': 'TOTAL EQUITY', 'compute': 'subtotal', 'above': 2, 'sign': 1, 'level': 0},
|
||||||
|
|
||||||
|
{'label': 'TOTAL LIABILITIES + EQUITY', 'compute': 'subtotal', 'above': 2, 'sign': 1, 'level': 0}
|
||||||
|
]"/>
|
||||||
|
<field name="company_id" eval="False"/>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
31
fusion_accounting_l10n_ca/data/report_ca_profit_loss.xml
Normal file
31
fusion_accounting_l10n_ca/data/report_ca_profit_loss.xml
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo noupdate="1">
|
||||||
|
<record id="report_ca_profit_loss" model="fusion.report">
|
||||||
|
<field name="name">Profit and Loss (Canada)</field>
|
||||||
|
<field name="code">ca_profit_loss</field>
|
||||||
|
<field name="report_type">pnl</field>
|
||||||
|
<field name="sequence">12</field>
|
||||||
|
<field name="default_comparison_mode">previous_year</field>
|
||||||
|
<field name="description">Canadian-formatted income statement.</field>
|
||||||
|
<field name="line_specs" eval="[
|
||||||
|
{'label': 'OPERATING REVENUE', 'level': 0},
|
||||||
|
{'label': 'Sales Revenue', 'account_type_prefix': 'income', 'sign': -1, 'level': 1},
|
||||||
|
{'label': 'Other Operating Revenue', 'account_type_prefix': 'income_other', 'sign': -1, 'level': 1},
|
||||||
|
{'label': 'Total Revenue', 'compute': 'subtotal', 'above': 2, 'sign': 1, 'level': 0},
|
||||||
|
|
||||||
|
{'label': 'COST OF GOODS SOLD', 'level': 0},
|
||||||
|
{'label': 'Direct Costs', 'account_type_prefix': 'expense_direct_cost', 'sign': -1, 'level': 1},
|
||||||
|
|
||||||
|
{'label': 'GROSS PROFIT', 'compute': 'subtotal', 'above': 2, 'sign': 1, 'level': 0},
|
||||||
|
|
||||||
|
{'label': 'OPERATING EXPENSES', 'level': 0},
|
||||||
|
{'label': 'Operating Expenses', 'account_type_prefix': 'expense', 'sign': -1, 'level': 1},
|
||||||
|
{'label': 'Depreciation', 'account_type_prefix': 'expense_depreciation', 'sign': -1, 'level': 1},
|
||||||
|
|
||||||
|
{'label': 'OPERATING INCOME', 'compute': 'subtotal', 'above': 3, 'sign': 1, 'level': 0},
|
||||||
|
|
||||||
|
{'label': 'NET INCOME BEFORE TAX', 'compute': 'subtotal', 'above': 1, 'sign': 1, 'level': 0}
|
||||||
|
]"/>
|
||||||
|
<field name="company_id" eval="False"/>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
1
fusion_accounting_l10n_ca/models/__init__.py
Normal file
1
fusion_accounting_l10n_ca/models/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from . import fusion_tax_return
|
||||||
92
fusion_accounting_l10n_ca/models/fusion_tax_return.py
Normal file
92
fusion_accounting_l10n_ca/models/fusion_tax_return.py
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
"""Fusion-native tax return tracking.
|
||||||
|
|
||||||
|
A simpler replacement for Enterprise's `account.return` model: a tax
|
||||||
|
return is a (return_type, period_from, period_to, status) record. Filers
|
||||||
|
mark them filed once submitted to CRA / Revenu Quebec / provincial
|
||||||
|
authorities.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from odoo import _, api, fields, models
|
||||||
|
from odoo.exceptions import UserError
|
||||||
|
|
||||||
|
|
||||||
|
class FusionTaxReturn(models.Model):
|
||||||
|
_name = "fusion.tax.return"
|
||||||
|
_inherit = ["mail.thread"]
|
||||||
|
_description = "Fusion Tax Return Filing"
|
||||||
|
_order = "date_to desc, id desc"
|
||||||
|
|
||||||
|
name = fields.Char(
|
||||||
|
string="Reference",
|
||||||
|
required=True,
|
||||||
|
copy=False,
|
||||||
|
index=True,
|
||||||
|
default=lambda self: _("New"),
|
||||||
|
)
|
||||||
|
return_type = fields.Selection(
|
||||||
|
[
|
||||||
|
("gst_hst", "GST/HST Return"),
|
||||||
|
("pst", "PST Return"),
|
||||||
|
("qst", "QST Return"),
|
||||||
|
("t4", "T4 Slip"),
|
||||||
|
("t5018", "T5018 Statement"),
|
||||||
|
("payroll_remittance", "Payroll Source Deductions"),
|
||||||
|
("other", "Other"),
|
||||||
|
],
|
||||||
|
required=True,
|
||||||
|
default="gst_hst",
|
||||||
|
tracking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
company_id = fields.Many2one(
|
||||||
|
"res.company",
|
||||||
|
required=True,
|
||||||
|
default=lambda self: self.env.company,
|
||||||
|
)
|
||||||
|
currency_id = fields.Many2one(related="company_id.currency_id")
|
||||||
|
|
||||||
|
date_from = fields.Date(string="Period Start", required=True)
|
||||||
|
date_to = fields.Date(string="Period End", required=True)
|
||||||
|
|
||||||
|
state = fields.Selection(
|
||||||
|
[
|
||||||
|
("draft", "Draft"),
|
||||||
|
("to_file", "To File"),
|
||||||
|
("filed", "Filed"),
|
||||||
|
("cancelled", "Cancelled"),
|
||||||
|
],
|
||||||
|
default="draft",
|
||||||
|
required=True,
|
||||||
|
tracking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
filing_date = fields.Date(string="Filed On")
|
||||||
|
filing_reference = fields.Char(string="Confirmation #")
|
||||||
|
notes = fields.Text()
|
||||||
|
|
||||||
|
@api.model_create_multi
|
||||||
|
def create(self, vals_list):
|
||||||
|
for vals in vals_list:
|
||||||
|
if vals.get("name", _("New")) == _("New"):
|
||||||
|
vals["name"] = self.env["ir.sequence"].next_by_code(
|
||||||
|
"fusion.tax.return"
|
||||||
|
) or _("New")
|
||||||
|
return super().create(vals_list)
|
||||||
|
|
||||||
|
@api.constrains("date_from", "date_to")
|
||||||
|
def _check_period(self):
|
||||||
|
for r in self:
|
||||||
|
if r.date_from and r.date_to and r.date_from > r.date_to:
|
||||||
|
raise UserError(_("Period start must precede period end."))
|
||||||
|
|
||||||
|
def action_mark_filed(self):
|
||||||
|
self.ensure_one()
|
||||||
|
if self.state != "to_file":
|
||||||
|
raise UserError(_("Can only mark 'To File' returns as filed."))
|
||||||
|
self.write(
|
||||||
|
{
|
||||||
|
"state": "filed",
|
||||||
|
"filing_date": fields.Date.context_today(self),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return True
|
||||||
3
fusion_accounting_l10n_ca/security/ir.model.access.csv
Normal file
3
fusion_accounting_l10n_ca/security/ir.model.access.csv
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||||
|
access_fusion_tax_return_user,fusion.tax.return.user,model_fusion_tax_return,base.group_user,1,0,0,0
|
||||||
|
access_fusion_tax_return_manager,fusion.tax.return.manager,model_fusion_tax_return,account.group_account_manager,1,1,1,1
|
||||||
|
BIN
fusion_accounting_l10n_ca/static/description/icon.png
Normal file
BIN
fusion_accounting_l10n_ca/static/description/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 72 KiB |
1
fusion_accounting_l10n_ca/tests/__init__.py
Normal file
1
fusion_accounting_l10n_ca/tests/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from . import test_l10n_ca
|
||||||
36
fusion_accounting_l10n_ca/tests/test_l10n_ca.py
Normal file
36
fusion_accounting_l10n_ca/tests/test_l10n_ca.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
from datetime import date
|
||||||
|
|
||||||
|
from odoo.tests import tagged
|
||||||
|
from odoo.tests.common import TransactionCase
|
||||||
|
|
||||||
|
|
||||||
|
@tagged("post_install", "-at_install")
|
||||||
|
class TestL10nCa(TransactionCase):
|
||||||
|
|
||||||
|
def test_canadian_reports_seeded(self):
|
||||||
|
Report = self.env["fusion.report"].sudo()
|
||||||
|
ca_bs = Report.search([("code", "=", "ca_balance_sheet")], limit=1)
|
||||||
|
ca_pl = Report.search([("code", "=", "ca_profit_loss")], limit=1)
|
||||||
|
self.assertTrue(ca_bs, "ca_balance_sheet not seeded")
|
||||||
|
self.assertTrue(ca_pl, "ca_profit_loss not seeded")
|
||||||
|
self.assertEqual(ca_bs.report_type, "balance_sheet")
|
||||||
|
self.assertEqual(ca_pl.report_type, "pnl")
|
||||||
|
|
||||||
|
def test_canadian_pnl_runs_via_engine(self):
|
||||||
|
from odoo.addons.fusion_accounting_reports.services.date_periods import Period
|
||||||
|
|
||||||
|
period = Period(date(2025, 1, 1), date(2025, 12, 31), "FY 2025")
|
||||||
|
result = self.env["fusion.report.engine"].compute_pnl(
|
||||||
|
period, report_code="ca_profit_loss",
|
||||||
|
)
|
||||||
|
self.assertEqual(result["report_name"], "Profit and Loss (Canada)")
|
||||||
|
self.assertGreater(len(result["rows"]), 0)
|
||||||
|
|
||||||
|
def test_tax_return_create(self):
|
||||||
|
ret = self.env["fusion.tax.return"].create({
|
||||||
|
"return_type": "gst_hst",
|
||||||
|
"date_from": date(2025, 1, 1),
|
||||||
|
"date_to": date(2025, 3, 31),
|
||||||
|
})
|
||||||
|
self.assertNotEqual(ret.name, "New")
|
||||||
|
self.assertEqual(ret.state, "draft")
|
||||||
Reference in New Issue
Block a user