from odoo import api, fields, models class ResPartner(models.Model): _inherit = "res.partner" mcs_is_client_account = fields.Boolean(string="MCS Client Account", tracking=True) mcs_account_status = fields.Selection( [ ("active", "Active"), ("watch", "Watch"), ("paused", "Paused"), ("completed", "Completed"), ("lost", "Lost"), ], string="Account Status", default="active", tracking=True, ) mcs_account_health = fields.Selection( [ ("healthy", "Healthy"), ("watch", "Watch"), ("at_risk", "At Risk"), ("blocked", "Blocked"), ("completed", "Completed"), ], string="Account Health", compute="_compute_mcs_account_metrics", store=True, tracking=True, ) mcs_currency_id = fields.Many2one( "res.currency", string="MCS Currency", default=lambda self: self.env.company.currency_id, ) mcs_monthly_revenue = fields.Monetary( string="Monthly Revenue", currency_field="mcs_currency_id", compute="_compute_mcs_account_metrics", store=True, ) mcs_contracted_value = fields.Monetary( string="Contracted Value", currency_field="mcs_currency_id", compute="_compute_mcs_account_metrics", store=True, ) mcs_monthly_hours = fields.Float( string="Monthly Hours", compute="_compute_mcs_account_metrics", store=True, ) mcs_effective_hourly_revenue = fields.Monetary( string="Effective Hourly Revenue", currency_field="mcs_currency_id", compute="_compute_mcs_account_metrics", store=True, ) mcs_account_owner_id = fields.Many2one( "res.users", string="Account Owner", tracking=True ) mcs_next_account_action = fields.Char(string="Next Account Action", tracking=True) mcs_account_risk_reason = fields.Text(string="Account Risk Reason", tracking=True) mcs_project_ids = fields.One2many( "project.project", "partner_id", string="MCS Workstreams" ) def init(self): self._mcs_ensure_client_account_columns() @api.model def action_mcs_ensure_client_account_columns(self): self._mcs_ensure_client_account_columns() return True def _mcs_ensure_client_account_columns(self): columns = { "mcs_is_client_account": "boolean", "mcs_account_status": "varchar", "mcs_account_health": "varchar", "mcs_currency_id": "integer", "mcs_monthly_revenue": "numeric", "mcs_contracted_value": "numeric", "mcs_monthly_hours": "double precision", "mcs_effective_hourly_revenue": "numeric", "mcs_account_owner_id": "integer", "mcs_next_account_action": "varchar", "mcs_account_risk_reason": "text", } for column, column_type in columns.items(): self.env.cr.execute( 'ALTER TABLE res_partner ADD COLUMN IF NOT EXISTS "%s" %s' % (column, column_type) ) @api.depends( "mcs_project_ids.mcs_monthly_revenue_target", "mcs_project_ids.mcs_contracted_revenue", "mcs_project_ids.mcs_actual_hours", "mcs_project_ids.mcs_portfolio_health", "mcs_project_ids.active", ) def _compute_mcs_account_metrics(self): for partner in self: projects = partner.mcs_project_ids.filtered(lambda project: project.active) partner.mcs_monthly_revenue = sum(projects.mapped("mcs_monthly_revenue_target")) partner.mcs_contracted_value = sum(projects.mapped("mcs_contracted_revenue")) partner.mcs_monthly_hours = sum(projects.mapped("mcs_actual_hours")) partner.mcs_effective_hourly_revenue = ( partner.mcs_monthly_revenue / partner.mcs_monthly_hours if partner.mcs_monthly_hours else 0 ) partner.mcs_account_health = partner._mcs_resolve_account_health( partner._mcs_get_account_health_signals(projects) ) def _mcs_get_account_health_signals(self, projects): """Return health signals used to resolve client account health. V2.1 support/helpdesk integration should extend this method and add ticket/SLA signals instead of replacing the account rollup model. """ health_values = set(projects.mapped("mcs_portfolio_health")) return { "has_projects": bool(projects), "project_health_values": health_values, "has_blocked_project": "blocked" in health_values, "has_at_risk_project": "at_risk" in health_values, "has_watch_project": "watch" in health_values, "all_projects_completed": bool(projects) and health_values == {"completed"}, } def _mcs_resolve_account_health(self, signals): """Resolve account health from extensible management signals.""" if signals.get("has_blocked_project"): return "blocked" if signals.get("has_at_risk_project"): return "at_risk" if signals.get("has_watch_project"): return "watch" if signals.get("all_projects_completed"): return "completed" return "healthy"