Map operating accounts to existing customers
This commit is contained in:
parent
84b23c268e
commit
7859d6a7a1
@ -16,56 +16,118 @@ class OperatingSystemSetup:
|
|||||||
self.Project = env["project.project"].sudo()
|
self.Project = env["project.project"].sudo()
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
self._promote_existing_customers()
|
||||||
self._ensure_active_portfolio()
|
self._ensure_active_portfolio()
|
||||||
|
self._hide_empty_seed_accounts()
|
||||||
self._ensure_followup_cron_active()
|
self._ensure_followup_cron_active()
|
||||||
|
|
||||||
|
def _promote_existing_customers(self):
|
||||||
|
invoice_partners = self.env["account.move"].sudo().search(
|
||||||
|
[
|
||||||
|
("move_type", "=", "out_invoice"),
|
||||||
|
("state", "=", "posted"),
|
||||||
|
("commercial_partner_id", "!=", False),
|
||||||
|
]
|
||||||
|
).mapped("commercial_partner_id")
|
||||||
|
customers = self.Partner.search([("customer_rank", ">", 0)])
|
||||||
|
for partner in customers | invoice_partners:
|
||||||
|
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
|
||||||
|
partner.write(values)
|
||||||
|
|
||||||
def _ensure_active_portfolio(self):
|
def _ensure_active_portfolio(self):
|
||||||
specs = {
|
specs = {
|
||||||
"ClicksToCart": [
|
"ClicksToCart": {
|
||||||
("ERP Implementation", "fixed_project"),
|
"aliases": ["Clickstocart Inc", "ClicksToCart"],
|
||||||
("App Development", "fixed_project"),
|
"workstreams": [
|
||||||
("Social Media Handling", "recurring"),
|
("ERP Implementation", "fixed_project"),
|
||||||
],
|
("App Development", "fixed_project"),
|
||||||
"Wallaceton": [
|
("Social Media Handling", "recurring"),
|
||||||
("SEO", "recurring"),
|
],
|
||||||
("Social Media Handling", "recurring"),
|
},
|
||||||
("Ads Management", "recurring"),
|
"Wallaceton": {
|
||||||
],
|
"aliases": ["Wallaceton Eye Care", "Wallaceton"],
|
||||||
"Maison de Treats": [("Social Media Handling", "recurring")],
|
"workstreams": [
|
||||||
"Lens and Frames": [
|
("SEO", "recurring"),
|
||||||
("SEO", "recurring"),
|
("Social Media Handling", "recurring"),
|
||||||
("Social Media Handling", "recurring"),
|
("Ads Management", "recurring"),
|
||||||
("Ads Management", "recurring"),
|
],
|
||||||
],
|
},
|
||||||
"TNCSC": [
|
"Maison de Treats": {
|
||||||
("Ads Management", "recurring"),
|
"aliases": ["Maison de Treats"],
|
||||||
("Videos", "fixed_project"),
|
"workstreams": [("Social Media Handling", "recurring")],
|
||||||
("ERP", "fixed_project"),
|
},
|
||||||
],
|
"Lens and Frames": {
|
||||||
"Infini": [("Social Media Handling", "recurring")],
|
"aliases": ["Lens & Frames Optical", "Lens and Frames"],
|
||||||
"Organic Healthy Family": [("Shopify Store Development", "fixed_project")],
|
"workstreams": [
|
||||||
"Hondavert": [
|
("SEO", "recurring"),
|
||||||
("Website Development", "fixed_project"),
|
("Social Media Handling", "recurring"),
|
||||||
("App Development", "fixed_project"),
|
("Ads Management", "recurring"),
|
||||||
],
|
],
|
||||||
"RaceNation": [("Website Development", "fixed_project")],
|
},
|
||||||
"Racewerks": [("Website Development", "fixed_project")],
|
"TNCSC": {
|
||||||
"Internal": [
|
"aliases": ["Tamil Nadu Cultural Society of Canada", "TNCSC"],
|
||||||
("Shopify App for Revenue Generation", "internal_product"),
|
"workstreams": [
|
||||||
("Custom Product Development", "internal_r_and_d"),
|
("Ads Management", "recurring"),
|
||||||
],
|
("Videos", "fixed_project"),
|
||||||
|
("ERP", "fixed_project"),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"Infini": {
|
||||||
|
"aliases": ["INFINI BRAND ENHANCERZ", "Infini"],
|
||||||
|
"workstreams": [("Social Media Handling", "recurring")],
|
||||||
|
},
|
||||||
|
"Organic Healthy Family": {
|
||||||
|
"aliases": ["Organic Healthy Family"],
|
||||||
|
"workstreams": [("Shopify Store Development", "fixed_project")],
|
||||||
|
},
|
||||||
|
"Hondavert": {
|
||||||
|
"aliases": ["Hondavert"],
|
||||||
|
"workstreams": [
|
||||||
|
("Website Development", "fixed_project"),
|
||||||
|
("App Development", "fixed_project"),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"RaceNation": {
|
||||||
|
"aliases": ["RaceNation"],
|
||||||
|
"workstreams": [("Website Development", "fixed_project")],
|
||||||
|
},
|
||||||
|
"Racewerks": {
|
||||||
|
"aliases": ["Racewerks"],
|
||||||
|
"workstreams": [("Website Development", "fixed_project")],
|
||||||
|
},
|
||||||
|
"Internal": {
|
||||||
|
"aliases": ["Internal"],
|
||||||
|
"workstreams": [
|
||||||
|
("Shopify App for Revenue Generation", "internal_product"),
|
||||||
|
("Custom Product Development", "internal_r_and_d"),
|
||||||
|
],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for account_name, workstreams in specs.items():
|
for account_name, spec in specs.items():
|
||||||
partner = self._ensure_client_account(account_name)
|
partner = self._ensure_client_account(account_name, spec["aliases"])
|
||||||
for workstream, delivery_type in workstreams:
|
for workstream, delivery_type in spec["workstreams"]:
|
||||||
self._ensure_project(partner, workstream, delivery_type)
|
self._ensure_project(partner, account_name, workstream, delivery_type)
|
||||||
|
|
||||||
def _ensure_client_account(self, name):
|
def _ensure_client_account(self, name, aliases=None):
|
||||||
partner = self.Partner.search([("name", "=", name)], limit=1)
|
aliases = aliases or [name]
|
||||||
|
partner = self.Partner.browse()
|
||||||
|
for alias in aliases:
|
||||||
|
partner = self.Partner.search([("name", "=", alias)], limit=1)
|
||||||
|
if partner:
|
||||||
|
break
|
||||||
|
if not partner:
|
||||||
|
partner = self.Partner.search(
|
||||||
|
[("name", "ilike", aliases[0]), ("customer_rank", ">", 0)],
|
||||||
|
limit=1,
|
||||||
|
)
|
||||||
if not partner:
|
if not partner:
|
||||||
partner = self.Partner.create(
|
partner = self.Partner.create(
|
||||||
{
|
{
|
||||||
"name": name,
|
"name": aliases[0] if aliases else name,
|
||||||
"company_type": "company",
|
"company_type": "company",
|
||||||
"mcs_is_client_account": True,
|
"mcs_is_client_account": True,
|
||||||
"mcs_account_status": "active",
|
"mcs_account_status": "active",
|
||||||
@ -77,18 +139,34 @@ class OperatingSystemSetup:
|
|||||||
values["mcs_is_client_account"] = True
|
values["mcs_is_client_account"] = True
|
||||||
if not partner.mcs_account_status:
|
if not partner.mcs_account_status:
|
||||||
values["mcs_account_status"] = "active"
|
values["mcs_account_status"] = "active"
|
||||||
|
if partner.currency_id and not partner.mcs_currency_id:
|
||||||
|
values["mcs_currency_id"] = partner.currency_id.id
|
||||||
if values:
|
if values:
|
||||||
partner.write(values)
|
partner.write(values)
|
||||||
return partner
|
return partner
|
||||||
|
|
||||||
def _ensure_project(self, partner, workstream, delivery_type):
|
def _ensure_project(self, partner, account_name, workstream, delivery_type):
|
||||||
|
is_internal = partner.name == "Internal"
|
||||||
project_name = "%s - %s" % (partner.name, workstream)
|
project_name = "%s - %s" % (partner.name, workstream)
|
||||||
project = self.Project.search([("name", "=", project_name)], limit=1)
|
legacy_project_name = "%s - %s" % (account_name, workstream)
|
||||||
|
project = self.Project.search(
|
||||||
|
[
|
||||||
|
"|",
|
||||||
|
("name", "=", project_name),
|
||||||
|
("name", "=", legacy_project_name),
|
||||||
|
],
|
||||||
|
limit=1,
|
||||||
|
)
|
||||||
|
if not project:
|
||||||
|
project = self.Project.search(
|
||||||
|
[("partner_id", "=", partner.id), ("name", "ilike", workstream)],
|
||||||
|
limit=1,
|
||||||
|
)
|
||||||
if not project:
|
if not project:
|
||||||
project = self.Project.create(
|
project = self.Project.create(
|
||||||
{
|
{
|
||||||
"name": project_name,
|
"name": project_name,
|
||||||
"partner_id": partner.id if partner.name != "Internal" else False,
|
"partner_id": partner.id if not is_internal else False,
|
||||||
"allow_timesheets": True,
|
"allow_timesheets": True,
|
||||||
"allow_milestones": True,
|
"allow_milestones": True,
|
||||||
"mcs_delivery_type": delivery_type,
|
"mcs_delivery_type": delivery_type,
|
||||||
@ -105,7 +183,9 @@ class OperatingSystemSetup:
|
|||||||
return project
|
return project
|
||||||
|
|
||||||
values = {}
|
values = {}
|
||||||
if not project.partner_id and partner.name != "Internal":
|
if not project.partner_id and not is_internal:
|
||||||
|
values["partner_id"] = partner.id
|
||||||
|
if project.partner_id and project.partner_id != partner and not is_internal:
|
||||||
values["partner_id"] = partner.id
|
values["partner_id"] = partner.id
|
||||||
if not project.mcs_delivery_type:
|
if not project.mcs_delivery_type:
|
||||||
values["mcs_delivery_type"] = delivery_type
|
values["mcs_delivery_type"] = delivery_type
|
||||||
@ -137,3 +217,34 @@ class OperatingSystemSetup:
|
|||||||
)
|
)
|
||||||
if cron and not cron.active:
|
if cron and not cron.active:
|
||||||
cron.sudo().write({"active": True})
|
cron.sudo().write({"active": True})
|
||||||
|
|
||||||
|
def _hide_empty_seed_accounts(self):
|
||||||
|
names = [
|
||||||
|
"ClicksToCart",
|
||||||
|
"Wallaceton",
|
||||||
|
"Lens and Frames",
|
||||||
|
"TNCSC",
|
||||||
|
"Infini",
|
||||||
|
"Organic Healthy Family",
|
||||||
|
"Hondavert",
|
||||||
|
"RaceNation",
|
||||||
|
"Racewerks",
|
||||||
|
]
|
||||||
|
for partner in self.Partner.search(
|
||||||
|
[
|
||||||
|
("name", "in", names),
|
||||||
|
("customer_rank", "=", 0),
|
||||||
|
("mcs_is_client_account", "=", True),
|
||||||
|
]
|
||||||
|
):
|
||||||
|
has_invoice = bool(
|
||||||
|
self.env["account.move"].sudo().search_count(
|
||||||
|
[
|
||||||
|
("move_type", "=", "out_invoice"),
|
||||||
|
("state", "=", "posted"),
|
||||||
|
("commercial_partner_id", "=", partner.commercial_partner_id.id),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if not has_invoice and not partner.mcs_project_ids:
|
||||||
|
partner.write({"mcs_is_client_account": False})
|
||||||
|
|||||||
@ -440,15 +440,26 @@ class McsOperatingDashboard(models.Model):
|
|||||||
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)
|
||||||
if not projects:
|
if (
|
||||||
|
not projects
|
||||||
|
and not partner.mcs_total_invoiced
|
||||||
|
and not partner.mcs_total_outstanding
|
||||||
|
):
|
||||||
continue
|
continue
|
||||||
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 "",
|
||||||
"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,
|
||||||
|
"outstanding": partner.mcs_total_outstanding,
|
||||||
|
"overdue": partner.mcs_total_overdue,
|
||||||
|
"last_invoice_date": fields.Date.to_string(partner.mcs_last_invoice_date)
|
||||||
|
if partner.mcs_last_invoice_date
|
||||||
|
else "",
|
||||||
"hours": sum(projects.mapped("mcs_actual_hours")),
|
"hours": sum(projects.mapped("mcs_actual_hours")),
|
||||||
"next_milestone": next(
|
"next_milestone": next(
|
||||||
(project.mcs_next_milestone for project in projects if project.mcs_next_milestone),
|
(project.mcs_next_milestone for project in projects if project.mcs_next_milestone),
|
||||||
|
|||||||
@ -58,6 +58,25 @@ class ResPartner(models.Model):
|
|||||||
compute="_compute_mcs_account_metrics",
|
compute="_compute_mcs_account_metrics",
|
||||||
store=True,
|
store=True,
|
||||||
)
|
)
|
||||||
|
mcs_total_invoiced = fields.Monetary(
|
||||||
|
string="Total Invoiced",
|
||||||
|
currency_field="mcs_currency_id",
|
||||||
|
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_overdue = fields.Monetary(
|
||||||
|
string="Overdue Receivables",
|
||||||
|
currency_field="mcs_currency_id",
|
||||||
|
compute="_compute_mcs_account_metrics",
|
||||||
|
)
|
||||||
|
mcs_last_invoice_date = fields.Date(
|
||||||
|
string="Last Invoice Date",
|
||||||
|
compute="_compute_mcs_account_metrics",
|
||||||
|
)
|
||||||
mcs_account_owner_id = fields.Many2one(
|
mcs_account_owner_id = fields.Many2one(
|
||||||
"res.users", string="Account Owner", tracking=True
|
"res.users", string="Account Owner", tracking=True
|
||||||
)
|
)
|
||||||
@ -85,6 +104,10 @@ class ResPartner(models.Model):
|
|||||||
"mcs_contracted_value": "numeric",
|
"mcs_contracted_value": "numeric",
|
||||||
"mcs_monthly_hours": "double precision",
|
"mcs_monthly_hours": "double precision",
|
||||||
"mcs_effective_hourly_revenue": "numeric",
|
"mcs_effective_hourly_revenue": "numeric",
|
||||||
|
"mcs_total_invoiced": "numeric",
|
||||||
|
"mcs_total_outstanding": "numeric",
|
||||||
|
"mcs_total_overdue": "numeric",
|
||||||
|
"mcs_last_invoice_date": "date",
|
||||||
"mcs_account_owner_id": "integer",
|
"mcs_account_owner_id": "integer",
|
||||||
"mcs_next_account_action": "varchar",
|
"mcs_next_account_action": "varchar",
|
||||||
"mcs_account_risk_reason": "text",
|
"mcs_account_risk_reason": "text",
|
||||||
@ -101,10 +124,31 @@ class ResPartner(models.Model):
|
|||||||
"mcs_project_ids.mcs_actual_hours",
|
"mcs_project_ids.mcs_actual_hours",
|
||||||
"mcs_project_ids.mcs_portfolio_health",
|
"mcs_project_ids.mcs_portfolio_health",
|
||||||
"mcs_project_ids.active",
|
"mcs_project_ids.active",
|
||||||
|
"currency_id",
|
||||||
)
|
)
|
||||||
def _compute_mcs_account_metrics(self):
|
def _compute_mcs_account_metrics(self):
|
||||||
|
today = fields.Date.context_today(self)
|
||||||
|
Invoice = self.env["account.move"].sudo()
|
||||||
for partner in self:
|
for partner in self:
|
||||||
projects = partner.mcs_project_ids.filtered(lambda project: project.active)
|
projects = partner.mcs_project_ids.filtered(lambda project: project.active)
|
||||||
|
commercial_partner = partner.commercial_partner_id
|
||||||
|
invoices = Invoice.search(
|
||||||
|
[
|
||||||
|
("move_type", "=", "out_invoice"),
|
||||||
|
("state", "=", "posted"),
|
||||||
|
("commercial_partner_id", "=", commercial_partner.id),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
overdue_invoices = invoices.filtered(
|
||||||
|
lambda invoice: invoice.invoice_date_due
|
||||||
|
and invoice.invoice_date_due < today
|
||||||
|
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
|
||||||
|
)
|
||||||
partner.mcs_monthly_revenue = sum(projects.mapped("mcs_monthly_revenue_target"))
|
partner.mcs_monthly_revenue = sum(projects.mapped("mcs_monthly_revenue_target"))
|
||||||
partner.mcs_contracted_value = sum(projects.mapped("mcs_contracted_revenue"))
|
partner.mcs_contracted_value = sum(projects.mapped("mcs_contracted_revenue"))
|
||||||
partner.mcs_monthly_hours = sum(projects.mapped("mcs_actual_hours"))
|
partner.mcs_monthly_hours = sum(projects.mapped("mcs_actual_hours"))
|
||||||
@ -113,6 +157,15 @@ class ResPartner(models.Model):
|
|||||||
if partner.mcs_monthly_hours
|
if partner.mcs_monthly_hours
|
||||||
else 0
|
else 0
|
||||||
)
|
)
|
||||||
|
partner.mcs_total_invoiced = sum(invoices.mapped("amount_total_signed"))
|
||||||
|
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_overdue = sum(overdue_invoices.mapped("amount_residual_signed"))
|
||||||
|
partner.mcs_last_invoice_date = max(invoice_dates) if invoice_dates else False
|
||||||
partner.mcs_account_health = partner._mcs_resolve_account_health(
|
partner.mcs_account_health = partner._mcs_resolve_account_health(
|
||||||
partner._mcs_get_account_health_signals(projects)
|
partner._mcs_get_account_health_signals(projects)
|
||||||
)
|
)
|
||||||
|
|||||||
@ -237,10 +237,10 @@
|
|||||||
.o_mcs_table_head,
|
.o_mcs_table_head,
|
||||||
.o_mcs_table_row {
|
.o_mcs_table_row {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1.3fr 92px 70px 90px 70px minmax(180px, 1.2fr);
|
grid-template-columns: 1.3fr 92px 70px 70px 100px 100px 100px 96px minmax(180px, 1.2fr);
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
min-width: 760px;
|
min-width: 980px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.o_mcs_table_head {
|
.o_mcs_table_head {
|
||||||
|
|||||||
@ -77,15 +77,18 @@
|
|||||||
</header>
|
</header>
|
||||||
<div class="o_mcs_table">
|
<div class="o_mcs_table">
|
||||||
<div class="o_mcs_table_head">
|
<div class="o_mcs_table_head">
|
||||||
<span>Client</span><span>Health</span><span>Streams</span><span>Revenue</span><span>Hours</span><span>Next Action</span>
|
<span>Client</span><span>Health</span><span>Currency</span><span>Streams</span><span>Invoiced</span><span>Outstanding</span><span>Overdue</span><span>Last Invoice</span><span>Next Action</span>
|
||||||
</div>
|
</div>
|
||||||
<t t-foreach="state.data.portfolio" t-as="row" t-key="row.id">
|
<t t-foreach="state.data.portfolio" t-as="row" t-key="row.id">
|
||||||
<button class="o_mcs_table_row" t-on-click="() => this.openClient(row.id)">
|
<button class="o_mcs_table_row" t-on-click="() => this.openClient(row.id)">
|
||||||
<span t-esc="row.name"/>
|
<span t-esc="row.name"/>
|
||||||
<span t-attf-class="o_mcs_badge o_mcs_status_{{ row.health }}" t-esc="row.health"/>
|
<span t-attf-class="o_mcs_badge o_mcs_status_{{ row.health }}" t-esc="row.health"/>
|
||||||
|
<span t-esc="row.currency"/>
|
||||||
<span t-esc="row.active_workstreams"/>
|
<span t-esc="row.active_workstreams"/>
|
||||||
<span t-esc="row.revenue"/>
|
<span t-esc="row.total_invoiced"/>
|
||||||
<span t-esc="row.hours"/>
|
<span t-esc="row.outstanding"/>
|
||||||
|
<span t-esc="row.overdue"/>
|
||||||
|
<span t-esc="row.last_invoice_date"/>
|
||||||
<span t-esc="row.next_action"/>
|
<span t-esc="row.next_action"/>
|
||||||
</button>
|
</button>
|
||||||
</t>
|
</t>
|
||||||
|
|||||||
@ -35,6 +35,12 @@ class TestCeoDashboardV2(TransactionCase):
|
|||||||
self.assertEqual(self.partner.mcs_monthly_revenue, 100000)
|
self.assertEqual(self.partner.mcs_monthly_revenue, 100000)
|
||||||
self.assertEqual(self.partner.mcs_contracted_value, 80000)
|
self.assertEqual(self.partner.mcs_contracted_value, 80000)
|
||||||
|
|
||||||
|
def test_client_account_invoice_rollup_fields_exist(self):
|
||||||
|
self.assertIn("mcs_total_invoiced", self.partner._fields)
|
||||||
|
self.assertIn("mcs_total_outstanding", self.partner._fields)
|
||||||
|
self.assertIn("mcs_total_overdue", self.partner._fields)
|
||||||
|
self.assertIn("mcs_last_invoice_date", self.partner._fields)
|
||||||
|
|
||||||
def test_account_health_uses_extensible_signals(self):
|
def test_account_health_uses_extensible_signals(self):
|
||||||
signals = self.partner._mcs_get_account_health_signals(self.project)
|
signals = self.partner._mcs_get_account_health_signals(self.project)
|
||||||
self.assertIn("project_health_values", signals)
|
self.assertIn("project_health_values", signals)
|
||||||
|
|||||||
@ -14,11 +14,15 @@
|
|||||||
<field name="mcs_account_owner_id"/>
|
<field name="mcs_account_owner_id"/>
|
||||||
</group>
|
</group>
|
||||||
<group string="Commercial Rollup">
|
<group string="Commercial Rollup">
|
||||||
<field name="mcs_currency_id" invisible="1"/>
|
<field name="mcs_currency_id"/>
|
||||||
<field name="mcs_monthly_revenue"/>
|
<field name="mcs_monthly_revenue"/>
|
||||||
<field name="mcs_contracted_value"/>
|
<field name="mcs_contracted_value"/>
|
||||||
<field name="mcs_monthly_hours"/>
|
<field name="mcs_monthly_hours"/>
|
||||||
<field name="mcs_effective_hourly_revenue"/>
|
<field name="mcs_effective_hourly_revenue"/>
|
||||||
|
<field name="mcs_total_invoiced"/>
|
||||||
|
<field name="mcs_total_outstanding"/>
|
||||||
|
<field name="mcs_total_overdue"/>
|
||||||
|
<field name="mcs_last_invoice_date"/>
|
||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
<group>
|
<group>
|
||||||
@ -52,6 +56,10 @@
|
|||||||
<field name="mcs_currency_id" column_invisible="True"/>
|
<field name="mcs_currency_id" column_invisible="True"/>
|
||||||
<field name="mcs_monthly_revenue"/>
|
<field name="mcs_monthly_revenue"/>
|
||||||
<field name="mcs_contracted_value"/>
|
<field name="mcs_contracted_value"/>
|
||||||
|
<field name="mcs_total_invoiced"/>
|
||||||
|
<field name="mcs_total_outstanding"/>
|
||||||
|
<field name="mcs_total_overdue"/>
|
||||||
|
<field name="mcs_last_invoice_date"/>
|
||||||
<field name="mcs_monthly_hours"/>
|
<field name="mcs_monthly_hours"/>
|
||||||
<field name="mcs_next_account_action"/>
|
<field name="mcs_next_account_action"/>
|
||||||
</tree>
|
</tree>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user