diff --git a/fusion_accounting_reports/__init__.py b/fusion_accounting_reports/__init__.py index 70f95eae..36233572 100644 --- a/fusion_accounting_reports/__init__.py +++ b/fusion_accounting_reports/__init__.py @@ -1,3 +1,4 @@ from . import services from . import models from . import controllers +from . import reports diff --git a/fusion_accounting_reports/__manifest__.py b/fusion_accounting_reports/__manifest__.py index 5a3082fe..119c298d 100644 --- a/fusion_accounting_reports/__manifest__.py +++ b/fusion_accounting_reports/__manifest__.py @@ -1,6 +1,6 @@ { 'name': 'Fusion Accounting Reports', - 'version': '19.0.1.0.29', + 'version': '19.0.1.0.30', 'category': 'Accounting/Accounting', 'summary': 'AI-augmented financial reports (P&L, balance sheet, trial balance, GL).', 'description': """ @@ -36,6 +36,7 @@ menu hides; the engine and AI tools remain available for the chat. 'data/report_trial_balance.xml', 'data/report_general_ledger.xml', 'data/cron.xml', + 'reports/report_pdf_template.xml', ], 'assets': { 'web.assets_backend': [ diff --git a/fusion_accounting_reports/controllers/reports_controller.py b/fusion_accounting_reports/controllers/reports_controller.py index 0bd3d8e8..fbd278f2 100644 --- a/fusion_accounting_reports/controllers/reports_controller.py +++ b/fusion_accounting_reports/controllers/reports_controller.py @@ -210,9 +210,25 @@ class FusionReportsController(http.Controller): @http.route('/fusion/reports/export_pdf', type='jsonrpc', auth='user') def export_pdf(self, report_type, date_from, date_to, comparison='none', company_id=None): + Report = request.env['fusion.report'] + report_def = Report.search([('report_type', '=', report_type)], limit=1) + if not report_def: + return {'status': 'error', 'message': f'No report definition for {report_type}'} + company_id = int(company_id) if company_id else request.env.company.id + pdf, _ct = request.env['ir.actions.report'].sudo()._render_qweb_pdf( + 'fusion_accounting_reports.report_pdf_template', + res_ids=[report_def.id], + data={ + 'report_type': report_type, + 'date_from': date_from, 'date_to': date_to, + 'comparison': comparison, 'company_id': company_id, + }, + ) + import base64 return { - 'status': 'not_implemented', - 'message': 'PDF export shipping in Task 34', + 'status': 'ok', + 'pdf_base64': base64.b64encode(pdf).decode('ascii'), + 'filename': f'{report_type}_{date_from}_{date_to}.pdf', } @http.route('/fusion/reports/export_xlsx', type='jsonrpc', auth='user') diff --git a/fusion_accounting_reports/reports/__init__.py b/fusion_accounting_reports/reports/__init__.py index e69de29b..c9c02fc7 100644 --- a/fusion_accounting_reports/reports/__init__.py +++ b/fusion_accounting_reports/reports/__init__.py @@ -0,0 +1 @@ +from . import report_pdf diff --git a/fusion_accounting_reports/reports/report_pdf.py b/fusion_accounting_reports/reports/report_pdf.py new file mode 100644 index 00000000..9c447fe6 --- /dev/null +++ b/fusion_accounting_reports/reports/report_pdf.py @@ -0,0 +1,58 @@ +"""QWeb PDF report for fusion financial reports. + +Wraps the engine's compute_* methods and feeds the result into a +single multi-purpose template that handles all 4 report types.""" + +from datetime import datetime + +from odoo import api, models + +from ..services.date_periods import Period + + +class FusionReportPdf(models.AbstractModel): + _name = "report.fusion_accounting_reports.report_pdf_template" + _description = "Fusion Financial Report PDF" + + @api.model + def _get_report_values(self, docids, data=None): + """data is expected to be {report_type, date_from, date_to, comparison, company_id}.""" + data = data or {} + report_type = data.get('report_type', 'pnl') + company_id = data.get('company_id') or self.env.company.id + date_from = data.get('date_from') + date_to = data.get('date_to') + comparison = data.get('comparison', 'none') + + if isinstance(date_from, str): + date_from = datetime.strptime(date_from, '%Y-%m-%d').date() + if isinstance(date_to, str): + date_to = datetime.strptime(date_to, '%Y-%m-%d').date() + + engine = self.env['fusion.report.engine'] + if report_type == 'pnl': + period = Period(date_from, date_to, f"{date_from} - {date_to}") + result = engine.compute_pnl(period, comparison=comparison, company_id=company_id) + elif report_type == 'balance_sheet': + result = engine.compute_balance_sheet(date_to, comparison=comparison, company_id=company_id) + elif report_type == 'trial_balance': + period = Period(date_from, date_to, f"{date_from} - {date_to}") + result = engine.compute_trial_balance(period, company_id=company_id) + elif report_type == 'general_ledger': + period = Period(date_from, date_to, f"{date_from} - {date_to}") + result = engine.compute_gl(period, company_id=company_id) + else: + result = {'rows': [], 'report_name': 'Unknown', 'period': {}} + + company = self.env['res.company'].browse(company_id) + return { + 'doc_ids': docids, + 'doc_model': 'fusion.report', + 'docs': self.env['fusion.report'].browse(docids) if docids else + self.env['fusion.report'].search([('report_type', '=', report_type)], limit=1), + 'data': data, + 'result': result, + 'company_id': company, + 'company': company, + 'res_company': company, + } diff --git a/fusion_accounting_reports/reports/report_pdf_template.xml b/fusion_accounting_reports/reports/report_pdf_template.xml new file mode 100644 index 00000000..fffd326c --- /dev/null +++ b/fusion_accounting_reports/reports/report_pdf_template.xml @@ -0,0 +1,72 @@ + + + + + + Fusion Financial Report (PDF) + fusion.report + qweb-pdf + fusion_accounting_reports.report_pdf_template + fusion_accounting_reports.report_pdf_template + + form,list + + diff --git a/fusion_accounting_reports/tests/__init__.py b/fusion_accounting_reports/tests/__init__.py index 86b1c345..78b7a9a6 100644 --- a/fusion_accounting_reports/tests/__init__.py +++ b/fusion_accounting_reports/tests/__init__.py @@ -18,3 +18,4 @@ from . import test_pnl_integration from . import test_bs_tb_integration from . import test_account_balance_mv from . import test_cron +from . import test_pdf_export diff --git a/fusion_accounting_reports/tests/test_pdf_export.py b/fusion_accounting_reports/tests/test_pdf_export.py new file mode 100644 index 00000000..47b93cbc --- /dev/null +++ b/fusion_accounting_reports/tests/test_pdf_export.py @@ -0,0 +1,34 @@ +"""Tests for the PDF export.""" + +from odoo.tests.common import TransactionCase, tagged + + +@tagged('post_install', '-at_install') +class TestPdfExport(TransactionCase): + + def test_pdf_render_pnl(self): + report = self.env.ref('fusion_accounting_reports.report_pnl') + pdf, content_type = self.env['ir.actions.report'].sudo()._render_qweb_pdf( + 'fusion_accounting_reports.report_pdf_template', + res_ids=[report.id], + data={ + 'report_type': 'pnl', + 'date_from': '2026-01-01', 'date_to': '2026-12-31', + 'company_id': self.env.company.id, + }, + ) + self.assertGreater(len(pdf), 500) + self.assertIn(content_type, ('pdf', 'html')) + + def test_pdf_render_balance_sheet(self): + report = self.env.ref('fusion_accounting_reports.report_balance_sheet') + pdf, _ = self.env['ir.actions.report'].sudo()._render_qweb_pdf( + 'fusion_accounting_reports.report_pdf_template', + res_ids=[report.id], + data={ + 'report_type': 'balance_sheet', + 'date_from': '2026-01-01', 'date_to': '2026-12-31', + 'company_id': self.env.company.id, + }, + ) + self.assertGreater(len(pdf), 500) diff --git a/fusion_accounting_reports/tests/test_reports_controller.py b/fusion_accounting_reports/tests/test_reports_controller.py index 19f51bcd..49023b61 100644 --- a/fusion_accounting_reports/tests/test_reports_controller.py +++ b/fusion_accounting_reports/tests/test_reports_controller.py @@ -101,13 +101,15 @@ class TestReportsController(HttpCase): self.assertIn('highlights', result) self.assertIn('concerns', result) - def test_export_pdf_placeholder(self): + def test_export_pdf_returns_pdf(self): result = self._jsonrpc('export_pdf', { 'report_type': 'pnl', 'date_from': '2026-01-01', 'date_to': '2026-12-31', }) - self.assertEqual(result.get('status'), 'not_implemented') + self.assertEqual(result.get('status'), 'ok') + self.assertIn('pdf_base64', result) + self.assertTrue(result.get('filename', '').endswith('.pdf')) def test_export_xlsx_placeholder(self): result = self._jsonrpc('export_xlsx', {