feat(fusion_accounting_reports): drill_down_resolver service
Pure-Python helper that, given an account_id and a date range, fetches posted account.move.line records and returns a flat list of dicts ready for the drill-down OWL dialog. Used by the engine's drill_down() method. 3 new tests, 35 total passing. 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.2',
|
'version': '19.0.1.0.3',
|
||||||
'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': """
|
||||||
|
|||||||
@@ -3,3 +3,4 @@ from . import account_hierarchy
|
|||||||
from . import totaling
|
from . import totaling
|
||||||
from . import currency_conversion
|
from . import currency_conversion
|
||||||
from . import line_resolver
|
from . import line_resolver
|
||||||
|
from . import drill_down_resolver
|
||||||
|
|||||||
81
fusion_accounting_reports/services/drill_down_resolver.py
Normal file
81
fusion_accounting_reports/services/drill_down_resolver.py
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
"""Drill-down: from a report line to its underlying journal items.
|
||||||
|
|
||||||
|
Given an account_id and a Period, fetches the matching account.move.line
|
||||||
|
records and returns them in a flat list. Used by the OWL drill-down
|
||||||
|
dialog and the engine's drill_down() public API."""
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class DrillDownRow:
|
||||||
|
move_line_id: int
|
||||||
|
move_id: int
|
||||||
|
move_name: str
|
||||||
|
date: date
|
||||||
|
account_code: str
|
||||||
|
account_name: str
|
||||||
|
partner_name: str | None
|
||||||
|
label: str
|
||||||
|
debit: float
|
||||||
|
credit: float
|
||||||
|
balance: float
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return {
|
||||||
|
'move_line_id': self.move_line_id,
|
||||||
|
'move_id': self.move_id,
|
||||||
|
'move_name': self.move_name,
|
||||||
|
'date': str(self.date),
|
||||||
|
'account_code': self.account_code,
|
||||||
|
'account_name': self.account_name,
|
||||||
|
'partner_name': self.partner_name or '',
|
||||||
|
'label': self.label,
|
||||||
|
'debit': self.debit,
|
||||||
|
'credit': self.credit,
|
||||||
|
'balance': self.balance,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_drill_down(
|
||||||
|
env,
|
||||||
|
*,
|
||||||
|
account_id: int,
|
||||||
|
date_from: date,
|
||||||
|
date_to: date,
|
||||||
|
company_id: int | None = None,
|
||||||
|
limit: int = 500,
|
||||||
|
) -> list[dict]:
|
||||||
|
"""Fetch journal items for an account within a date range.
|
||||||
|
|
||||||
|
Returns flat list of dicts ready for the drill-down OWL table."""
|
||||||
|
Line = env['account.move.line'].sudo()
|
||||||
|
domain = [
|
||||||
|
('account_id', '=', account_id),
|
||||||
|
('date', '>=', date_from),
|
||||||
|
('date', '<=', date_to),
|
||||||
|
('parent_state', '=', 'posted'),
|
||||||
|
]
|
||||||
|
if company_id:
|
||||||
|
domain.append(('company_id', '=', company_id))
|
||||||
|
|
||||||
|
move_lines = Line.search(domain, limit=limit, order='date asc, id asc')
|
||||||
|
rows = []
|
||||||
|
for ml in move_lines:
|
||||||
|
rows.append(
|
||||||
|
DrillDownRow(
|
||||||
|
move_line_id=ml.id,
|
||||||
|
move_id=ml.move_id.id,
|
||||||
|
move_name=ml.move_id.name or '',
|
||||||
|
date=ml.date,
|
||||||
|
account_code=ml.account_id.code,
|
||||||
|
account_name=ml.account_id.name,
|
||||||
|
partner_name=ml.partner_id.name if ml.partner_id else None,
|
||||||
|
label=ml.name or '',
|
||||||
|
debit=ml.debit,
|
||||||
|
credit=ml.credit,
|
||||||
|
balance=ml.balance,
|
||||||
|
).to_dict()
|
||||||
|
)
|
||||||
|
return rows
|
||||||
@@ -2,3 +2,4 @@ from . import test_services_unit
|
|||||||
from . import test_currency_conversion
|
from . import test_currency_conversion
|
||||||
from . import test_fusion_report
|
from . import test_fusion_report
|
||||||
from . import test_line_resolver
|
from . import test_line_resolver
|
||||||
|
from . import test_drill_down_resolver
|
||||||
|
|||||||
60
fusion_accounting_reports/tests/test_drill_down_resolver.py
Normal file
60
fusion_accounting_reports/tests/test_drill_down_resolver.py
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
"""Tests for drill_down_resolver."""
|
||||||
|
|
||||||
|
from datetime import date, timedelta
|
||||||
|
|
||||||
|
from odoo.tests.common import TransactionCase, tagged
|
||||||
|
from odoo.addons.fusion_accounting_reports.services.drill_down_resolver import (
|
||||||
|
fetch_drill_down,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@tagged('post_install', '-at_install')
|
||||||
|
class TestDrillDownResolver(TransactionCase):
|
||||||
|
|
||||||
|
def test_returns_empty_for_account_with_no_lines(self):
|
||||||
|
account = self.env['account.account'].search([
|
||||||
|
('company_ids', 'in', self.env.company.id),
|
||||||
|
], limit=1)
|
||||||
|
if not account:
|
||||||
|
self.skipTest("No accounts in DB")
|
||||||
|
rows = fetch_drill_down(
|
||||||
|
self.env,
|
||||||
|
account_id=account.id,
|
||||||
|
date_from=date(2099, 1, 1),
|
||||||
|
date_to=date(2099, 12, 31),
|
||||||
|
company_id=self.env.company.id,
|
||||||
|
)
|
||||||
|
self.assertEqual(rows, [])
|
||||||
|
|
||||||
|
def test_returns_lines_for_account_with_data(self):
|
||||||
|
line = self.env['account.move.line'].search([
|
||||||
|
('parent_state', '=', 'posted'),
|
||||||
|
], limit=1)
|
||||||
|
if not line:
|
||||||
|
self.skipTest("No posted move lines in DB")
|
||||||
|
rows = fetch_drill_down(
|
||||||
|
self.env,
|
||||||
|
account_id=line.account_id.id,
|
||||||
|
date_from=line.date - timedelta(days=1),
|
||||||
|
date_to=line.date + timedelta(days=1),
|
||||||
|
company_id=line.company_id.id,
|
||||||
|
)
|
||||||
|
self.assertGreater(len(rows), 0)
|
||||||
|
ids = [r['move_line_id'] for r in rows]
|
||||||
|
self.assertIn(line.id, ids)
|
||||||
|
|
||||||
|
def test_respects_limit(self):
|
||||||
|
line = self.env['account.move.line'].search([
|
||||||
|
('parent_state', '=', 'posted'),
|
||||||
|
], limit=1)
|
||||||
|
if not line:
|
||||||
|
self.skipTest("No posted move lines in DB")
|
||||||
|
rows = fetch_drill_down(
|
||||||
|
self.env,
|
||||||
|
account_id=line.account_id.id,
|
||||||
|
date_from=date(2000, 1, 1),
|
||||||
|
date_to=date(2099, 12, 31),
|
||||||
|
company_id=line.company_id.id,
|
||||||
|
limit=2,
|
||||||
|
)
|
||||||
|
self.assertLessEqual(len(rows), 2)
|
||||||
Reference in New Issue
Block a user