feat(fusion_accounting_reports): fusion.report definition model
Persistent definition of a Fusion financial report. Each report (P&L, balance sheet, trial balance, GL) has one row in fusion.report holding its metadata + line specs (stored as JSON for layout flexibility). V19 conventions: models.Constraint inline, no _sql_constraints. Per- company uniqueness on (company_id, code). 3 new tests, 27 total passing. Made-with: Cursor
This commit is contained in:
@@ -1 +1,2 @@
|
||||
from . import services
|
||||
from . import models
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
'name': 'Fusion Accounting Reports',
|
||||
'version': '19.0.1.0.0',
|
||||
'version': '19.0.1.0.1',
|
||||
'category': 'Accounting/Accounting',
|
||||
'summary': 'AI-augmented financial reports (P&L, balance sheet, trial balance, GL).',
|
||||
'description': """
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
from . import fusion_report
|
||||
|
||||
63
fusion_accounting_reports/models/fusion_report.py
Normal file
63
fusion_accounting_reports/models/fusion_report.py
Normal file
@@ -0,0 +1,63 @@
|
||||
"""Persistent definition of a Fusion financial report.
|
||||
|
||||
Each report (P&L, balance sheet, trial balance, GL) has ONE row in
|
||||
fusion.report describing its metadata + line specs. The line specs
|
||||
are stored as a JSON-typed field for flexibility (each line spec
|
||||
includes account_type filter, sub-totaling rules, sign convention)."""
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
|
||||
|
||||
REPORT_TYPES = [
|
||||
('pnl', 'Income Statement (P&L)'),
|
||||
('balance_sheet', 'Balance Sheet'),
|
||||
('trial_balance', 'Trial Balance'),
|
||||
('general_ledger', 'General Ledger'),
|
||||
]
|
||||
|
||||
|
||||
class FusionReport(models.Model):
|
||||
_name = "fusion.report"
|
||||
_description = "Fusion Financial Report Definition"
|
||||
_order = "sequence, id"
|
||||
|
||||
name = fields.Char(required=True, translate=True)
|
||||
code = fields.Char(
|
||||
required=True,
|
||||
help="Unique technical code (e.g. 'pnl', 'balance_sheet').",
|
||||
)
|
||||
report_type = fields.Selection(REPORT_TYPES, required=True)
|
||||
sequence = fields.Integer(default=10)
|
||||
description = fields.Text()
|
||||
active = fields.Boolean(default=True)
|
||||
|
||||
# Layout config - stored as JSON for flexibility per report type.
|
||||
# Example for P&L:
|
||||
# [
|
||||
# {"label": "Revenue", "account_type_prefix": "income_", "sign": 1},
|
||||
# {"label": "Cost of Goods Sold", "account_type_prefix": "expense_direct_", "sign": -1},
|
||||
# {"label": "Gross Profit", "compute": "subtotal", "above": 2},
|
||||
# ...
|
||||
# ]
|
||||
line_specs = fields.Json(string="Line Specs")
|
||||
|
||||
show_zero_balances = fields.Boolean(default=False)
|
||||
show_unposted = fields.Boolean(default=False)
|
||||
default_comparison_mode = fields.Selection(
|
||||
[
|
||||
('none', 'No comparison'),
|
||||
('previous_period', 'Previous Period'),
|
||||
('previous_year', 'Previous Year'),
|
||||
],
|
||||
default='none',
|
||||
)
|
||||
|
||||
company_id = fields.Many2one(
|
||||
'res.company',
|
||||
default=lambda self: self.env.company,
|
||||
)
|
||||
|
||||
_unique_company_code = models.Constraint(
|
||||
'UNIQUE(company_id, code)',
|
||||
'Report code must be unique per company.',
|
||||
)
|
||||
@@ -1 +1,3 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_fusion_report_user,fusion.report.user,model_fusion_report,base.group_user,1,0,0,0
|
||||
access_fusion_report_admin,fusion.report.admin,model_fusion_report,fusion_accounting_core.group_fusion_accounting_admin,1,1,1,1
|
||||
|
||||
|
@@ -1,2 +1,3 @@
|
||||
from . import test_services_unit
|
||||
from . import test_currency_conversion
|
||||
from . import test_fusion_report
|
||||
|
||||
44
fusion_accounting_reports/tests/test_fusion_report.py
Normal file
44
fusion_accounting_reports/tests/test_fusion_report.py
Normal file
@@ -0,0 +1,44 @@
|
||||
"""Tests for fusion.report definition model."""
|
||||
|
||||
from odoo.tests.common import TransactionCase, tagged
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestFusionReport(TransactionCase):
|
||||
|
||||
def test_create_minimal(self):
|
||||
report = self.env['fusion.report'].create({
|
||||
'name': 'Test P&L',
|
||||
'code': 'test_pnl_minimal',
|
||||
'report_type': 'pnl',
|
||||
})
|
||||
self.assertEqual(report.name, 'Test P&L')
|
||||
self.assertTrue(report.active)
|
||||
self.assertEqual(report.default_comparison_mode, 'none')
|
||||
|
||||
def test_line_specs_json_roundtrip(self):
|
||||
specs = [
|
||||
{'label': 'Revenue', 'account_type_prefix': 'income_', 'sign': 1},
|
||||
{'label': 'COGS', 'account_type_prefix': 'expense_direct_', 'sign': -1},
|
||||
]
|
||||
report = self.env['fusion.report'].create({
|
||||
'name': 'Test',
|
||||
'code': 'test_json_roundtrip',
|
||||
'report_type': 'pnl',
|
||||
'line_specs': specs,
|
||||
})
|
||||
self.assertEqual(report.line_specs, specs)
|
||||
self.assertEqual(report.line_specs[0]['label'], 'Revenue')
|
||||
|
||||
def test_company_code_uniqueness(self):
|
||||
self.env['fusion.report'].create({
|
||||
'name': 'A',
|
||||
'code': 'dup_code_test',
|
||||
'report_type': 'pnl',
|
||||
})
|
||||
with self.assertRaises(Exception):
|
||||
self.env['fusion.report'].create({
|
||||
'name': 'B',
|
||||
'code': 'dup_code_test',
|
||||
'report_type': 'pnl',
|
||||
})
|
||||
Reference in New Issue
Block a user