import logging _logger = logging.getLogger(__name__) # --------------------------------------------------------------------------- # Enterprise account.report wrappers — all routed through ReportsAdapter. # --------------------------------------------------------------------------- def get_profit_loss(env, params): """Route through ReportsAdapter for tri-mode consistency.""" from ..data_adapters import get_adapter adapter = get_adapter(env, 'reports') return adapter.run_report( ref_id='account_reports.profit_and_loss', date_from=params.get('date_from'), date_to=params.get('date_to'), ) def get_balance_sheet(env, params): """Route through ReportsAdapter for tri-mode consistency.""" from ..data_adapters import get_adapter adapter = get_adapter(env, 'reports') return adapter.run_report( ref_id='account_reports.balance_sheet', date_from=params.get('date_from'), date_to=params.get('date_to'), ) def get_trial_balance(env, params): """Route through ReportsAdapter for tri-mode consistency. In Enterprise mode returns the hierarchical report lines. In Community mode falls back to the adapter's trial_balance() aggregation so the tool continues to return useful data with a compatible shape. """ from ..data_adapters import get_adapter adapter = get_adapter(env, 'reports') result = adapter.run_report( ref_id='account_reports.trial_balance_report', date_from=params.get('date_from'), date_to=params.get('date_to'), ) if isinstance(result, dict) and result.get('error'): rows = adapter.trial_balance( date_to=params.get('date_to'), company_ids=[env.company.id], ) return { 'report_name': 'Trial Balance (Community aggregation)', 'lines': [{ 'name': f"{r['account_code']} {r['account_name']}", 'level': 2, 'columns': [r['debit'], r['credit'], r['balance']], } for r in rows], } return result def get_cash_flow(env, params): """Route through ReportsAdapter for tri-mode consistency.""" from ..data_adapters import get_adapter adapter = get_adapter(env, 'reports') return adapter.run_report( ref_id='account_reports.cash_flow_statement', date_from=params.get('date_from'), date_to=params.get('date_to'), ) def compare_periods(env, params): """Run the same report over two periods and return both results. Routes both runs through ReportsAdapter.""" from ..data_adapters import get_adapter adapter = get_adapter(env, 'reports') report_ref = params.get('report_ref', 'account_reports.profit_and_loss') period1 = adapter.run_report( ref_id=report_ref, date_from=params.get('period1_from'), date_to=params.get('period1_to'), ) period2 = adapter.run_report( ref_id=report_ref, date_from=params.get('period2_from'), date_to=params.get('period2_to'), ) return {'period_1': period1, 'period_2': period2} def answer_financial_question(env, params): question = params.get('question', '') sql_query = params.get('sql_query') if sql_query: return {'error': 'Direct SQL not permitted. Use report tools instead.'} return {'status': 'info', 'message': f'Use specific report tools to answer: {question}'} def export_report(env, params): """Route through ReportsAdapter for tri-mode consistency.""" from ..data_adapters import get_adapter adapter = get_adapter(env, 'reports') return adapter.export_report( ref_id=params.get('report_ref', 'account_reports.profit_and_loss'), fmt=params.get('format', 'pdf'), date_from=params.get('date_from'), date_to=params.get('date_to'), ) # --------------------------------------------------------------------------- # Pure-Community tools — search account.move / account.payment directly. # These are tri-mode safe (the data lives in the same tables regardless of # install profile) so they don't need adapter routing. # --------------------------------------------------------------------------- def get_invoicing_summary(env, params): """Get invoicing summary — total invoiced by month, by partner, or for a date range. Supports: monthly breakdown for a year, current month totals, or filtered by partner.""" from datetime import date import calendar year = int(params.get('year', date.today().year)) partner_name = params.get('partner_name') date_from = params.get('date_from') date_to = params.get('date_to') domain = [ ('move_type', '=', 'out_invoice'), ('state', '=', 'posted'), ('company_id', '=', env.company.id), ] if partner_name: partner = env['res.partner'].search([('name', 'ilike', partner_name)], limit=1) if partner: domain.append(('partner_id', '=', partner.id)) else: return {'error': f'Partner not found: {partner_name}'} if date_from and date_to: domain += [('date', '>=', date_from), ('date', '<=', date_to)] invoices = env['account.move'].search(domain, order='date desc') total = sum(inv.amount_total for inv in invoices) return { 'period': f'{date_from} to {date_to}', 'count': len(invoices), 'total': total, 'invoices': [{ 'id': inv.id, 'name': inv.name, 'partner': inv.partner_id.name, 'date': str(inv.date), 'amount': inv.amount_total, 'payment_state': inv.payment_state, } for inv in invoices[:30]], } months = [] grand_total = 0 for month in range(1, 13): m_start = f'{year}-{month:02d}-01' last_day = calendar.monthrange(year, month)[1] m_end = f'{year}-{month:02d}-{last_day}' m_domain = domain + [('date', '>=', m_start), ('date', '<=', m_end)] invoices = env['account.move'].search(m_domain) total = sum(inv.amount_total for inv in invoices) grand_total += total months.append({ 'month': f'{year}-{month:02d}', 'month_name': calendar.month_name[month], 'count': len(invoices), 'total': round(total, 2), }) return { 'year': year, 'grand_total': round(grand_total, 2), 'months': months, 'partner': partner_name or 'All', } def get_billing_summary(env, params): """Get billing (vendor bills) summary — total billed by month or date range.""" from datetime import date import calendar year = int(params.get('year', date.today().year)) partner_name = params.get('partner_name') date_from = params.get('date_from') date_to = params.get('date_to') domain = [ ('move_type', '=', 'in_invoice'), ('state', '=', 'posted'), ('company_id', '=', env.company.id), ] if partner_name: partner = env['res.partner'].search([('name', 'ilike', partner_name)], limit=1) if partner: domain.append(('partner_id', '=', partner.id)) else: return {'error': f'Partner not found: {partner_name}'} if date_from and date_to: domain += [('date', '>=', date_from), ('date', '<=', date_to)] bills = env['account.move'].search(domain, order='date desc') total = sum(b.amount_total for b in bills) return { 'period': f'{date_from} to {date_to}', 'count': len(bills), 'total': total, 'bills': [{ 'id': b.id, 'name': b.name, 'partner': b.partner_id.name, 'date': str(b.date), 'amount': b.amount_total, 'payment_state': b.payment_state, } for b in bills[:30]], } months = [] grand_total = 0 for month in range(1, 13): m_start = f'{year}-{month:02d}-01' last_day = calendar.monthrange(year, month)[1] m_end = f'{year}-{month:02d}-{last_day}' m_domain = domain + [('date', '>=', m_start), ('date', '<=', m_end)] bills = env['account.move'].search(m_domain) total = sum(b.amount_total for b in bills) grand_total += total months.append({ 'month': f'{year}-{month:02d}', 'month_name': calendar.month_name[month], 'count': len(bills), 'total': round(total, 2), }) return { 'year': year, 'grand_total': round(grand_total, 2), 'months': months, 'partner': partner_name or 'All', } def get_collections_summary(env, params): """Get payment collections summary — how much was collected (received) in a period.""" date_from = params.get('date_from') date_to = params.get('date_to') if not date_from or not date_to: from datetime import date today = date.today() date_from = date_from or f'{today.year}-{today.month:02d}-01' date_to = date_to or str(today) payments = env['account.payment'].search([ ('payment_type', '=', 'inbound'), ('state', '=', 'posted'), ('date', '>=', date_from), ('date', '<=', date_to), ('company_id', '=', env.company.id), ], order='date desc') total = sum(p.amount for p in payments) by_partner = {} for p in payments: pname = p.partner_id.name if p.partner_id else 'Unknown' by_partner.setdefault(pname, {'count': 0, 'total': 0}) by_partner[pname]['count'] += 1 by_partner[pname]['total'] += p.amount top_partners = sorted(by_partner.items(), key=lambda x: -x[1]['total'])[:15] return { 'period': f'{date_from} to {date_to}', 'total_collected': round(total, 2), 'payment_count': len(payments), 'by_partner': [{'partner': k, 'count': v['count'], 'total': round(v['total'], 2)} for k, v in top_partners], } TOOLS = { 'get_profit_loss': get_profit_loss, 'get_balance_sheet': get_balance_sheet, 'get_trial_balance': get_trial_balance, 'get_cash_flow': get_cash_flow, 'compare_periods': compare_periods, 'answer_financial_question': answer_financial_question, 'export_report': export_report, 'get_invoicing_summary': get_invoicing_summary, 'get_billing_summary': get_billing_summary, 'get_collections_summary': get_collections_summary, }