from dateutil.relativedelta import relativedelta from odoo import api, fields, models class McsOperatingDashboard(models.Model): _name = "mcs.operating.dashboard" _description = "Operating Dashboard" name = fields.Char(default="CEO Dashboard", required=True) currency_id = fields.Many2one( "res.currency", default=lambda self: self.env.company.currency_id ) month_start = fields.Date(compute="_compute_metrics") month_end = fields.Date(compute="_compute_metrics") monthly_revenue_target = fields.Monetary(compute="_compute_metrics") revenue_closed_month = fields.Monetary(compute="_compute_metrics") monthly_cash_gap = fields.Monetary(compute="_compute_metrics") new_mrr_won = fields.Monetary(compute="_compute_metrics") pipeline_value = fields.Monetary(compute="_compute_metrics") weighted_pipeline = fields.Monetary(compute="_compute_metrics") proposals_outstanding = fields.Integer(compute="_compute_metrics") deals_expected_month = fields.Integer(compute="_compute_metrics") cash_collected = fields.Monetary(compute="_compute_metrics") invoices_overdue = fields.Integer(compute="_compute_metrics") active_clients = fields.Integer(compute="_compute_metrics") opportunities_created = fields.Integer(compute="_compute_metrics") opportunities_won = fields.Integer(compute="_compute_metrics") conversion_rate = fields.Float(compute="_compute_metrics") followups_due_today = fields.Integer(compute="_compute_metrics") projects_at_risk = fields.Integer(compute="_compute_metrics") blockers = fields.Integer(compute="_compute_metrics") overdue_tasks = fields.Integer(compute="_compute_metrics") team_capacity_hours = fields.Float(compute="_compute_metrics") planned_hours = fields.Float(compute="_compute_metrics") actual_hours = fields.Float(compute="_compute_metrics") billable_hours = fields.Float(compute="_compute_metrics") non_billable_hours = fields.Float(compute="_compute_metrics") utilization = fields.Float(compute="_compute_metrics") @api.depends_context("uid") def _compute_metrics(self): today = fields.Date.context_today(self) month_start = today.replace(day=1) month_end = month_start + relativedelta(months=1, days=-1) Lead = self.env["crm.lead"] Project = self.env["project.project"] Task = self.env["project.task"] Timesheet = self.env["account.analytic.line"] Invoice = self.env["account.move"] Employee = self.env["hr.employee"] open_pipeline = Lead.search( [("type", "=", "opportunity"), ("active", "=", True), ("probability", "<", 100)] ) won_month = Lead.search( [ ("type", "=", "opportunity"), ("active", "=", True), ("probability", "=", 100), ("date_closed", ">=", month_start), ("date_closed", "<=", month_end), ] ) invoices_month = Invoice.search( [ ("move_type", "=", "out_invoice"), ("state", "=", "posted"), ("invoice_date", ">=", month_start), ("invoice_date", "<=", month_end), ] ) overdue_invoices = Invoice.search( [ ("move_type", "=", "out_invoice"), ("state", "=", "posted"), ("payment_state", "not in", ["paid", "in_payment", "reversed"]), ("invoice_date_due", "<", today), ] ) timesheets_month = Timesheet.search( [("date", ">=", month_start), ("date", "<=", month_end)] ) active_projects = Project.search([("active", "=", True)]) active_tasks = Task.search([("active", "=", True)]) employees = Employee.search([("active", "=", True)]) for dashboard in self: dashboard.month_start = month_start dashboard.month_end = month_end dashboard.monthly_revenue_target = sum( active_projects.mapped("mcs_monthly_revenue_target") ) dashboard.revenue_closed_month = sum(won_month.mapped("expected_revenue")) dashboard.monthly_cash_gap = ( dashboard.monthly_revenue_target - dashboard.revenue_closed_month ) dashboard.new_mrr_won = sum(won_month.mapped("mcs_expected_monthly_revenue")) dashboard.pipeline_value = sum(open_pipeline.mapped("expected_revenue")) dashboard.weighted_pipeline = sum( lead.expected_revenue * lead.probability / 100 for lead in open_pipeline ) dashboard.proposals_outstanding = Lead.search_count( [ ("type", "=", "opportunity"), ("active", "=", True), ("mcs_proposal_sent_date", "!=", False), ("probability", "<", 100), ] ) dashboard.deals_expected_month = Lead.search_count( [ ("type", "=", "opportunity"), ("active", "=", True), ("date_deadline", ">=", month_start), ("date_deadline", "<=", month_end), ("probability", "<", 100), ] ) dashboard.cash_collected = sum( invoice.amount_total - invoice.amount_residual for invoice in invoices_month ) dashboard.invoices_overdue = len(overdue_invoices) dashboard.active_clients = len(set(active_projects.mapped("partner_id").ids)) created = Lead.search_count( [ ("type", "=", "opportunity"), ("create_date", ">=", month_start), ("create_date", "<=", month_end), ] ) dashboard.opportunities_created = created dashboard.opportunities_won = len(won_month) dashboard.conversion_rate = len(won_month) / created * 100 if created else 0 dashboard.followups_due_today = self.env["mail.activity"].search_count( [ ("user_id", "=", self.env.user.id), ("date_deadline", "<=", today), ("res_model", "=", "crm.lead"), ] ) dashboard.projects_at_risk = len(active_projects.filtered("mcs_at_risk")) dashboard.blockers = len(active_tasks.filtered("mcs_is_blocked")) dashboard.overdue_tasks = len( active_tasks.filtered( lambda task: task.date_deadline and fields.Date.to_date(task.date_deadline) < today and not (task.stage_id and task.stage_id.fold) ) ) dashboard.team_capacity_hours = sum(employees.mapped("mcs_available_hours_week")) dashboard.planned_hours = sum(active_tasks.mapped("allocated_hours")) dashboard.actual_hours = sum(timesheets_month.mapped("unit_amount")) dashboard.billable_hours = sum( timesheets_month.filtered( lambda line: line.mcs_billable_classification == "billable" ).mapped("unit_amount") ) dashboard.non_billable_hours = dashboard.actual_hours - dashboard.billable_hours dashboard.utilization = ( dashboard.billable_hours / dashboard.team_capacity_hours * 100 if dashboard.team_capacity_hours else 0 ) def action_refresh(self): return { "type": "ir.actions.act_window", "name": "CEO Dashboard", "res_model": "mcs.operating.dashboard", "view_mode": "form", "target": "current", "context": {"create": False}, }