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',
|
||||
'version': '19.0.1.0.11',
|
||||
'version': '19.0.1.0.12',
|
||||
'category': 'Accounting/Accounting',
|
||||
'summary': 'AI-augmented financial reports (P&L, balance sheet, trial balance, GL).',
|
||||
'description': """
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
from . import fusion_report
|
||||
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
|
||||
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_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_commentary_prompt
|
||||
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