"""Cron handlers for fusion_accounting_followup. Two scheduled jobs: - Daily scan: walk every partner with an open overdue receivable line and call the engine to send/escalate where appropriate. - Weekly risk refresh: recompute fusion_followup_risk_score on every partner with overdue. """ import logging from datetime import date from odoo import api, models _logger = logging.getLogger(__name__) class FusionFollowupCron(models.AbstractModel): _name = "fusion.followup.cron" _description = "Fusion Follow-up Cron Handlers" @api.model def _cron_daily_scan(self): """Scan every partner with overdue and send follow-ups when due.""" engine = self.env['fusion.followup.engine'] Line = self.env['account.move.line'].sudo() overdue_lines = Line.search([ ('parent_state', '=', 'posted'), ('account_id.account_type', '=', 'asset_receivable'), ('reconciled', '=', False), ('amount_residual', '>', 0), ('date_maturity', '<', date.today()), ]) partner_ids = list(set(overdue_lines.mapped('partner_id').ids)) sent = 0 skipped = 0 for pid in partner_ids: partner = self.env['res.partner'].sudo().browse(pid) if not partner.exists(): continue try: with self.env.cr.savepoint(): result = engine.send_followup_email(partner) if result.get('status') == 'sent': sent += 1 else: skipped += 1 except Exception as e: _logger.warning( "Cron daily_scan failed for partner %s: %s", pid, e, ) skipped += 1 _logger.info( "Cron: scanned %d partners, sent %d, skipped %d", len(partner_ids), sent, skipped, ) @api.model def _cron_risk_refresh(self): """Refresh fusion_followup_risk_score on every partner with overdue.""" Partner = self.env['res.partner'].sudo() engine = self.env['fusion.followup.engine'] Line = self.env['account.move.line'].sudo() partner_ids = list(set(Line.search([ ('parent_state', '=', 'posted'), ('account_id.account_type', '=', 'asset_receivable'), ('reconciled', '=', False), ('amount_residual', '>', 0), ]).mapped('partner_id').ids)) updated = 0 for pid in partner_ids: partner = Partner.browse(pid) try: overdue = engine.get_overdue_for_partner(partner) partner.write({ 'fusion_followup_risk_score': overdue['risk']['score'], 'fusion_followup_risk_band': overdue['risk']['band'], }) updated += 1 except Exception as e: _logger.warning( "Risk refresh failed for partner %s: %s", pid, e, ) _logger.info("Cron: refreshed risk on %d partners", updated)