from __future__ import annotations import logging from ..tools.odoo_client import OdooClient logger = logging.getLogger(__name__) class FinanceTools: def __init__(self, odoo: OdooClient): self._odoo = odoo async def get_invoices(self, state='all', partner_id=None, date_from=None, date_to=None, move_type='all', limit=50): domain = [] if move_type != 'all': domain.append(['move_type', '=', move_type]) else: domain.append(['move_type', 'in', ['out_invoice', 'in_invoice', 'out_refund', 'in_refund']]) if state != 'all': domain.append(['state', '=', state]) if partner_id: domain.append(['partner_id', '=', partner_id]) if date_from: domain.append(['invoice_date', '>=', date_from]) if date_to: domain.append(['invoice_date', '<=', date_to]) fields = ['name', 'move_type', 'state', 'partner_id', 'amount_total', 'amount_residual', 'payment_state', 'invoice_date', 'invoice_date_due'] return await self._odoo.search_read('account.move', domain, fields, limit=limit, order='invoice_date_due desc') async def get_overdue_invoices(self, partner_id=None, min_days_overdue=1): from datetime import date, timedelta cutoff = (date.today() - timedelta(days=min_days_overdue)).isoformat() domain = [ ['move_type', 'in', ['out_invoice', 'out_refund']], ['state', '=', 'posted'], ['payment_state', 'in', ['not_paid', 'partial']], ['invoice_date_due', '<', cutoff], ] if partner_id: domain.append(['partner_id', '=', partner_id]) fields = ['name', 'partner_id', 'amount_total', 'amount_residual', 'invoice_date_due', 'payment_state'] return await self._odoo.search_read('account.move', domain, fields, order='invoice_date_due asc') async def get_unreconciled_statements(self, journal_id, date_from=None, date_to=None): domain = [['journal_id', '=', journal_id], ['is_reconciled', '=', False]] if date_from: domain.append(['date', '>=', date_from]) if date_to: domain.append(['date', '<=', date_to]) fields = ['date', 'payment_ref', 'amount', 'partner_id', 'is_reconciled', 'move_id'] return await self._odoo.search_read('account.bank.statement.line', domain, fields, order='date asc') async def match_statement_line(self, statement_line_id, move_id): result = await self._odoo.call( 'account.bank.statement.line', 'reconcile', [[statement_line_id]], {'lines_vals': [{'id': move_id}]}) return result async def send_payment_reminder(self, invoice_id, custom_message=None): invoices = await self._odoo.read('account.move', [invoice_id], ['name', 'partner_id', 'amount_residual', 'invoice_date_due']) if not invoices: return {'success': False, 'error': 'Invoice not found'} inv = invoices[0] body = custom_message or ( f'Reminder: Invoice {inv["name"]} for {inv["amount_residual"]} ' f'was due on {inv["invoice_date_due"]}. Please arrange payment.') msg_id = await self._odoo.post_chatter('account.move', invoice_id, body, subtype='mail.mt_comment') return {'success': True, 'message_id': msg_id, 'invoice': inv['name']} async def get_financial_summary(self, period='current'): from datetime import date if period == 'current': today = date.today() date_from = today.replace(day=1).isoformat() date_to = today.isoformat() else: year, month = period.split('-') import calendar last_day = calendar.monthrange(int(year), int(month))[1] date_from = f'{year}-{month}-01' date_to = f'{year}-{month}-{last_day:02d}' invoiced = await self._odoo.search_read( 'account.move', [['move_type', '=', 'out_invoice'], ['state', '=', 'posted'], ['invoice_date', '>=', date_from], ['invoice_date', '<=', date_to]], ['amount_total', 'amount_residual', 'payment_state']) total_invoiced = sum(i['amount_total'] for i in invoiced) total_outstanding = sum(i['amount_residual'] for i in invoiced) paid = sum(1 for i in invoiced if i['payment_state'] == 'paid') return { 'period': period, 'date_from': date_from, 'date_to': date_to, 'total_invoiced': total_invoiced, 'total_outstanding': total_outstanding, 'total_paid': total_invoiced - total_outstanding, 'invoice_count': len(invoiced), 'paid_count': paid, 'collection_rate': round((1 - total_outstanding/total_invoiced)*100, 1) if total_invoiced else 0, } async def get_payment_history(self, partner_id): fields = ['name', 'payment_type', 'amount', 'state', 'date', 'journal_id', 'ref'] return await self._odoo.search_read( 'account.payment', [['partner_id', '=', partner_id], ['state', '=', 'posted']], fields, limit=50, order='date desc') async def flag_for_review(self, model, record_id, reason, severity='medium'): note = f'[REVIEW FLAGGED - {severity.upper()}] {reason}' await self._odoo.post_chatter(model, record_id, note) return {'flagged': True, 'model': model, 'record_id': record_id, 'severity': severity} async def post_chatter_note(self, model, record_id, note): msg_id = await self._odoo.post_chatter(model, record_id, note) return {'success': True, 'message_id': msg_id}