feat(fusion_accounting_reports): fusion.report.commentary cache model
Made-with: Cursor
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
'name': 'Fusion Accounting Reports',
|
'name': 'Fusion Accounting Reports',
|
||||||
'version': '19.0.1.0.11',
|
'version': '19.0.1.0.12',
|
||||||
'category': 'Accounting/Accounting',
|
'category': 'Accounting/Accounting',
|
||||||
'summary': 'AI-augmented financial reports (P&L, balance sheet, trial balance, GL).',
|
'summary': 'AI-augmented financial reports (P&L, balance sheet, trial balance, GL).',
|
||||||
'description': """
|
'description': """
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
from . import fusion_report
|
from . import fusion_report
|
||||||
from . import fusion_report_engine
|
from . import fusion_report_engine
|
||||||
|
from . import fusion_report_commentary
|
||||||
|
|||||||
43
fusion_accounting_reports/models/fusion_report_commentary.py
Normal file
43
fusion_accounting_reports/models/fusion_report_commentary.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
"""Cached AI-generated commentary for a report run.
|
||||||
|
|
||||||
|
One row per (report, period_from, period_to, comparison_mode, company).
|
||||||
|
Refreshed on demand or via cron when the underlying data has changed."""
|
||||||
|
|
||||||
|
from odoo import _, api, fields, models
|
||||||
|
|
||||||
|
|
||||||
|
class FusionReportCommentary(models.Model):
|
||||||
|
_name = "fusion.report.commentary"
|
||||||
|
_description = "AI-Generated Report Commentary Cache"
|
||||||
|
_order = "generated_at desc"
|
||||||
|
|
||||||
|
report_id = fields.Many2one('fusion.report', required=True, ondelete='cascade')
|
||||||
|
company_id = fields.Many2one('res.company', required=True,
|
||||||
|
default=lambda self: self.env.company)
|
||||||
|
period_from = fields.Date(required=True)
|
||||||
|
period_to = fields.Date(required=True)
|
||||||
|
comparison_mode = fields.Selection([
|
||||||
|
('none', 'None'),
|
||||||
|
('previous_period', 'Previous Period'),
|
||||||
|
('previous_year', 'Previous Year'),
|
||||||
|
], default='none', required=True)
|
||||||
|
|
||||||
|
summary = fields.Text()
|
||||||
|
highlights = fields.Json() # list of strings
|
||||||
|
concerns = fields.Json() # list of strings
|
||||||
|
next_actions = fields.Json() # list of strings
|
||||||
|
|
||||||
|
generated_at = fields.Datetime(default=fields.Datetime.now, required=True)
|
||||||
|
generated_by = fields.Selection([
|
||||||
|
('on_demand', 'On Demand'),
|
||||||
|
('cron', 'Cron'),
|
||||||
|
('templated', 'Templated Fallback'),
|
||||||
|
], default='on_demand', required=True)
|
||||||
|
|
||||||
|
provider = fields.Char(help="LLM provider used (e.g. 'openai', 'claude', 'local'). "
|
||||||
|
"Empty for templated fallback.")
|
||||||
|
|
||||||
|
_unique_period = models.Constraint(
|
||||||
|
'UNIQUE(report_id, company_id, period_from, period_to, comparison_mode)',
|
||||||
|
'Only one commentary cache row per report+period+mode.',
|
||||||
|
)
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
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_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
|
access_fusion_report_admin,fusion.report.admin,model_fusion_report,fusion_accounting_core.group_fusion_accounting_admin,1,1,1,1
|
||||||
|
access_fusion_report_commentary,fusion.report.commentary,model_fusion_report_commentary,base.group_user,1,1,1,0
|
||||||
|
|||||||
|
@@ -8,3 +8,4 @@ from . import test_seeded_reports
|
|||||||
from . import test_anomaly_detection
|
from . import test_anomaly_detection
|
||||||
from . import test_commentary_prompt
|
from . import test_commentary_prompt
|
||||||
from . import test_commentary_generator
|
from . import test_commentary_generator
|
||||||
|
from . import test_fusion_report_commentary
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
"""Tests for fusion.report.commentary cache model."""
|
||||||
|
|
||||||
|
from datetime import date
|
||||||
|
from odoo.tests.common import TransactionCase, tagged
|
||||||
|
|
||||||
|
|
||||||
|
@tagged('post_install', '-at_install')
|
||||||
|
class TestFusionReportCommentary(TransactionCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
self.report = self.env.ref('fusion_accounting_reports.report_pnl')
|
||||||
|
|
||||||
|
def test_create_minimal(self):
|
||||||
|
c = self.env['fusion.report.commentary'].create({
|
||||||
|
'report_id': self.report.id,
|
||||||
|
'period_from': date(2026, 4, 1),
|
||||||
|
'period_to': date(2026, 4, 30),
|
||||||
|
'summary': 'Test summary.',
|
||||||
|
'highlights': ['point 1', 'point 2'],
|
||||||
|
})
|
||||||
|
self.assertEqual(c.summary, 'Test summary.')
|
||||||
|
self.assertEqual(c.highlights, ['point 1', 'point 2'])
|
||||||
|
self.assertEqual(c.generated_by, 'on_demand')
|
||||||
|
|
||||||
|
def test_uniqueness_per_period(self):
|
||||||
|
self.env['fusion.report.commentary'].create({
|
||||||
|
'report_id': self.report.id,
|
||||||
|
'period_from': date(2026, 4, 1),
|
||||||
|
'period_to': date(2026, 4, 30),
|
||||||
|
'comparison_mode': 'none',
|
||||||
|
})
|
||||||
|
with self.assertRaises(Exception):
|
||||||
|
self.env['fusion.report.commentary'].create({
|
||||||
|
'report_id': self.report.id,
|
||||||
|
'period_from': date(2026, 4, 1),
|
||||||
|
'period_to': date(2026, 4, 30),
|
||||||
|
'comparison_mode': 'none',
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_different_comparison_modes_can_coexist(self):
|
||||||
|
for mode in ['none', 'previous_period', 'previous_year']:
|
||||||
|
self.env['fusion.report.commentary'].create({
|
||||||
|
'report_id': self.report.id,
|
||||||
|
'period_from': date(2026, 5, 1),
|
||||||
|
'period_to': date(2026, 5, 31),
|
||||||
|
'comparison_mode': mode,
|
||||||
|
})
|
||||||
|
count = self.env['fusion.report.commentary'].search_count([
|
||||||
|
('report_id', '=', self.report.id),
|
||||||
|
('period_from', '=', date(2026, 5, 1)),
|
||||||
|
])
|
||||||
|
self.assertEqual(count, 3)
|
||||||
Reference in New Issue
Block a user