Skip to content

Commit 07349e9

Browse files
committed
[IMP] base, *: compute stats for the views
This commit introduces a new field `application_statistics` which computes all stat info related to the partner. We use it in list view and kanban view to show the info. Task-4377720
1 parent 4334733 commit 07349e9

File tree

15 files changed

+129
-89
lines changed

15 files changed

+129
-89
lines changed

addons/account/models/partner.py

+35
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,7 @@ def _default_display_invoice_template_pdf_report_id(self):
559559
ref_company_ids = fields.One2many('res.company', 'partner_id',
560560
string='Companies that refers to partner')
561561
supplier_invoice_count = fields.Integer(compute='_compute_supplier_invoice_count', string='# Vendor Bills')
562+
account_move_count = fields.Integer(compute='_compute_account_move_count', groups='account.group_account_invoice,account.group_account_readonly')
562563
invoice_ids = fields.One2many('account.move', 'partner_id', string='Invoices', readonly=True, copy=False)
563564
contract_ids = fields.One2many('account.analytic.account', 'partner_id', string='Partner Contracts', readonly=True)
564565
bank_account_count = fields.Integer(compute='_compute_bank_count', string="Bank")
@@ -1023,3 +1024,37 @@ def _compute_partner_company_registry_placeholder(self):
10231024
for partner in self:
10241025
country_code = partner.country_id.code or ''
10251026
partner.partner_company_registry_placeholder = _ref_company_registry.get(country_code.lower(), '')
1027+
1028+
def _compute_account_move_count(self):
1029+
# retrieve all children partners and prefetch 'parent_id' on them
1030+
all_partners = self.with_context(active_test=False).search_fetch(
1031+
[("id", "child_of", self.ids)],
1032+
["parent_id"],
1033+
)
1034+
domain = [
1035+
("partner_id", "in", all_partners.ids),
1036+
("move_type", "in", ("out_invoice", "out_refund")),
1037+
]
1038+
account_move_groups = self.env["account.move"]._read_group(
1039+
domain=domain, groupby=["partner_id"], aggregates=["__count"],
1040+
)
1041+
self_ids = set(self._ids)
1042+
1043+
self.account_move_count = 0
1044+
for partner, count in account_move_groups:
1045+
while partner:
1046+
if partner.id in self_ids:
1047+
partner.account_move_count += count
1048+
partner = partner.parent_id
1049+
1050+
def _get_stat_info(self):
1051+
data_list = super()._get_stat_info()
1052+
if not self.env.user.has_group('account.group_account_invoice'):
1053+
return data_list
1054+
for partner in self.filtered(lambda p: p._get_account_stat_count()):
1055+
stat_info = {'iconClass': 'fa-pencil-square-o', 'value': partner._get_account_stat_count(), 'label': _('Invoices/Bills/Mandates')}
1056+
data_list[partner.id].append(stat_info)
1057+
return data_list
1058+
1059+
def _get_account_stat_count(self):
1060+
return self.account_move_count + self.supplier_invoice_count

addons/calendar/models/res_partner.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from collections import defaultdict
44
from datetime import datetime
55

6-
from odoo import api, fields, models
6+
from odoo import _, api, fields, models
77
from odoo.tools import SQL
88

99

@@ -112,3 +112,10 @@ def _get_busy_calendar_events(self, start_datetime, end_datetime):
112112
for partner in event.partner_ids:
113113
event_by_partner_id[partner.id] |= event
114114
return dict(event_by_partner_id)
115+
116+
def _get_stat_info(self):
117+
data_list = super()._get_stat_info()
118+
for partner in self.filtered('meeting_count'):
119+
stat_info = {'iconClass': 'fa-calendar', 'value': partner.meeting_count, 'label': _('Meetings')}
120+
data_list[partner.id].append(stat_info)
121+
return data_list

addons/calendar/views/res_partner_views.xml

-17
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,5 @@
11
<?xml version="1.0"?>
22
<odoo>
3-
4-
<!-- Partner kanban view inherit -->
5-
<record id="res_partner_kanban_view" model="ir.ui.view">
6-
<field name="name">res.partner.view.kanban.calendar</field>
7-
<field name="model">res.partner</field>
8-
<field name="inherit_id" ref="base.res_partner_kanban_view"/>
9-
<field name="priority" eval="10"/>
10-
<field name="arch" type="xml">
11-
<xpath expr="//footer/div" position="inside">
12-
<span t-if="record.meeting_count.raw_value" class="badge bg-secondary mx-1 rounded-pill smaller">
13-
<i class="fa fa-calendar me-1" aria-label="Meetings" role="img" title="Meetings"/>
14-
<field name="meeting_count"/>
15-
</span>
16-
</xpath>
17-
</field>
18-
</record>
19-
203
<!-- Add contextual button on partner form view -->
214
<record id="view_partners_form" model="ir.ui.view">
225
<field name="name">res_partner.view.form.calendar</field>

addons/crm/models/res_partner.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Part of Odoo. See LICENSE file for full copyright and licensing details.
22

3-
from odoo import api, fields, models
3+
from odoo import _, api, fields, models
44
from odoo.osv import expression
55

66

@@ -59,6 +59,15 @@ def _compute_opportunity_count(self):
5959
partner.opportunity_count += count
6060
partner = partner.parent_id
6161

62+
def _get_stat_info(self):
63+
data_list = super()._get_stat_info()
64+
if not self.env.user.has_group('sales_team.group_sale_salesman'):
65+
return data_list
66+
for partner in self.filtered('opportunity_count'):
67+
stat_info = {'iconClass': 'fa-star', 'value': partner.opportunity_count, 'label': _('Opportunities')}
68+
data_list[partner.id].append(stat_info)
69+
return data_list
70+
6271
def action_view_opportunity(self):
6372
action = self.env['ir.actions.act_window']._for_xml_id('crm.crm_lead_opportunities')
6473
action['context'] = {

addons/crm/views/res_partner_views.xml

-19
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,5 @@
11
<?xml version="1.0"?>
22
<odoo>
3-
4-
<!-- Partner kanban view inherit -->
5-
<record id="crm_lead_partner_kanban_view" model="ir.ui.view">
6-
<field name="name">res.partner.kanban.inherit</field>
7-
<field name="model">res.partner</field>
8-
<field name="inherit_id" ref="base.res_partner_kanban_view"/>
9-
<field name="priority" eval="10"/>
10-
<field name="arch" type="xml">
11-
<xpath expr="//footer/div" position="inside">
12-
<span t-if="record.opportunity_count?.raw_value"
13-
class="badge bg-secondary mx-1 rounded-pill smaller"
14-
groups="sales_team.group_sale_salesman">
15-
<i class="fa fa-star me-1" aria-label="Opportunities" role="img" title="Opportunities"/>
16-
<field name="opportunity_count"/>
17-
</span>
18-
</xpath>
19-
</field>
20-
</record>
21-
223
<!-- Add contextual button on partner form view -->
234
<record id="view_partners_form_crm1" model="ir.ui.view">
245
<field name="name">view.res.partner.form.crm.inherited1</field>

addons/point_of_sale/models/res_partner.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Part of Odoo. See LICENSE file for full copyright and licensing details.
2-
from odoo import api, fields, models
2+
from odoo import _, api, fields, models
33

44

55
class ResPartner(models.Model):
@@ -110,3 +110,12 @@ def open_commercial_entity(self):
110110
**super().open_commercial_entity(),
111111
**({'target': 'new'} if self.env.context.get('target') == 'new' else {}),
112112
}
113+
114+
def _get_stat_info(self):
115+
data_list = super()._get_stat_info()
116+
if not self.env.user.has_group('point_of_sale.group_pos_user'):
117+
return data_list
118+
for partner in self.filtered('pos_order_count'):
119+
stat_info = {'iconClass': 'fa-shopping-bag', 'value': partner.pos_order_count, 'label': _('Shopping cart')}
120+
data_list[partner.id].append(stat_info)
121+
return data_list

addons/point_of_sale/views/res_partner_view.xml

-13
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,6 @@
2323
</field>
2424
</record>
2525

26-
<record id="view_partner_pos_kanban" model="ir.ui.view">
27-
<field name="name">res.partner.pos.kanban.inherit</field>
28-
<field name="model">res.partner</field>
29-
<field name="inherit_id" ref="base.res_partner_kanban_view"/>
30-
<field name="arch" type="xml">
31-
<xpath expr="//footer/div" position="inside">
32-
<span t-if="record.pos_order_count.raw_value" groups="point_of_sale.group_pos_user" class="badge bg-secondary mx-1 rounded-pill smaller">
33-
<i class="fa fa-fw fa-shopping-bag me-1" aria-label="Shopping cart" role="img" title="Shopping cart"/>
34-
<field name="pos_order_count"/>
35-
</span>
36-
</xpath>
37-
</field>
38-
</record>
3926
<record id="res_partner_action_edit_pos" model="ir.actions.act_window">
4027
<field name="name">Edit Partner</field>
4128
<field name="res_model">res.partner</field>

addons/purchase/models/res_partner.py

+9
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,12 @@ def _compute_purchase_order_count(self):
4343
reminder_date_before_receipt = fields.Integer('Days Before Receipt', company_dependent=True,
4444
help="Number of days to send reminder email before the promised receipt date")
4545
buyer_id = fields.Many2one('res.users', string='Buyer')
46+
47+
def _get_stat_info(self):
48+
data_list = super()._get_stat_info()
49+
if not self.env.user.has_group('purchase.group_purchase_user'):
50+
return data_list
51+
for partner in self.filtered(lambda partner: partner.purchase_order_count):
52+
stat_info = {'iconClass': 'fa-credit-card', 'value': partner.purchase_order_count, 'label': _('Purchases')}
53+
data_list[partner.id].append(stat_info)
54+
return data_list

addons/purchase/views/res_partner_views.xml

-17
Original file line numberDiff line numberDiff line change
@@ -49,23 +49,6 @@
4949
</field>
5050
</record>
5151

52-
<!-- Partner kanban view inherited -->
53-
<record model="ir.ui.view" id="purchase_partner_kanban_view">
54-
<field name="name">res.partner.kanban.purchaseorder.inherit</field>
55-
<field name="model">res.partner</field>
56-
<field name="inherit_id" ref="base.res_partner_kanban_view"/>
57-
<field name="arch" type="xml">
58-
<xpath expr="//footer/div" position="inside">
59-
<span t-if="record.purchase_order_count?.raw_value"
60-
class="badge bg-secondary mx-1 rounded-pill smaller"
61-
groups="purchase.group_purchase_user">
62-
<i class="fa fa-credit-card me-1" aria-label="Purchases" role="img" title="Purchases"/>
63-
<field name="purchase_order_count"/>
64-
</span>
65-
</xpath>
66-
</field>
67-
</record>
68-
6952
<record id="act_res_partner_2_supplier_invoices" model="ir.actions.act_window">
7053
<field name="name">Vendor Bills</field>
7154
<field name="res_model">account.move</field>

addons/sale/models/res_partner.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Part of Odoo. See LICENSE file for full copyright and licensing details.
22

3-
from odoo import api, fields, models
3+
from odoo import _, api, fields, models
44
from odoo.osv import expression
55

66

@@ -102,3 +102,12 @@ def unlink(self):
102102
('partner_shipping_id', 'in', self.ids),
103103
]).unlink()
104104
return super().unlink()
105+
106+
def _get_stat_info(self):
107+
data_list = super()._get_stat_info()
108+
if not self.env.user.has_group('sales_team.group_sale_salesman'):
109+
return data_list
110+
for partner in self.filtered('sale_order_count'):
111+
stat_info = {'iconClass': 'fa-usd', 'value': partner.sale_order_count, 'label': _('Sale Orders')}
112+
data_list[partner.id].append(stat_info)
113+
return data_list

addons/sale/views/res_partner_views.xml

-18
Original file line numberDiff line numberDiff line change
@@ -17,24 +17,6 @@
1717
</field>
1818
</record>
1919

20-
<record id="crm_lead_partner_kanban_view" model="ir.ui.view">
21-
<field name="name">res.partner.kanban.saleorder.inherit</field>
22-
<field name="model">res.partner</field>
23-
<field name="inherit_id" ref="base.res_partner_kanban_view"/>
24-
<field name="priority" eval="20"/>
25-
<field name="arch" type="xml">
26-
<xpath expr="//footer/div" position="inside">
27-
<span t-if="record.sale_order_count?.raw_value"
28-
class="badge bg-secondary mx-1 rounded-pill smaller"
29-
groups="sales_team.group_sale_salesman"
30-
type="action">
31-
<i class="fa fa-usd me-1" role="img" aria-label="Sale orders" title="Sales orders"/>
32-
<field name="sale_order_count"/>
33-
</span>
34-
</xpath>
35-
</field>
36-
</record>
37-
3820
<record id="res_partner_view_buttons" model="ir.ui.view">
3921
<field name="name">res.partner.view.buttons</field>
4022
<field name="model">res.partner</field>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { registry } from "@web/core/registry";
2+
import { standardFieldProps } from "../standard_field_props";
3+
import { _t } from "@web/core/l10n/translation";
4+
import { Component } from "@odoo/owl";
5+
6+
export class ContactStatisticsField extends Component {
7+
static template = "web.ContactStatisticsField";
8+
static props = {
9+
...standardFieldProps,
10+
};
11+
12+
get list() {
13+
return this.props.record.data[this.props.name] || [];
14+
}
15+
}
16+
17+
export const contactStatisticsField = {
18+
component: ContactStatisticsField,
19+
displayName: _t("Contact Statistics"),
20+
supportedTypes: ["json"],
21+
};
22+
23+
registry.category("fields").add("contact_statistics", contactStatisticsField);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<templates xml:space="preserve">
3+
4+
<t t-name="web.ContactStatisticsField">
5+
<div class="d-flex flex-wrap gap-1">
6+
<span t-foreach="list" t-as="data" t-key="data_index" class="badge bg-secondary rounded-pill smaller">
7+
<i t-attf-class="fa me-1 {{data.iconClass}}" t-att-aria-label="data.label" role="img" t-att-title="data.label"/>
8+
<t t-out="data.value"/>
9+
</span>
10+
</div>
11+
</t>
12+
13+
</templates>

odoo/addons/base/models/res_partner.py

+9
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,15 @@ def default_get(self, default_fields):
292292

293293
# hack to allow using plain browse record in qweb views, and used in ir.qweb.field.contact
294294
self: ResPartner = fields.Many2one(comodel_name='res.partner', compute='_compute_get_ids')
295+
application_statistics = fields.Json(string="Stats", compute="_compute_application_statistics")
296+
297+
def _compute_application_statistics(self):
298+
result = self._get_stat_info()
299+
for p in self:
300+
p.application_statistics = result.get(p.id, [])
301+
302+
def _get_stat_info(self):
303+
return defaultdict(list)
295304

296305
_check_name = models.Constraint(
297306
"CHECK( (type='contact' AND name IS NOT NULL) or (type!='contact') )",

odoo/addons/base/views/res_partner_views.xml

+2-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
<field name="city" optional="hide"/>
2828
<field name="state_id" optional="hide" readonly="1"/>
2929
<field name="country_id" optional="show" readonly="1"/>
30+
<field name="application_statistics" widget="contact_statistics" nolabel="1" optional="show"/>
3031
<field name="category_id" optional="show" widget="many2many_tags" options="{'color_field': 'color'}"/>
3132
<field name="company_id" groups="base.group_multi_company" readonly="1" optional="hide"/>
3233
</list>
@@ -367,7 +368,7 @@
367368
<span t-if="record.city.raw_value and record.country_id.raw_value">, </span>
368369
<field name="country_id"/>
369370
</div>
370-
<footer><div/></footer>
371+
<footer><field name="application_statistics" widget="contact_statistics"/></footer>
371372
</main>
372373
</t>
373374
</templates>

0 commit comments

Comments
 (0)