import logging from odoo import models, fields, api _logger = logging.getLogger(__name__) class FusionAccountingRule(models.Model): _name = 'fusion.accounting.rule' _description = 'Fusion Accounting Rule' _order = 'sequence, id' _inherit = ['mail.thread'] name = fields.Char(string='Name', required=True, tracking=True) rule_type = fields.Selection( selection=[ ('match', 'Match'), ('classify', 'Classify'), ('audit', 'Audit'), ('fee', 'Fee'), ('routing', 'Routing'), ('followup', 'Follow-Up'), ], string='Type', required=True, tracking=True, ) description = fields.Text( string='Description', help='Natural language description read by the AI.', ) trigger_domain = fields.Text( string='Trigger Domain (JSON)', help='Odoo domain filter for matching records.', ) match_logic = fields.Text( string='Match Logic', help='Natural language matching instructions for the AI.', ) match_code = fields.Text( string='Match Code (Python)', help='Optional deterministic Python matching code.', ) fee_account_id = fields.Many2one( 'account.account', string='Fee Account', ) write_off_account_id = fields.Many2one( 'account.account', string='Write-Off Account', ) approval_tier = fields.Selection( selection=[('auto', 'Auto-Approved'), ('needs_approval', 'Needs Approval')], string='Approval Tier', default='needs_approval', tracking=True, ) created_by = fields.Selection( selection=[('admin', 'Admin'), ('ai', 'AI')], string='Created By', default='admin', ) confidence_score = fields.Float( string='Confidence Score', digits=(3, 2), default=0.0, ) total_uses = fields.Integer(string='Total Uses', default=0) total_approved = fields.Integer(string='Total Approved', default=0) total_rejected = fields.Integer(string='Total Rejected', default=0) promotion_threshold = fields.Float( string='Promotion Threshold', default=0.95, ) min_sample_size = fields.Integer(string='Min Sample Size', default=30) active = fields.Boolean(string='Active', default=True, tracking=True) version = fields.Integer(string='Version', default=1) parent_rule_id = fields.Many2one( 'fusion.accounting.rule', string='Previous Version', ondelete='set null', ) journal_ids = fields.Many2many( 'account.journal', string='Journals', ) company_id = fields.Many2one( 'res.company', string='Company', default=lambda self: self.env.company, ) sequence = fields.Integer(string='Sequence', default=10) notes = fields.Text(string='Notes') def _record_decision(self, approved=True): for rec in self: self.env.cr.execute(""" UPDATE fusion_accounting_rule SET total_uses = total_uses + 1, total_approved = total_approved + %s, total_rejected = total_rejected + %s WHERE id = %s RETURNING total_uses, total_approved """, (int(approved), int(not approved), rec.id)) row = self.env.cr.fetchone() rec.invalidate_recordset(['total_uses', 'total_approved', 'total_rejected']) if row and row[0] > 0: rec.confidence_score = row[1] / row[0] rec._check_promotion() def _check_promotion(self): for rec in self: if (rec.approval_tier == 'needs_approval' and rec.total_uses >= rec.min_sample_size and rec.confidence_score >= rec.promotion_threshold): rec.write({'approval_tier': 'auto'}) _logger.info( "Rule '%s' promoted to auto-approved (confidence=%.2f, uses=%d)", rec.name, rec.confidence_score, rec.total_uses, ) def action_demote(self): self.write({'approval_tier': 'needs_approval'}) def action_rollback(self): for rec in self: if rec.parent_rule_id: # M5: Use write() to trigger tracking on tracked fields rec.write({'active': False}) rec.parent_rule_id.write({'active': True})