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