import json import logging from odoo import models, fields, api _logger = logging.getLogger(__name__) TOOL_LABELS = { 'get_unreconciled_bank_lines': 'Get Unreconciled Bank Lines', 'get_unreconciled_receipts': 'Get Unreconciled Receipts', 'match_bank_line_to_payments': 'Match Bank Line to Payments', 'auto_reconcile_bank_lines': 'Auto-Reconcile Bank Lines', 'apply_reconcile_model': 'Apply Reconcile Model', 'unmatch_bank_line': 'Unmatch Bank Line', 'get_reconcile_suggestions': 'Get Reconcile Suggestions', 'sum_payments_by_date': 'Sum Payments by Date', 'get_bank_line_details': 'Get Bank Line Details', 'check_recurring_pattern': 'Check Recurring Pattern', 'match_internal_transfers': 'Match Internal Transfers', 'find_unreconciled_cheques': 'Find Unreconciled Cheques', 'reconcile_payroll_cheques': 'Reconcile Payroll Cheques', 'suggest_bank_line_matches': 'Suggest Bank Line Matches', 'search_matching_entries': 'Search Matching Entries', 'calculate_hst_balance': 'Calculate HST Balance', 'create_expense_entry': 'Create Expense Entry', 'find_missing_itc_bills': 'Find Missing ITC Bills', 'find_missing_tax_invoices': 'Find Missing Tax Invoices', 'get_tax_report': 'Get Tax Report', 'get_ar_aging': 'Get AR Aging', 'get_overdue_invoices': 'Get Overdue Invoices', 'get_partner_balance': 'Get Partner Balance', 'get_ap_aging': 'Get AP Aging', 'get_unpaid_bills': 'Get Unpaid Bills', 'find_duplicate_bills': 'Find Duplicate Bills', 'create_vendor_bill': 'Create Vendor Bill', 'register_bill_payment': 'Register Bill Payment', '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', 'get_invoicing_summary': 'Get Invoicing Summary', 'get_billing_summary': 'Get Billing Summary', 'get_collections_summary': 'Get Collections Summary', 'create_payroll_journal_entry': 'Create Payroll Journal Entry', 'find_adp_without_payment': 'Find ADP Without Payment', 'get_adp_receivable_aging': 'Get ADP Receivable Aging', 'register_adp_batch_payment': 'Register ADP Batch Payment', 'get_close_checklist': 'Get Month-End Checklist', 'find_draft_entries': 'Find Draft Entries', 'find_wrong_direction_balances': 'Find Wrong Direction Balances', 'find_duplicate_entries': 'Find Duplicate Entries', 'get_payroll_entries': 'Get Payroll Entries', 'get_cra_remittance_status': 'Get CRA Remittance Status', } class FusionAccountingMatchHistory(models.Model): _name = 'fusion.accounting.match.history' _description = 'Fusion Accounting Match History' _order = 'proposed_at desc' _rec_name = 'display_label' display_label = fields.Char( string='Label', compute='_compute_display_label', store=True, ) session_id = fields.Many2one( 'fusion.accounting.session', string='Session', index=True, ondelete='cascade', ) tool_name = fields.Char(string='Tool Name', required=True, index=True) tool_display_name = fields.Char( string='Tool', compute='_compute_tool_display_name', store=True, ) tool_params_pretty = fields.Text( string='Parameters', compute='_compute_pretty_json', ) tool_result_pretty = fields.Text( string='Result', compute='_compute_pretty_json', ) tool_params = fields.Text(string='Tool Parameters (JSON)') tool_result = fields.Text(string='Tool Result (JSON)') ai_reasoning = fields.Text(string='AI Reasoning') ai_confidence = fields.Float(string='AI Confidence', digits=(3, 2)) rule_id = fields.Many2one( 'fusion.accounting.rule', string='Applied Rule', ondelete='set null', ) proposed_at = fields.Datetime( string='Proposed At', default=fields.Datetime.now, required=True, ) decision = fields.Selection( selection=[ ('approved', 'Approved'), ('rejected', 'Rejected'), ('pending', 'Pending'), ('auto', 'Auto-Executed'), ], string='Decision', default='pending', index=True, ) decided_at = fields.Datetime(string='Decided At') decided_by = fields.Many2one('res.users', string='Decided By') rejection_reason = fields.Text(string='Rejection Reason') correct_action = fields.Text(string='Correct Action (JSON)') bank_statement_line_id = fields.Many2one( 'account.bank.statement.line', string='Bank Statement Line', ondelete='set null', ) move_line_ids = fields.Many2many( 'account.move.line', string='Journal Items', ) amount = fields.Monetary(string='Amount', currency_field='currency_id') currency_id = fields.Many2one( 'res.currency', string='Currency', default=lambda self: self.env.company.currency_id, ) partner_id = fields.Many2one('res.partner', string='Partner') company_id = fields.Many2one( 'res.company', string='Company', default=lambda self: self.env.company, ) @api.depends('tool_name') def _compute_tool_display_name(self): for rec in self: rec.tool_display_name = TOOL_LABELS.get(rec.tool_name, (rec.tool_name or '').replace('_', ' ').title()) @api.depends('tool_params', 'tool_result') def _compute_pretty_json(self): for rec in self: for src, dst in [('tool_params', 'tool_params_pretty'), ('tool_result', 'tool_result_pretty')]: raw = getattr(rec, src) or '{}' try: parsed = json.loads(raw) setattr(rec, dst, json.dumps(parsed, indent=2, default=str, ensure_ascii=False)) except (json.JSONDecodeError, TypeError): setattr(rec, dst, raw) @api.depends('tool_name', 'proposed_at', 'decision') def _compute_display_label(self): for rec in self: label = TOOL_LABELS.get(rec.tool_name, (rec.tool_name or '').replace('_', ' ').title()) date_str = rec.proposed_at.strftime('%b %d %H:%M') if rec.proposed_at else '' decision_str = dict(rec._fields['decision'].selection).get(rec.decision, '') rec.display_label = f"{label} — {decision_str} ({date_str})" if date_str else label def action_approve(self): self.write({ 'decision': 'approved', 'decided_at': fields.Datetime.now(), 'decided_by': self.env.user.id, }) for rec in self: if rec.rule_id: rec.rule_id._record_decision(approved=True) def action_reject(self): self.write({ 'decision': 'rejected', 'decided_at': fields.Datetime.now(), 'decided_by': self.env.user.id, }) for rec in self: if rec.rule_id: rec.rule_id._record_decision(approved=False)