Scope CEO portfolio by active companies
This commit is contained in:
parent
b17a5aaf7a
commit
462e91f00c
@ -187,6 +187,7 @@ class McsOperatingDashboard(models.Model):
|
|||||||
month_end = month_start + relativedelta(months=1, days=-1)
|
month_end = month_start + relativedelta(months=1, days=-1)
|
||||||
week_start = today - relativedelta(days=today.weekday())
|
week_start = today - relativedelta(days=today.weekday())
|
||||||
week_end = week_start + relativedelta(days=6)
|
week_end = week_start + relativedelta(days=6)
|
||||||
|
company_ids = self._dashboard_company_ids()
|
||||||
|
|
||||||
Lead = self.env["crm.lead"]
|
Lead = self.env["crm.lead"]
|
||||||
Project = self.env["project.project"]
|
Project = self.env["project.project"]
|
||||||
@ -197,7 +198,10 @@ class McsOperatingDashboard(models.Model):
|
|||||||
Partner = self.env["res.partner"]
|
Partner = self.env["res.partner"]
|
||||||
CeoAction = self.env["mcs.ceo.action"]
|
CeoAction = self.env["mcs.ceo.action"]
|
||||||
|
|
||||||
active_projects = Project.search([("active", "=", True)])
|
active_projects = Project.search(
|
||||||
|
[("active", "=", True)]
|
||||||
|
+ self._company_domain(Project, company_ids, include_shared=True)
|
||||||
|
)
|
||||||
client_projects = active_projects.filtered(
|
client_projects = active_projects.filtered(
|
||||||
lambda project: project.mcs_delivery_type
|
lambda project: project.mcs_delivery_type
|
||||||
not in ("internal_product", "internal_r_and_d", "sales_initiative")
|
not in ("internal_product", "internal_r_and_d", "sales_initiative")
|
||||||
@ -207,7 +211,12 @@ class McsOperatingDashboard(models.Model):
|
|||||||
in ("internal_product", "internal_r_and_d")
|
in ("internal_product", "internal_r_and_d")
|
||||||
)
|
)
|
||||||
open_pipeline = Lead.search(
|
open_pipeline = Lead.search(
|
||||||
[("type", "=", "opportunity"), ("active", "=", True), ("probability", "<", 100)]
|
[
|
||||||
|
("type", "=", "opportunity"),
|
||||||
|
("active", "=", True),
|
||||||
|
("probability", "<", 100),
|
||||||
|
]
|
||||||
|
+ self._company_domain(Lead, company_ids, include_shared=True)
|
||||||
)
|
)
|
||||||
won_month = Lead.search(
|
won_month = Lead.search(
|
||||||
[
|
[
|
||||||
@ -217,6 +226,7 @@ class McsOperatingDashboard(models.Model):
|
|||||||
("date_closed", ">=", month_start),
|
("date_closed", ">=", month_start),
|
||||||
("date_closed", "<=", month_end),
|
("date_closed", "<=", month_end),
|
||||||
]
|
]
|
||||||
|
+ self._company_domain(Lead, company_ids, include_shared=True)
|
||||||
)
|
)
|
||||||
invoices_month = Invoice.search(
|
invoices_month = Invoice.search(
|
||||||
[
|
[
|
||||||
@ -225,6 +235,7 @@ class McsOperatingDashboard(models.Model):
|
|||||||
("invoice_date", ">=", month_start),
|
("invoice_date", ">=", month_start),
|
||||||
("invoice_date", "<=", month_end),
|
("invoice_date", "<=", month_end),
|
||||||
]
|
]
|
||||||
|
+ self._company_domain(Invoice, company_ids, include_shared=False)
|
||||||
)
|
)
|
||||||
receivables = Invoice.search(
|
receivables = Invoice.search(
|
||||||
[
|
[
|
||||||
@ -232,18 +243,31 @@ class McsOperatingDashboard(models.Model):
|
|||||||
("state", "=", "posted"),
|
("state", "=", "posted"),
|
||||||
("payment_state", "not in", ["paid", "in_payment", "reversed"]),
|
("payment_state", "not in", ["paid", "in_payment", "reversed"]),
|
||||||
]
|
]
|
||||||
|
+ self._company_domain(Invoice, company_ids, include_shared=False)
|
||||||
|
)
|
||||||
|
posted_invoices = Invoice.search(
|
||||||
|
[("move_type", "=", "out_invoice"), ("state", "=", "posted")]
|
||||||
|
+ self._company_domain(Invoice, company_ids, include_shared=False)
|
||||||
)
|
)
|
||||||
overdue_receivables = receivables.filtered(
|
overdue_receivables = receivables.filtered(
|
||||||
lambda invoice: invoice.invoice_date_due and invoice.invoice_date_due < today
|
lambda invoice: invoice.invoice_date_due and invoice.invoice_date_due < today
|
||||||
)
|
)
|
||||||
timesheets_month = Timesheet.search(
|
timesheets_month = Timesheet.search(
|
||||||
[("date", ">=", month_start), ("date", "<=", month_end)]
|
[("date", ">=", month_start), ("date", "<=", month_end)]
|
||||||
|
+ self._company_domain(Timesheet, company_ids, include_shared=True)
|
||||||
)
|
)
|
||||||
timesheets_week = Timesheet.search(
|
timesheets_week = Timesheet.search(
|
||||||
[("date", ">=", week_start), ("date", "<=", week_end)]
|
[("date", ">=", week_start), ("date", "<=", week_end)]
|
||||||
|
+ self._company_domain(Timesheet, company_ids, include_shared=True)
|
||||||
|
)
|
||||||
|
active_employees = Employee.search(
|
||||||
|
[("active", "=", True)]
|
||||||
|
+ self._company_domain(Employee, company_ids, include_shared=True)
|
||||||
|
)
|
||||||
|
active_tasks = Task.search(
|
||||||
|
[("active", "=", True)]
|
||||||
|
+ self._company_domain(Task, company_ids, include_shared=True)
|
||||||
)
|
)
|
||||||
active_employees = Employee.search([("active", "=", True)])
|
|
||||||
active_tasks = Task.search([("active", "=", True)])
|
|
||||||
|
|
||||||
monthly_target = sum(active_projects.mapped("mcs_monthly_revenue_target"))
|
monthly_target = sum(active_projects.mapped("mcs_monthly_revenue_target"))
|
||||||
contracted_revenue = sum(client_projects.mapped("mcs_contracted_revenue"))
|
contracted_revenue = sum(client_projects.mapped("mcs_contracted_revenue"))
|
||||||
@ -293,6 +317,8 @@ class McsOperatingDashboard(models.Model):
|
|||||||
"month_end": fields.Date.to_string(month_end),
|
"month_end": fields.Date.to_string(month_end),
|
||||||
"week_start": fields.Date.to_string(week_start),
|
"week_start": fields.Date.to_string(week_start),
|
||||||
"week_end": fields.Date.to_string(week_end),
|
"week_end": fields.Date.to_string(week_end),
|
||||||
|
"company_ids": company_ids,
|
||||||
|
"companies": self.env["res.company"].browse(company_ids).mapped("name"),
|
||||||
},
|
},
|
||||||
"company_pulse": {
|
"company_pulse": {
|
||||||
"revenue_coverage": self._format_percent(revenue_coverage),
|
"revenue_coverage": self._format_percent(revenue_coverage),
|
||||||
@ -336,19 +362,32 @@ class McsOperatingDashboard(models.Model):
|
|||||||
"pipeline_coverage_status": self._coverage_status(pipeline_coverage),
|
"pipeline_coverage_status": self._coverage_status(pipeline_coverage),
|
||||||
"funnel": self._crm_funnel_data(open_pipeline),
|
"funnel": self._crm_funnel_data(open_pipeline),
|
||||||
},
|
},
|
||||||
"portfolio": self._portfolio_rows(Partner, client_projects),
|
"portfolio": self._portfolio_rows(Partner, client_projects, posted_invoices),
|
||||||
"project_health": self._project_health_distribution(active_projects),
|
"project_health": self._project_health_distribution(active_projects),
|
||||||
"capacity": self._capacity_rows(active_employees, active_tasks, timesheets_week),
|
"capacity": self._capacity_rows(active_employees, active_tasks, timesheets_week),
|
||||||
"time_distribution": self._time_distribution(timesheets_week),
|
"time_distribution": self._time_distribution(timesheets_week),
|
||||||
"allocation_guardrail": self._allocation_guardrail(timesheets_week),
|
"allocation_guardrail": self._allocation_guardrail(timesheets_week),
|
||||||
"ceo_actions": self._ceo_action_rows(CeoAction),
|
"ceo_actions": self._ceo_action_rows(CeoAction),
|
||||||
"internal_bets": self._internal_bet_rows(internal_projects, timesheets_month),
|
"internal_bets": self._internal_bet_rows(internal_projects, timesheets_month),
|
||||||
"actions": self._dashboard_actions(today, month_start, month_end, week_start, week_end),
|
"actions": self._dashboard_actions(
|
||||||
|
today, month_start, month_end, week_start, week_end, company_ids
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
def _format_percent(self, value):
|
def _format_percent(self, value):
|
||||||
return round(value or 0, 1)
|
return round(value or 0, 1)
|
||||||
|
|
||||||
|
def _dashboard_company_ids(self):
|
||||||
|
company_ids = self.env.context.get("allowed_company_ids") or self.env.companies.ids
|
||||||
|
return company_ids or [self.env.company.id]
|
||||||
|
|
||||||
|
def _company_domain(self, Model, company_ids, include_shared=True):
|
||||||
|
if "company_id" not in Model._fields:
|
||||||
|
return []
|
||||||
|
if include_shared:
|
||||||
|
return ["|", ("company_id", "=", False), ("company_id", "in", company_ids)]
|
||||||
|
return [("company_id", "in", company_ids)]
|
||||||
|
|
||||||
def _monthly_operating_cost(self):
|
def _monthly_operating_cost(self):
|
||||||
try:
|
try:
|
||||||
Contract = self.env["hr.contract"]
|
Contract = self.env["hr.contract"]
|
||||||
@ -356,6 +395,9 @@ class McsOperatingDashboard(models.Model):
|
|||||||
return 0
|
return 0
|
||||||
contracts = Contract.sudo().search(
|
contracts = Contract.sudo().search(
|
||||||
[("state", "=", "open"), ("employee_id.active", "=", True)]
|
[("state", "=", "open"), ("employee_id.active", "=", True)]
|
||||||
|
+ self._company_domain(
|
||||||
|
Contract, self._dashboard_company_ids(), include_shared=True
|
||||||
|
)
|
||||||
)
|
)
|
||||||
return sum(contracts.mapped("wage"))
|
return sum(contracts.mapped("wage"))
|
||||||
|
|
||||||
@ -435,32 +477,53 @@ class McsOperatingDashboard(models.Model):
|
|||||||
)
|
)
|
||||||
return rows
|
return rows
|
||||||
|
|
||||||
def _portfolio_rows(self, Partner, client_projects):
|
def _portfolio_rows(self, Partner, client_projects, posted_invoices):
|
||||||
partners = Partner.search([("mcs_is_client_account", "=", True)])
|
partners = (
|
||||||
|
Partner.search([("mcs_is_client_account", "=", True)])
|
||||||
|
| client_projects.mapped("partner_id")
|
||||||
|
| posted_invoices.mapped("commercial_partner_id")
|
||||||
|
)
|
||||||
rows = []
|
rows = []
|
||||||
for partner in partners:
|
for partner in partners:
|
||||||
projects = client_projects.filtered(lambda project: project.partner_id == partner)
|
projects = client_projects.filtered(lambda project: project.partner_id == partner)
|
||||||
|
invoices = posted_invoices.filtered(
|
||||||
|
lambda invoice: invoice.commercial_partner_id == partner.commercial_partner_id
|
||||||
|
)
|
||||||
|
unpaid_invoices = invoices.filtered(
|
||||||
|
lambda invoice: invoice.payment_state
|
||||||
|
not in ("paid", "in_payment", "reversed")
|
||||||
|
)
|
||||||
|
overdue_invoices = unpaid_invoices.filtered(
|
||||||
|
lambda invoice: invoice.invoice_date_due
|
||||||
|
and invoice.invoice_date_due < fields.Date.context_today(self)
|
||||||
|
)
|
||||||
if partner.parent_id and not projects:
|
if partner.parent_id and not projects:
|
||||||
continue
|
continue
|
||||||
if (
|
if (
|
||||||
not projects
|
not projects
|
||||||
and not partner.mcs_total_invoiced
|
and not invoices
|
||||||
and not partner.mcs_total_outstanding
|
|
||||||
):
|
):
|
||||||
continue
|
continue
|
||||||
|
currencies = invoices.mapped("currency_id") or partner.mcs_currency_id
|
||||||
|
currency_name = (
|
||||||
|
currencies[0].name
|
||||||
|
if len(currencies) == 1
|
||||||
|
else "Mixed"
|
||||||
|
)
|
||||||
|
invoice_dates = [date for date in invoices.mapped("invoice_date") if date]
|
||||||
rows.append(
|
rows.append(
|
||||||
{
|
{
|
||||||
"id": partner.id,
|
"id": partner.id,
|
||||||
"name": partner.display_name,
|
"name": partner.display_name,
|
||||||
"currency": partner.mcs_currency_id.name or "",
|
"currency": currency_name,
|
||||||
"health": partner.mcs_account_health,
|
"health": partner.mcs_account_health,
|
||||||
"active_workstreams": len(projects),
|
"active_workstreams": len(projects),
|
||||||
"revenue": sum(projects.mapped("mcs_monthly_revenue_target")),
|
"revenue": sum(projects.mapped("mcs_monthly_revenue_target")),
|
||||||
"total_invoiced": partner.mcs_total_invoiced,
|
"total_invoiced": sum(invoices.mapped("amount_total_signed")),
|
||||||
"outstanding": partner.mcs_total_outstanding,
|
"outstanding": sum(unpaid_invoices.mapped("amount_residual_signed")),
|
||||||
"overdue": partner.mcs_total_overdue,
|
"overdue": sum(overdue_invoices.mapped("amount_residual_signed")),
|
||||||
"last_invoice_date": fields.Date.to_string(partner.mcs_last_invoice_date)
|
"last_invoice_date": fields.Date.to_string(max(invoice_dates))
|
||||||
if partner.mcs_last_invoice_date
|
if invoice_dates
|
||||||
else "",
|
else "",
|
||||||
"hours": sum(projects.mapped("mcs_actual_hours")),
|
"hours": sum(projects.mapped("mcs_actual_hours")),
|
||||||
"next_milestone": next(
|
"next_milestone": next(
|
||||||
@ -626,7 +689,11 @@ class McsOperatingDashboard(models.Model):
|
|||||||
)
|
)
|
||||||
return rows
|
return rows
|
||||||
|
|
||||||
def _dashboard_actions(self, today, month_start, month_end, week_start, week_end):
|
def _dashboard_actions(self, today, month_start, month_end, week_start, week_end, company_ids):
|
||||||
|
Project = self.env["project.project"]
|
||||||
|
Lead = self.env["crm.lead"]
|
||||||
|
Invoice = self.env["account.move"]
|
||||||
|
Timesheet = self.env["account.analytic.line"]
|
||||||
return {
|
return {
|
||||||
"monthly_revenue_projects": {
|
"monthly_revenue_projects": {
|
||||||
"type": "ir.actions.act_window",
|
"type": "ir.actions.act_window",
|
||||||
@ -634,7 +701,8 @@ class McsOperatingDashboard(models.Model):
|
|||||||
"res_model": "project.project",
|
"res_model": "project.project",
|
||||||
"view_mode": "tree,form,kanban",
|
"view_mode": "tree,form,kanban",
|
||||||
"views": self._action_views("tree", "form", "kanban"),
|
"views": self._action_views("tree", "form", "kanban"),
|
||||||
"domain": [("active", "=", True), ("mcs_monthly_revenue_target", ">", 0)],
|
"domain": [("active", "=", True), ("mcs_monthly_revenue_target", ">", 0)]
|
||||||
|
+ self._company_domain(Project, company_ids, include_shared=True),
|
||||||
},
|
},
|
||||||
"contracted_projects": {
|
"contracted_projects": {
|
||||||
"type": "ir.actions.act_window",
|
"type": "ir.actions.act_window",
|
||||||
@ -642,7 +710,8 @@ class McsOperatingDashboard(models.Model):
|
|||||||
"res_model": "project.project",
|
"res_model": "project.project",
|
||||||
"view_mode": "tree,form,kanban",
|
"view_mode": "tree,form,kanban",
|
||||||
"views": self._action_views("tree", "form", "kanban"),
|
"views": self._action_views("tree", "form", "kanban"),
|
||||||
"domain": [("active", "=", True), ("mcs_contracted_revenue", ">", 0)],
|
"domain": [("active", "=", True), ("mcs_contracted_revenue", ">", 0)]
|
||||||
|
+ self._company_domain(Project, company_ids, include_shared=True),
|
||||||
},
|
},
|
||||||
"pipeline": {
|
"pipeline": {
|
||||||
"type": "ir.actions.act_window",
|
"type": "ir.actions.act_window",
|
||||||
@ -650,7 +719,12 @@ class McsOperatingDashboard(models.Model):
|
|||||||
"res_model": "crm.lead",
|
"res_model": "crm.lead",
|
||||||
"view_mode": "kanban,tree,form,pivot,graph",
|
"view_mode": "kanban,tree,form,pivot,graph",
|
||||||
"views": self._action_views("kanban", "tree", "form", "pivot", "graph"),
|
"views": self._action_views("kanban", "tree", "form", "pivot", "graph"),
|
||||||
"domain": [("type", "=", "opportunity"), ("active", "=", True), ("probability", "<", 100)],
|
"domain": [
|
||||||
|
("type", "=", "opportunity"),
|
||||||
|
("active", "=", True),
|
||||||
|
("probability", "<", 100),
|
||||||
|
]
|
||||||
|
+ self._company_domain(Lead, company_ids, include_shared=True),
|
||||||
},
|
},
|
||||||
"overdue_receivables": {
|
"overdue_receivables": {
|
||||||
"type": "ir.actions.act_window",
|
"type": "ir.actions.act_window",
|
||||||
@ -663,7 +737,8 @@ class McsOperatingDashboard(models.Model):
|
|||||||
("state", "=", "posted"),
|
("state", "=", "posted"),
|
||||||
("payment_state", "not in", ["paid", "in_payment", "reversed"]),
|
("payment_state", "not in", ["paid", "in_payment", "reversed"]),
|
||||||
("invoice_date_due", "<", fields.Date.to_string(today)),
|
("invoice_date_due", "<", fields.Date.to_string(today)),
|
||||||
],
|
]
|
||||||
|
+ self._company_domain(Invoice, company_ids, include_shared=False),
|
||||||
},
|
},
|
||||||
"capacity": {
|
"capacity": {
|
||||||
"type": "ir.actions.act_window",
|
"type": "ir.actions.act_window",
|
||||||
@ -674,7 +749,8 @@ class McsOperatingDashboard(models.Model):
|
|||||||
"domain": [
|
"domain": [
|
||||||
("date", ">=", fields.Date.to_string(week_start)),
|
("date", ">=", fields.Date.to_string(week_start)),
|
||||||
("date", "<=", fields.Date.to_string(week_end)),
|
("date", "<=", fields.Date.to_string(week_end)),
|
||||||
],
|
]
|
||||||
|
+ self._company_domain(Timesheet, company_ids, include_shared=True),
|
||||||
"context": {"group_by": "employee_id"},
|
"context": {"group_by": "employee_id"},
|
||||||
},
|
},
|
||||||
"projects_at_risk": {
|
"projects_at_risk": {
|
||||||
@ -683,7 +759,11 @@ class McsOperatingDashboard(models.Model):
|
|||||||
"res_model": "project.project",
|
"res_model": "project.project",
|
||||||
"view_mode": "tree,form,kanban",
|
"view_mode": "tree,form,kanban",
|
||||||
"views": self._action_views("tree", "form", "kanban"),
|
"views": self._action_views("tree", "form", "kanban"),
|
||||||
"domain": [("active", "=", True), ("mcs_portfolio_health", "in", ["at_risk", "blocked"])],
|
"domain": [
|
||||||
|
("active", "=", True),
|
||||||
|
("mcs_portfolio_health", "in", ["at_risk", "blocked"]),
|
||||||
|
]
|
||||||
|
+ self._company_domain(Project, company_ids, include_shared=True),
|
||||||
},
|
},
|
||||||
"ceo_actions": {
|
"ceo_actions": {
|
||||||
"type": "ir.actions.act_window",
|
"type": "ir.actions.act_window",
|
||||||
@ -699,7 +779,8 @@ class McsOperatingDashboard(models.Model):
|
|||||||
"res_model": "project.project",
|
"res_model": "project.project",
|
||||||
"view_mode": "tree,form,kanban",
|
"view_mode": "tree,form,kanban",
|
||||||
"views": self._action_views("tree", "form", "kanban"),
|
"views": self._action_views("tree", "form", "kanban"),
|
||||||
"domain": [("mcs_delivery_type", "in", ["internal_product", "internal_r_and_d"])],
|
"domain": [("mcs_delivery_type", "in", ["internal_product", "internal_r_and_d"])]
|
||||||
|
+ self._company_domain(Project, company_ids, include_shared=True),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user