Initial commit
This commit is contained in:
162
Fusion Accounting/models/fiscal_category.py
Normal file
162
Fusion Accounting/models/fiscal_category.py
Normal file
@@ -0,0 +1,162 @@
|
||||
"""
|
||||
Fusion Accounting - Fiscal Categories
|
||||
|
||||
Provides a classification system for grouping general ledger accounts
|
||||
into fiscal reporting categories (income, expense, asset, liability).
|
||||
These categories drive structured fiscal reports and SAF-T exports,
|
||||
allowing companies to map their chart of accounts onto standardised
|
||||
government reporting taxonomies.
|
||||
|
||||
Original implementation by Nexa Systems Inc.
|
||||
"""
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class FusionFiscalCategory(models.Model):
|
||||
"""
|
||||
A fiscal reporting category that groups one or more GL accounts.
|
||||
|
||||
Each category carries a ``category_type`` that mirrors the four
|
||||
fundamental pillars of double-entry bookkeeping. When a SAF-T or
|
||||
Intrastat export is generated the accounts linked here determine
|
||||
which transactions are included.
|
||||
|
||||
Uniqueness of ``code`` is enforced per company so that external
|
||||
auditors can refer to categories unambiguously.
|
||||
"""
|
||||
|
||||
_name = "fusion.fiscal.category"
|
||||
_description = "Fiscal Category"
|
||||
_order = "code, name"
|
||||
_rec_name = "display_name"
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Fields
|
||||
# ------------------------------------------------------------------
|
||||
name = fields.Char(
|
||||
string="Category Name",
|
||||
required=True,
|
||||
translate=True,
|
||||
help="Human-readable label shown in reports and menus.",
|
||||
)
|
||||
code = fields.Char(
|
||||
string="Code",
|
||||
required=True,
|
||||
help=(
|
||||
"Short alphanumeric identifier used in fiscal exports "
|
||||
"(e.g. SAF-T GroupingCode). Must be unique per company."
|
||||
),
|
||||
)
|
||||
category_type = fields.Selection(
|
||||
selection=[
|
||||
("income", "Income"),
|
||||
("expense", "Expense"),
|
||||
("asset", "Asset"),
|
||||
("liability", "Liability"),
|
||||
],
|
||||
string="Type",
|
||||
required=True,
|
||||
default="expense",
|
||||
help="Determines the section of the fiscal report this category appears in.",
|
||||
)
|
||||
description = fields.Text(
|
||||
string="Description",
|
||||
translate=True,
|
||||
help="Optional long description for internal documentation purposes.",
|
||||
)
|
||||
active = fields.Boolean(
|
||||
string="Active",
|
||||
default=True,
|
||||
help="Archived categories are excluded from new exports but remain on historical records.",
|
||||
)
|
||||
company_id = fields.Many2one(
|
||||
comodel_name="res.company",
|
||||
string="Company",
|
||||
required=True,
|
||||
default=lambda self: self.env.company,
|
||||
help="Company to which this fiscal category belongs.",
|
||||
)
|
||||
account_ids = fields.Many2many(
|
||||
comodel_name="account.account",
|
||||
relation="fusion_fiscal_category_account_rel",
|
||||
column1="category_id",
|
||||
column2="account_id",
|
||||
string="Accounts",
|
||||
help="General-ledger accounts assigned to this fiscal category.",
|
||||
)
|
||||
account_count = fields.Integer(
|
||||
string="# Accounts",
|
||||
compute="_compute_account_count",
|
||||
store=False,
|
||||
help="Number of accounts linked to this category.",
|
||||
)
|
||||
parent_id = fields.Many2one(
|
||||
comodel_name="fusion.fiscal.category",
|
||||
string="Parent Category",
|
||||
index=True,
|
||||
ondelete="restrict",
|
||||
help="Optional parent for hierarchical fiscal taxonomies.",
|
||||
)
|
||||
child_ids = fields.One2many(
|
||||
comodel_name="fusion.fiscal.category",
|
||||
inverse_name="parent_id",
|
||||
string="Sub-categories",
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# SQL constraints
|
||||
# ------------------------------------------------------------------
|
||||
_sql_constraints = [
|
||||
(
|
||||
"unique_code_per_company",
|
||||
"UNIQUE(code, company_id)",
|
||||
"The fiscal category code must be unique within each company.",
|
||||
),
|
||||
]
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Computed fields
|
||||
# ------------------------------------------------------------------
|
||||
@api.depends("account_ids")
|
||||
def _compute_account_count(self):
|
||||
"""Count the number of accounts linked to each category."""
|
||||
for record in self:
|
||||
record.account_count = len(record.account_ids)
|
||||
|
||||
@api.depends("name", "code")
|
||||
def _compute_display_name(self):
|
||||
"""Build a display name combining code and name for clarity."""
|
||||
for record in self:
|
||||
if record.code:
|
||||
record.display_name = f"[{record.code}] {record.name}"
|
||||
else:
|
||||
record.display_name = record.name or ""
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Constraints
|
||||
# ------------------------------------------------------------------
|
||||
@api.constrains("parent_id")
|
||||
def _check_parent_recursion(self):
|
||||
"""Prevent circular parent-child references."""
|
||||
if not self._check_recursion():
|
||||
raise ValidationError(
|
||||
_("A fiscal category cannot be its own ancestor. "
|
||||
"Please choose a different parent.")
|
||||
)
|
||||
|
||||
@api.constrains("account_ids", "company_id")
|
||||
def _check_account_company(self):
|
||||
"""Ensure all linked accounts belong to the same company."""
|
||||
for record in self:
|
||||
foreign = record.account_ids.filtered(
|
||||
lambda a: a.company_id != record.company_id
|
||||
)
|
||||
if foreign:
|
||||
raise ValidationError(
|
||||
_("All linked accounts must belong to company '%(company)s'. "
|
||||
"The following accounts belong to a different company: %(accounts)s",
|
||||
company=record.company_id.name,
|
||||
accounts=", ".join(foreign.mapped("code")))
|
||||
)
|
||||
Reference in New Issue
Block a user