diff --git a/fusion_accounting_l10n_ca/__init__.py b/fusion_accounting_l10n_ca/__init__.py
new file mode 100644
index 00000000..0650744f
--- /dev/null
+++ b/fusion_accounting_l10n_ca/__init__.py
@@ -0,0 +1 @@
+from . import models
diff --git a/fusion_accounting_l10n_ca/__manifest__.py b/fusion_accounting_l10n_ca/__manifest__.py
new file mode 100644
index 00000000..1fc3db66
--- /dev/null
+++ b/fusion_accounting_l10n_ca/__manifest__.py
@@ -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',
+}
diff --git a/fusion_accounting_l10n_ca/data/fusion_tax_return_data.xml b/fusion_accounting_l10n_ca/data/fusion_tax_return_data.xml
new file mode 100644
index 00000000..817d90d2
--- /dev/null
+++ b/fusion_accounting_l10n_ca/data/fusion_tax_return_data.xml
@@ -0,0 +1,9 @@
+
+
+
+ Fusion Tax Return
+ fusion.tax.return
+ TAX/%(year)s/
+ 4
+
+
diff --git a/fusion_accounting_l10n_ca/data/report_ca_balance_sheet.xml b/fusion_accounting_l10n_ca/data/report_ca_balance_sheet.xml
new file mode 100644
index 00000000..b58b349b
--- /dev/null
+++ b/fusion_accounting_l10n_ca/data/report_ca_balance_sheet.xml
@@ -0,0 +1,45 @@
+
+
+
+ Balance Sheet (Canada)
+ ca_balance_sheet
+ balance_sheet
+ 21
+ previous_period
+ Canadian-formatted balance sheet aligned to GAAP/IFRS classifications used in Canada.
+
+
+
+
diff --git a/fusion_accounting_l10n_ca/data/report_ca_profit_loss.xml b/fusion_accounting_l10n_ca/data/report_ca_profit_loss.xml
new file mode 100644
index 00000000..fe7c1fcf
--- /dev/null
+++ b/fusion_accounting_l10n_ca/data/report_ca_profit_loss.xml
@@ -0,0 +1,31 @@
+
+
+
+ Profit and Loss (Canada)
+ ca_profit_loss
+ pnl
+ 12
+ previous_year
+ Canadian-formatted income statement.
+
+
+
+
diff --git a/fusion_accounting_l10n_ca/models/__init__.py b/fusion_accounting_l10n_ca/models/__init__.py
new file mode 100644
index 00000000..db35004d
--- /dev/null
+++ b/fusion_accounting_l10n_ca/models/__init__.py
@@ -0,0 +1 @@
+from . import fusion_tax_return
diff --git a/fusion_accounting_l10n_ca/models/fusion_tax_return.py b/fusion_accounting_l10n_ca/models/fusion_tax_return.py
new file mode 100644
index 00000000..9a285e92
--- /dev/null
+++ b/fusion_accounting_l10n_ca/models/fusion_tax_return.py
@@ -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
diff --git a/fusion_accounting_l10n_ca/security/ir.model.access.csv b/fusion_accounting_l10n_ca/security/ir.model.access.csv
new file mode 100644
index 00000000..8d8779f1
--- /dev/null
+++ b/fusion_accounting_l10n_ca/security/ir.model.access.csv
@@ -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
diff --git a/fusion_accounting_l10n_ca/static/description/icon.png b/fusion_accounting_l10n_ca/static/description/icon.png
new file mode 100644
index 00000000..6773c627
Binary files /dev/null and b/fusion_accounting_l10n_ca/static/description/icon.png differ
diff --git a/fusion_accounting_l10n_ca/tests/__init__.py b/fusion_accounting_l10n_ca/tests/__init__.py
new file mode 100644
index 00000000..f2bc84b5
--- /dev/null
+++ b/fusion_accounting_l10n_ca/tests/__init__.py
@@ -0,0 +1 @@
+from . import test_l10n_ca
diff --git a/fusion_accounting_l10n_ca/tests/test_l10n_ca.py b/fusion_accounting_l10n_ca/tests/test_l10n_ca.py
new file mode 100644
index 00000000..303342de
--- /dev/null
+++ b/fusion_accounting_l10n_ca/tests/test_l10n_ca.py
@@ -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")