Add CEO dashboard company selector
This commit is contained in:
parent
315ab7497a
commit
27778a7901
@ -181,13 +181,13 @@ class McsOperatingDashboard(models.Model):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def get_ceo_dashboard_v2_data(self):
|
def get_ceo_dashboard_v2_data(self, selected_company_ids=None):
|
||||||
today = fields.Date.context_today(self)
|
today = fields.Date.context_today(self)
|
||||||
month_start = today.replace(day=1)
|
month_start = today.replace(day=1)
|
||||||
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()
|
company_ids = self._dashboard_company_ids(selected_company_ids)
|
||||||
|
|
||||||
Lead = self.env["crm.lead"]
|
Lead = self.env["crm.lead"]
|
||||||
Project = self.env["project.project"]
|
Project = self.env["project.project"]
|
||||||
@ -275,7 +275,7 @@ class McsOperatingDashboard(models.Model):
|
|||||||
cash_collected = sum(
|
cash_collected = sum(
|
||||||
invoice.amount_total - invoice.amount_residual for invoice in invoices_month
|
invoice.amount_total - invoice.amount_residual for invoice in invoices_month
|
||||||
)
|
)
|
||||||
operating_cost = self._monthly_operating_cost()
|
operating_cost = self._monthly_operating_cost(company_ids)
|
||||||
revenue_gap = max(monthly_target - contracted_revenue, 0)
|
revenue_gap = max(monthly_target - contracted_revenue, 0)
|
||||||
open_pipeline_value = sum(open_pipeline.mapped("expected_revenue"))
|
open_pipeline_value = sum(open_pipeline.mapped("expected_revenue"))
|
||||||
weighted_pipeline = sum(
|
weighted_pipeline = sum(
|
||||||
@ -319,6 +319,12 @@ class McsOperatingDashboard(models.Model):
|
|||||||
"week_end": fields.Date.to_string(week_end),
|
"week_end": fields.Date.to_string(week_end),
|
||||||
"company_ids": company_ids,
|
"company_ids": company_ids,
|
||||||
"companies": self.env["res.company"].browse(company_ids).mapped("name"),
|
"companies": self.env["res.company"].browse(company_ids).mapped("name"),
|
||||||
|
"company_options": [
|
||||||
|
{"id": company.id, "name": company.name}
|
||||||
|
for company in self.env["res.company"].browse(
|
||||||
|
self._dashboard_company_ids()
|
||||||
|
)
|
||||||
|
],
|
||||||
},
|
},
|
||||||
"company_pulse": {
|
"company_pulse": {
|
||||||
"revenue_coverage": self._format_percent(revenue_coverage),
|
"revenue_coverage": self._format_percent(revenue_coverage),
|
||||||
@ -377,9 +383,20 @@ class McsOperatingDashboard(models.Model):
|
|||||||
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):
|
def _dashboard_company_ids(self, selected_company_ids=None):
|
||||||
company_ids = self.env.context.get("allowed_company_ids") or self.env.companies.ids
|
allowed_company_ids = (
|
||||||
return company_ids or [self.env.company.id]
|
self.env.context.get("allowed_company_ids") or self.env.companies.ids
|
||||||
|
)
|
||||||
|
allowed_company_ids = allowed_company_ids or [self.env.company.id]
|
||||||
|
if not selected_company_ids:
|
||||||
|
return allowed_company_ids
|
||||||
|
selected_company_ids = [int(company_id) for company_id in selected_company_ids]
|
||||||
|
selected_company_ids = [
|
||||||
|
company_id
|
||||||
|
for company_id in selected_company_ids
|
||||||
|
if company_id in allowed_company_ids
|
||||||
|
]
|
||||||
|
return selected_company_ids or allowed_company_ids
|
||||||
|
|
||||||
def _company_domain(self, Model, company_ids, include_shared=True):
|
def _company_domain(self, Model, company_ids, include_shared=True):
|
||||||
if "company_id" not in Model._fields:
|
if "company_id" not in Model._fields:
|
||||||
@ -388,16 +405,15 @@ class McsOperatingDashboard(models.Model):
|
|||||||
return ["|", ("company_id", "=", False), ("company_id", "in", company_ids)]
|
return ["|", ("company_id", "=", False), ("company_id", "in", company_ids)]
|
||||||
return [("company_id", "in", company_ids)]
|
return [("company_id", "in", company_ids)]
|
||||||
|
|
||||||
def _monthly_operating_cost(self):
|
def _monthly_operating_cost(self, company_ids=None):
|
||||||
try:
|
try:
|
||||||
Contract = self.env["hr.contract"]
|
Contract = self.env["hr.contract"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return 0
|
return 0
|
||||||
|
company_ids = company_ids or self._dashboard_company_ids()
|
||||||
contracts = Contract.sudo().search(
|
contracts = Contract.sudo().search(
|
||||||
[("state", "=", "open"), ("employee_id.active", "=", True)]
|
[("state", "=", "open"), ("employee_id.active", "=", True)]
|
||||||
+ self._company_domain(
|
+ self._company_domain(Contract, company_ids, include_shared=True)
|
||||||
Contract, self._dashboard_company_ids(), include_shared=True
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
return sum(contracts.mapped("wage"))
|
return sum(contracts.mapped("wage"))
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,7 @@ export class McsCeoDashboardV2 extends Component {
|
|||||||
setup() {
|
setup() {
|
||||||
this.action = useService("action");
|
this.action = useService("action");
|
||||||
this.orm = useService("orm");
|
this.orm = useService("orm");
|
||||||
this.state = useState({ data: null, loading: true });
|
this.state = useState({ data: null, loading: true, selectedCompanyId: "all" });
|
||||||
|
|
||||||
onWillStart(async () => {
|
onWillStart(async () => {
|
||||||
await this.loadDashboard();
|
await this.loadDashboard();
|
||||||
@ -17,14 +17,31 @@ export class McsCeoDashboardV2 extends Component {
|
|||||||
|
|
||||||
async loadDashboard() {
|
async loadDashboard() {
|
||||||
this.state.loading = true;
|
this.state.loading = true;
|
||||||
|
const selectedCompanyIds = this.selectedCompanyIds();
|
||||||
this.state.data = await this.orm.call(
|
this.state.data = await this.orm.call(
|
||||||
"mcs.operating.dashboard",
|
"mcs.operating.dashboard",
|
||||||
"get_ceo_dashboard_v2_data",
|
"get_ceo_dashboard_v2_data",
|
||||||
[]
|
[selectedCompanyIds]
|
||||||
);
|
);
|
||||||
this.state.loading = false;
|
this.state.loading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
selectedCompanyIds() {
|
||||||
|
if (this.state.selectedCompanyId === "all") {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return [Number(this.state.selectedCompanyId)];
|
||||||
|
}
|
||||||
|
|
||||||
|
async selectCompany(companyId) {
|
||||||
|
this.state.selectedCompanyId = String(companyId);
|
||||||
|
await this.loadDashboard();
|
||||||
|
}
|
||||||
|
|
||||||
|
isCompanySelected(companyId) {
|
||||||
|
return this.state.selectedCompanyId === String(companyId);
|
||||||
|
}
|
||||||
|
|
||||||
openAction(actionKey) {
|
openAction(actionKey) {
|
||||||
const action = this.state.data?.actions?.[actionKey];
|
const action = this.state.data?.actions?.[actionKey];
|
||||||
if (action) {
|
if (action) {
|
||||||
|
|||||||
@ -33,6 +33,25 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.o_mcs_header_controls,
|
||||||
|
.o_mcs_company_switcher {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.o_mcs_company_switcher {
|
||||||
|
justify-content: flex-end;
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
max-width: 220px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.o_mcs_pulse {
|
.o_mcs_pulse {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(220px, 1.4fr) repeat(4, minmax(140px, 1fr));
|
grid-template-columns: minmax(220px, 1.4fr) repeat(4, minmax(140px, 1fr));
|
||||||
|
|||||||
@ -13,9 +13,21 @@
|
|||||||
<t t-esc="state.data.period.month_start"/>
|
<t t-esc="state.data.period.month_start"/>
|
||||||
to
|
to
|
||||||
<t t-esc="state.data.period.month_end"/>
|
<t t-esc="state.data.period.month_end"/>
|
||||||
|
-
|
||||||
|
<t t-esc="state.data.period.companies.join(', ')"/>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-primary" t-on-click="loadDashboard">Refresh</button>
|
<div class="o_mcs_header_controls">
|
||||||
|
<div t-if="state.data.period.company_options.length > 1" class="o_mcs_company_switcher">
|
||||||
|
<button t-attf-class="btn {{ state.selectedCompanyId === 'all' ? 'btn-primary' : 'btn-secondary' }}" t-on-click="() => this.selectCompany('all')">All</button>
|
||||||
|
<t t-foreach="state.data.period.company_options" t-as="company" t-key="company.id">
|
||||||
|
<button t-attf-class="btn {{ this.isCompanySelected(company.id) ? 'btn-primary' : 'btn-secondary' }}" t-on-click="() => this.selectCompany(company.id)">
|
||||||
|
<t t-esc="company.name"/>
|
||||||
|
</button>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-primary" t-on-click="loadDashboard">Refresh</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section class="o_mcs_pulse">
|
<section class="o_mcs_pulse">
|
||||||
@ -153,7 +165,7 @@
|
|||||||
<t t-foreach="state.data.ceo_actions" t-as="action" t-key="action.id">
|
<t t-foreach="state.data.ceo_actions" t-as="action" t-key="action.id">
|
||||||
<button class="o_mcs_action_item" t-on-click="() => this.openAction('ceo_actions')">
|
<button class="o_mcs_action_item" t-on-click="() => this.openAction('ceo_actions')">
|
||||||
<strong t-esc="action.title"/>
|
<strong t-esc="action.title"/>
|
||||||
<span><t t-esc="action.category"/> · <t t-esc="action.priority"/> · <t t-esc="action.state"/></span>
|
<span><t t-esc="action.category"/> - <t t-esc="action.priority"/> - <t t-esc="action.state"/></span>
|
||||||
<small t-esc="action.next_action"/>
|
<small t-esc="action.next_action"/>
|
||||||
</button>
|
</button>
|
||||||
</t>
|
</t>
|
||||||
@ -169,7 +181,7 @@
|
|||||||
<t t-foreach="state.data.internal_bets" t-as="project" t-key="project.id">
|
<t t-foreach="state.data.internal_bets" t-as="project" t-key="project.id">
|
||||||
<button class="o_mcs_internal_item" t-on-click="() => this.openProject(project.id)">
|
<button class="o_mcs_internal_item" t-on-click="() => this.openProject(project.id)">
|
||||||
<strong t-esc="project.name"/>
|
<strong t-esc="project.name"/>
|
||||||
<span><t t-esc="project.stage"/> · <t t-esc="project.hours"/>h invested</span>
|
<span><t t-esc="project.stage"/> - <t t-esc="project.hours"/>h invested</span>
|
||||||
<small>Next: <t t-esc="project.next_milestone"/></small>
|
<small>Next: <t t-esc="project.next_milestone"/></small>
|
||||||
</button>
|
</button>
|
||||||
</t>
|
</t>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user