diff --git a/addons/mcs_operating_system/hooks.py b/addons/mcs_operating_system/hooks.py
index 41a8786..b7ea46b 100644
--- a/addons/mcs_operating_system/hooks.py
+++ b/addons/mcs_operating_system/hooks.py
@@ -38,8 +38,7 @@ class OperatingSystemSetup:
values = {"mcs_is_client_account": True}
if not partner.mcs_account_status:
values["mcs_account_status"] = "active"
- if partner.currency_id and not partner.mcs_currency_id:
- values["mcs_currency_id"] = partner.currency_id.id
+ values.update(self._client_company_currency_values(partner))
partner.write(values)
def _ensure_active_portfolio(self):
@@ -143,12 +142,47 @@ class OperatingSystemSetup:
values["mcs_is_client_account"] = True
if not partner.mcs_account_status:
values["mcs_account_status"] = "active"
- if partner.currency_id and not partner.mcs_currency_id:
- values["mcs_currency_id"] = partner.currency_id.id
+ values.update(self._client_company_currency_values(partner))
if values:
partner.write(values)
return partner
+ def _client_company_currency_values(self, partner):
+ invoice = self.Invoice.search(
+ [
+ ("move_type", "=", "out_invoice"),
+ ("state", "=", "posted"),
+ ("commercial_partner_id", "=", partner.commercial_partner_id.id),
+ ("company_id", "!=", False),
+ ],
+ order="invoice_date desc, id desc",
+ limit=1,
+ )
+ if invoice:
+ return {
+ "mcs_client_company_id": invoice.company_id.id,
+ "mcs_currency_id": invoice.currency_id.id or invoice.company_currency_id.id,
+ }
+ project = self.Project.search(
+ [("partner_id", "=", partner.id), ("company_id", "!=", False)],
+ order="write_date desc, id desc",
+ limit=1,
+ )
+ if project:
+ return {
+ "mcs_client_company_id": project.company_id.id,
+ "mcs_currency_id": project.company_id.currency_id.id,
+ }
+ if partner.currency_id:
+ company = self.env["res.company"].sudo().search(
+ [("currency_id", "=", partner.currency_id.id)], limit=1
+ )
+ values = {"mcs_currency_id": partner.currency_id.id}
+ if company:
+ values["mcs_client_company_id"] = company.id
+ return values
+ return {}
+
def _ensure_project(self, partner, account_name, workstream, delivery_type):
is_internal = partner.name == "Internal"
project_name = "%s - %s" % (partner.name, workstream)
diff --git a/addons/mcs_operating_system/models/crm_lead.py b/addons/mcs_operating_system/models/crm_lead.py
index f928848..cebda6c 100644
--- a/addons/mcs_operating_system/models/crm_lead.py
+++ b/addons/mcs_operating_system/models/crm_lead.py
@@ -107,6 +107,10 @@ class CrmLead(models.Model):
partner = lead.partner_id
if partner:
values = {"mcs_is_client_account": True}
+ company = lead.company_id or self.env.company
+ if company:
+ values["mcs_client_company_id"] = company.id
+ values["mcs_currency_id"] = company.currency_id.id
if not partner.mcs_account_owner_id and lead.user_id:
values["mcs_account_owner_id"] = lead.user_id.id
partner.write(values)
@@ -123,6 +127,7 @@ class CrmLead(models.Model):
{
"name": project_name,
"partner_id": partner.id if partner else False,
+ "company_id": lead.company_id.id if lead.company_id else self.env.company.id,
"user_id": lead.user_id.id if lead.user_id else False,
"allow_timesheets": True,
"allow_milestones": True,
diff --git a/addons/mcs_operating_system/models/res_partner.py b/addons/mcs_operating_system/models/res_partner.py
index 7131e55..fcf531c 100644
--- a/addons/mcs_operating_system/models/res_partner.py
+++ b/addons/mcs_operating_system/models/res_partner.py
@@ -35,6 +35,11 @@ class ResPartner(models.Model):
string="MCS Currency",
default=lambda self: self.env.company.currency_id,
)
+ mcs_client_company_id = fields.Many2one(
+ "res.company",
+ string="MCS Company",
+ help="Company inferred from this client's invoices or delivery projects.",
+ )
mcs_monthly_revenue = fields.Monetary(
string="Monthly Revenue",
currency_field="mcs_currency_id",
@@ -63,16 +68,28 @@ class ResPartner(models.Model):
currency_field="mcs_currency_id",
compute="_compute_mcs_account_metrics",
)
+ mcs_total_invoiced_display = fields.Char(
+ string="Total Invoiced",
+ compute="_compute_mcs_account_metrics",
+ )
mcs_total_outstanding = fields.Monetary(
string="Outstanding Receivables",
currency_field="mcs_currency_id",
compute="_compute_mcs_account_metrics",
)
+ mcs_total_outstanding_display = fields.Char(
+ string="Outstanding Receivables",
+ compute="_compute_mcs_account_metrics",
+ )
mcs_total_overdue = fields.Monetary(
string="Overdue Receivables",
currency_field="mcs_currency_id",
compute="_compute_mcs_account_metrics",
)
+ mcs_total_overdue_display = fields.Char(
+ string="Overdue Receivables",
+ compute="_compute_mcs_account_metrics",
+ )
mcs_last_invoice_date = fields.Date(
string="Last Invoice Date",
compute="_compute_mcs_account_metrics",
@@ -100,13 +117,17 @@ class ResPartner(models.Model):
"mcs_account_status": "varchar",
"mcs_account_health": "varchar",
"mcs_currency_id": "integer",
+ "mcs_client_company_id": "integer",
"mcs_monthly_revenue": "numeric",
"mcs_contracted_value": "numeric",
"mcs_monthly_hours": "double precision",
"mcs_effective_hourly_revenue": "numeric",
"mcs_total_invoiced": "numeric",
+ "mcs_total_invoiced_display": "varchar",
"mcs_total_outstanding": "numeric",
+ "mcs_total_outstanding_display": "varchar",
"mcs_total_overdue": "numeric",
+ "mcs_total_overdue_display": "varchar",
"mcs_last_invoice_date": "date",
"mcs_account_owner_id": "integer",
"mcs_next_account_action": "varchar",
@@ -137,7 +158,8 @@ class ResPartner(models.Model):
("move_type", "=", "out_invoice"),
("state", "=", "posted"),
("commercial_partner_id", "=", commercial_partner.id),
- ]
+ ],
+ order="invoice_date desc, id desc",
)
overdue_invoices = invoices.filtered(
lambda invoice: invoice.invoice_date_due
@@ -145,10 +167,10 @@ class ResPartner(models.Model):
and invoice.payment_state not in ("paid", "in_payment", "reversed")
)
invoice_dates = [date for date in invoices.mapped("invoice_date") if date]
- partner.mcs_currency_id = (
- partner.currency_id.id
- or self.env.company.currency_id.id
- )
+ account_company = partner._mcs_resolve_client_company(invoices, projects)
+ account_currency = partner._mcs_resolve_client_currency(invoices, projects, account_company)
+ partner.mcs_client_company_id = account_company.id if account_company else False
+ partner.mcs_currency_id = account_currency.id
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"))
@@ -158,18 +180,70 @@ class ResPartner(models.Model):
else 0
)
partner.mcs_total_invoiced = sum(invoices.mapped("amount_total_signed"))
+ partner.mcs_total_invoiced_display = partner._mcs_invoice_amount_display(
+ invoices, "amount_total", account_currency
+ )
partner.mcs_total_outstanding = sum(
invoices.filtered(
lambda invoice: invoice.payment_state
not in ("paid", "in_payment", "reversed")
).mapped("amount_residual_signed")
)
+ partner.mcs_total_outstanding_display = partner._mcs_invoice_amount_display(
+ invoices.filtered(
+ lambda invoice: invoice.payment_state
+ not in ("paid", "in_payment", "reversed")
+ ),
+ "amount_residual",
+ account_currency,
+ )
partner.mcs_total_overdue = sum(overdue_invoices.mapped("amount_residual_signed"))
+ partner.mcs_total_overdue_display = partner._mcs_invoice_amount_display(
+ overdue_invoices, "amount_residual", account_currency
+ )
partner.mcs_last_invoice_date = max(invoice_dates) if invoice_dates else False
partner.mcs_account_health = partner._mcs_resolve_account_health(
partner._mcs_get_account_health_signals(projects)
)
+ def _mcs_resolve_client_company(self, invoices, projects):
+ company = invoices[:1].company_id if invoices else self.env["res.company"]
+ if not company and projects:
+ company = projects[:1].company_id
+ if not company:
+ company = self.company_id
+ return company
+
+ def _mcs_resolve_client_currency(self, invoices, projects, company):
+ currencies = invoices.mapped("currency_id")
+ if len(currencies) == 1:
+ return currencies
+ if invoices:
+ return invoices[:1].currency_id or invoices[:1].company_currency_id
+ if projects and projects[:1].company_id:
+ return projects[:1].company_id.currency_id
+ if company:
+ return company.currency_id
+ return self.currency_id or self.env.company.currency_id
+
+ def _mcs_invoice_amount_display(self, invoices, amount_field, default_currency):
+ buckets = {}
+ for invoice in invoices:
+ currency = invoice.currency_id or invoice.company_currency_id or default_currency
+ buckets[currency.id] = buckets.get(currency.id, 0) + (invoice[amount_field] or 0)
+ if not buckets and default_currency:
+ buckets[default_currency.id] = 0
+ if not buckets:
+ buckets[self.env.company.currency_id.id] = 0
+ Currency = self.env["res.currency"].sudo()
+ return " / ".join(
+ "%s %s" % (currency.name, "{:,.2f}".format(round(amount or 0, 2)))
+ for currency, amount in sorted(
+ ((Currency.browse(currency_id), amount) for currency_id, amount in buckets.items()),
+ key=lambda item: item[0].name,
+ )
+ )
+
def _mcs_get_account_health_signals(self, projects):
"""Return health signals used to resolve client account health.
diff --git a/addons/mcs_operating_system/views/res_partner_views.xml b/addons/mcs_operating_system/views/res_partner_views.xml
index 16645f9..96b136b 100644
--- a/addons/mcs_operating_system/views/res_partner_views.xml
+++ b/addons/mcs_operating_system/views/res_partner_views.xml
@@ -9,6 +9,7 @@
+
@@ -19,9 +20,9 @@
-
-
-
+
+
+
@@ -50,15 +51,16 @@
+
-
-
-
+
+
+
@@ -66,12 +68,33 @@
+
+ res.partner.search.mcs.client.account
+ res.partner
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Client Accounts
res.partner
tree,form
+
[('mcs_is_client_account','=',True)]
- {'default_mcs_is_client_account': True}
+ {'default_mcs_is_client_account': True, 'group_by': 'mcs_client_company_id'}