from __future__ import annotations import logging from .base_agent import BaseAgent, AgentReport, AgentDirective, SweepReport from ..tools.accounting_tools import AccountingTools logger = logging.getLogger(__name__) ACCOUNTING_TOOLS = [ {'name': 'get_journal_entries', 'description': 'Retrieve journal entries', 'parameters': {'journal_id': {'type': 'integer', 'optional': True}, 'date_from': {'type': 'string', 'optional': True}, 'date_to': {'type': 'string', 'optional': True}, 'state': {'type': 'string', 'optional': True}, 'limit': {'type': 'integer', 'optional': True}}}, {'name': 'get_chart_of_accounts', 'description': 'Get chart of accounts', 'parameters': {'account_type': {'type': 'string', 'optional': True}, 'limit': {'type': 'integer', 'optional': True}}}, {'name': 'get_account_balance', 'description': 'Get balance for a specific account', 'parameters': {'account_id': {'type': 'integer'}}}, {'name': 'get_trial_balance', 'description': 'Get trial balance for a period', 'parameters': {'date_from': {'type': 'string', 'optional': True}, 'date_to': {'type': 'string', 'optional': True}}}, {'name': 'get_tax_summary', 'description': 'Get tax summary for a period', 'parameters': {'date_from': {'type': 'string', 'optional': True}, 'date_to': {'type': 'string', 'optional': True}}}, {'name': 'flag_for_review', 'description': 'Flag a journal entry for review', 'parameters': {'model': {'type': 'string'}, 'record_id': {'type': 'integer'}, 'reason': {'type': 'string'}, 'severity': {'type': 'string', 'optional': True}}}, {'name': 'post_chatter_note', 'description': 'Post a note on a record', 'parameters': {'model': {'type': 'string'}, 'record_id': {'type': 'integer'}, 'note': {'type': 'string'}}}, ] class AccountingAgent(BaseAgent): name = 'accounting_agent' domain = 'accounting' required_odoo_module = 'account' system_prompt_file = 'accounting_system.txt' tools = ACCOUNTING_TOOLS def __init__(self, odoo, llm, peer_bus=None): super().__init__(odoo, llm, peer_bus) self._at = AccountingTools(odoo) self._gathered_data = {} self._actions_taken = [] self._escalations_list = [] async def _plan(self, directive: AgentDirective) -> dict: intent = (directive.intent or '').lower() return { 'fetch_trial_balance': any(k in intent for k in ('trial', 'balance', 'report')), 'fetch_tax': any(k in intent for k in ('tax', 'vat', 'gst')), 'fetch_entries': any(k in intent for k in ('journal', 'entry', 'entries')), 'date_from': directive.context.get('date_from'), 'date_to': directive.context.get('date_to'), } async def _gather(self, ctx: dict) -> dict: plan = ctx.get('plan', {}) data: dict = {} if plan.get('fetch_trial_balance'): data['trial_balance'] = await self._at.get_trial_balance( date_from=plan.get('date_from'), date_to=plan.get('date_to'), ) if plan.get('fetch_tax'): data['tax_summary'] = await self._at.get_tax_summary( date_from=plan.get('date_from'), date_to=plan.get('date_to'), ) if plan.get('fetch_entries') or not data: data['entries'] = await self._at.get_journal_entries(limit=20) self._gathered_data = data return data async def _reason(self, ctx: dict) -> dict: data = self._gathered_data analysis: dict = {'flags': [], 'escalations': []} trial = data.get('trial_balance', []) for account in trial: bal = account.get('balance', 0) if abs(bal) > 100000: analysis['flags'].append({'account': account.get('account_name'), 'balance': bal}) self._escalations_list = analysis.get('escalations', []) return analysis async def _act(self, ctx: dict) -> list: return [] async def _report(self, ctx: dict) -> AgentReport: data = self._gathered_data parts = [] trial = data.get('trial_balance', []) if trial: parts.append(f'Trial balance: {len(trial)} accounts.') tax = data.get('tax_summary', {}) if tax: parts.append(f'Tax: {tax.get("total_tax_amount", 0):.2f} in {tax.get("total_tax_lines", 0)} lines.') if not parts: parts.append('Accounting review complete.') return AgentReport( agent=self.name, summary=chr(10).join(parts), data=data, escalations=self._escalations_list, actions_taken=[], ) async def _dispatch_tool(self, name: str, args: dict): if name == 'get_journal_entries': return await self._at.get_journal_entries(**args) if name == 'get_chart_of_accounts': return await self._at.get_chart_of_accounts(**args) if name == 'get_account_balance': return await self._at.get_account_balance(**args) if name == 'get_trial_balance': return await self._at.get_trial_balance(**args) if name == 'get_tax_summary': return await self._at.get_tax_summary(**args) if name == 'flag_for_review': return await self._at.flag_for_review(**args) if name == 'post_chatter_note': return await self._at.post_chatter_note(**args) raise ValueError(f'Unknown tool: {name}') async def handle_peer_request(self, request_type: str, params: dict, directive_id: str) -> dict: try: if request_type == 'trial_balance': return {'trial_balance': await self._at.get_trial_balance()} if request_type == 'account_balance': return await self._at.get_account_balance(account_id=params['account_id']) if request_type == 'tax_summary': return await self._at.get_tax_summary() return {'error': f'Unknown type: {request_type}'} except Exception as exc: return {'error': str(exc)} async def sweep(self) -> SweepReport: findings = [] try: trial = await self._at.get_trial_balance() for account in trial: if abs(account.get('balance', 0)) > 500000: findings.append({'type': 'large_balance', 'account': account.get('account_name'), 'balance': account.get('balance', 0), 'severity': 'high'}) except Exception as exc: return SweepReport(agent=self.name, findings=[], actions=[], error=str(exc)) return SweepReport(agent=self.name, findings=findings, actions=[], summary=f'Sweep: {len(findings)} large balance accounts found.')