Fix client account company and currency rollups
This commit is contained in:
parent
20ef8be015
commit
d19fea9dc7
@ -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)
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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.
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
<group>
|
||||
<group string="Account Control">
|
||||
<field name="mcs_is_client_account"/>
|
||||
<field name="mcs_client_company_id"/>
|
||||
<field name="mcs_account_status"/>
|
||||
<field name="mcs_account_health"/>
|
||||
<field name="mcs_account_owner_id"/>
|
||||
@ -19,9 +20,9 @@
|
||||
<field name="mcs_contracted_value"/>
|
||||
<field name="mcs_monthly_hours"/>
|
||||
<field name="mcs_effective_hourly_revenue"/>
|
||||
<field name="mcs_total_invoiced"/>
|
||||
<field name="mcs_total_outstanding"/>
|
||||
<field name="mcs_total_overdue"/>
|
||||
<field name="mcs_total_invoiced_display"/>
|
||||
<field name="mcs_total_outstanding_display"/>
|
||||
<field name="mcs_total_overdue_display"/>
|
||||
<field name="mcs_last_invoice_date"/>
|
||||
</group>
|
||||
</group>
|
||||
@ -50,15 +51,16 @@
|
||||
<field name="arch" type="xml">
|
||||
<tree decoration-danger="mcs_account_health in ('at_risk','blocked')" decoration-warning="mcs_account_health == 'watch'">
|
||||
<field name="display_name"/>
|
||||
<field name="mcs_client_company_id"/>
|
||||
<field name="mcs_account_status"/>
|
||||
<field name="mcs_account_health"/>
|
||||
<field name="mcs_account_owner_id"/>
|
||||
<field name="mcs_currency_id" column_invisible="True"/>
|
||||
<field name="mcs_monthly_revenue"/>
|
||||
<field name="mcs_contracted_value"/>
|
||||
<field name="mcs_total_invoiced"/>
|
||||
<field name="mcs_total_outstanding"/>
|
||||
<field name="mcs_total_overdue"/>
|
||||
<field name="mcs_total_invoiced_display"/>
|
||||
<field name="mcs_total_outstanding_display"/>
|
||||
<field name="mcs_total_overdue_display"/>
|
||||
<field name="mcs_last_invoice_date"/>
|
||||
<field name="mcs_monthly_hours"/>
|
||||
<field name="mcs_next_account_action"/>
|
||||
@ -66,12 +68,33 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_partner_search_mcs_client_account" model="ir.ui.view">
|
||||
<field name="name">res.partner.search.mcs.client.account</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="arch" type="xml">
|
||||
<search>
|
||||
<field name="display_name"/>
|
||||
<field name="mcs_client_company_id"/>
|
||||
<field name="mcs_account_owner_id"/>
|
||||
<field name="mcs_account_status"/>
|
||||
<filter string="Canada Company" name="mcs_company_canada" domain="[('mcs_client_company_id.name','=','Metatroncube Software Solutions')]"/>
|
||||
<filter string="India LLP" name="mcs_company_india" domain="[('mcs_client_company_id.name','=','Metatroncube Software Solutions LLP')]"/>
|
||||
<group expand="0" string="Group By">
|
||||
<filter string="MCS Company" name="group_mcs_client_company" context="{'group_by':'mcs_client_company_id'}"/>
|
||||
<filter string="Currency" name="group_mcs_currency" context="{'group_by':'mcs_currency_id'}"/>
|
||||
<filter string="Health" name="group_mcs_health" context="{'group_by':'mcs_account_health'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_mcs_client_accounts" model="ir.actions.act_window">
|
||||
<field name="name">Client Accounts</field>
|
||||
<field name="res_model">res.partner</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="view_id" ref="view_partner_tree_mcs_client_account"/>
|
||||
<field name="search_view_id" ref="view_partner_search_mcs_client_account"/>
|
||||
<field name="domain">[('mcs_is_client_account','=',True)]</field>
|
||||
<field name="context">{'default_mcs_is_client_account': True}</field>
|
||||
<field name="context">{'default_mcs_is_client_account': True, 'group_by': 'mcs_client_company_id'}</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user