2026-08-30 11:03:54 -04:00

147 lines
5.3 KiB
Python

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"