# -*- coding: utf-8 -*- # Copyright 2026 Nexa Systems Inc. # License OPL-1 (Odoo Proprietary License v1.0) from datetime import datetime, timedelta from odoo import api, fields, models class RcCallDashboard(models.TransientModel): _name = 'rc.call.dashboard' _description = 'RingCentral Call Dashboard' _rec_name = 'name' name = fields.Char(default='Call Dashboard', readonly=True) total_count = fields.Integer(compute='_compute_stats') inbound_count = fields.Integer(compute='_compute_stats') outbound_count = fields.Integer(compute='_compute_stats') answered_count = fields.Integer(compute='_compute_stats') missed_count = fields.Integer(compute='_compute_stats') avg_duration = fields.Float(compute='_compute_stats', string='Avg Duration (min)') success_rate = fields.Float(compute='_compute_stats', string='Success Rate (%)') today_count = fields.Integer(compute='_compute_time_stats', string='Today') week_count = fields.Integer(compute='_compute_time_stats', string='This Week') month_count = fields.Integer(compute='_compute_time_stats', string='This Month') voicemail_count = fields.Integer(compute='_compute_voicemail_stats') unread_voicemail_count = fields.Integer(compute='_compute_voicemail_stats') recent_call_ids = fields.Many2many( 'rc.call.history', compute='_compute_recent_calls', ) recent_voicemail_ids = fields.Many2many( 'rc.voicemail', 'rc_dashboard_recent_vm_rel', compute='_compute_voicemail_lists', ) older_voicemail_ids = fields.Many2many( 'rc.voicemail', 'rc_dashboard_older_vm_rel', compute='_compute_voicemail_lists', ) @api.depends_context('uid') def _compute_stats(self): Call = self.env['rc.call.history'] for rec in self: all_calls = Call.search([]) rec.total_count = len(all_calls) rec.inbound_count = Call.search_count([('direction', '=', 'inbound')]) rec.outbound_count = Call.search_count([('direction', '=', 'outbound')]) rec.answered_count = Call.search_count([('status', '=', 'answered')]) rec.missed_count = Call.search_count([('status', 'in', ('missed', 'no_answer'))]) durations = all_calls.mapped('duration') rec.avg_duration = round(sum(durations) / len(durations) / 60, 1) if durations else 0.0 rec.success_rate = round( (rec.answered_count / rec.total_count * 100) if rec.total_count else 0.0, 1 ) @api.depends_context('uid') def _compute_time_stats(self): Call = self.env['rc.call.history'] now = datetime.utcnow() today_start = now.replace(hour=0, minute=0, second=0, microsecond=0) week_start = today_start - timedelta(days=today_start.weekday()) month_start = today_start.replace(day=1) for rec in self: rec.today_count = Call.search_count([('start_time', '>=', today_start)]) rec.week_count = Call.search_count([('start_time', '>=', week_start)]) rec.month_count = Call.search_count([('start_time', '>=', month_start)]) @api.depends_context('uid') def _compute_recent_calls(self): for rec in self: rec.recent_call_ids = self.env['rc.call.history'].search([], limit=20) @api.depends_context('uid') def _compute_voicemail_lists(self): VM = self.env['rc.voicemail'] one_week_ago = datetime.utcnow() - timedelta(days=7) for rec in self: rec.recent_voicemail_ids = VM.search([ ('received_date', '>=', one_week_ago), ], limit=50) rec.older_voicemail_ids = VM.search([ ('received_date', '<', one_week_ago), ], limit=100) @api.depends_context('uid') def _compute_voicemail_stats(self): VM = self.env['rc.voicemail'] for rec in self: rec.voicemail_count = VM.search_count([]) rec.unread_voicemail_count = VM.search_count( [('read_status', '=', 'Unread')], ) # ────────────────────────────────────────────────────────── # Navigation actions # ────────────────────────────────────────────────────────── def action_open_all(self): return { 'type': 'ir.actions.act_window', 'name': 'Call History', 'res_model': 'rc.call.history', 'view_mode': 'list,form', } def action_open_inbound(self): return { 'type': 'ir.actions.act_window', 'name': 'Inbound Calls', 'res_model': 'rc.call.history', 'view_mode': 'list,form', 'domain': [('direction', '=', 'inbound')], } def action_open_outbound(self): return { 'type': 'ir.actions.act_window', 'name': 'Outbound Calls', 'res_model': 'rc.call.history', 'view_mode': 'list,form', 'domain': [('direction', '=', 'outbound')], } def action_open_missed(self): return { 'type': 'ir.actions.act_window', 'name': 'Missed Calls', 'res_model': 'rc.call.history', 'view_mode': 'list,form', 'domain': [('status', 'in', ('missed', 'no_answer'))], } def action_open_graph(self): return { 'type': 'ir.actions.act_window', 'name': 'Call Analytics', 'res_model': 'rc.call.history', 'view_mode': 'graph,pivot,list', } def action_open_voicemails(self): return { 'type': 'ir.actions.act_window', 'name': 'Voicemails', 'res_model': 'rc.voicemail', 'view_mode': 'list,form', } def action_open_unread_voicemails(self): return { 'type': 'ir.actions.act_window', 'name': 'Voicemails', 'res_model': 'rc.call.dashboard', 'view_mode': 'form', 'view_id': self.env.ref( 'fusion_ringcentral.view_rc_voicemail_dashboard_form', ).id, 'target': 'current', } # ────────────────────────────────────────────────────────── # Quick action buttons (Call / Message / Fax) # ────────────────────────────────────────────────────────── def action_quick_call(self): """Open the RingCentral dialer via the Embeddable widget.""" return { 'type': 'ir.actions.act_window_close', 'infos': { 'rc_action': 'call', 'rc_phone_number': '', }, } def action_quick_sms(self): """Open the RingCentral SMS compose via the Embeddable widget.""" return { 'type': 'ir.actions.act_window_close', 'infos': { 'rc_action': 'sms', 'rc_phone_number': '', }, } def action_quick_fax(self): """Open the Send Fax wizard.""" return { 'type': 'ir.actions.act_window', 'name': 'Send Fax', 'res_model': 'fusion_faxes.send.fax.wizard', 'view_mode': 'form', 'target': 'new', }