[IMP] digest, *: compute the KPI in batch
*: account, crm, hr_recruitement, im_livechat, point_of_sale, project, sale_management, website_sale Purpose ======= Now, the KPI are computed based on their `company_id` and not based on the current company. If no company is set on the digest, we compute it based on the current company. The KPIs are computed based on their company. Most of the time, they will all belong to the same company so we can improve the performance by computing them in batch. Task-2827996 See odoo/enterprise/pull/27658 Part-of: odoo/odoo#91945
This commit is contained in:
@@ -14,19 +14,33 @@ class Digest(models.Model):
|
||||
def _compute_kpi_account_total_revenue_value(self):
|
||||
if not self.env.user.has_group('account.group_account_invoice'):
|
||||
raise AccessError(_("Do not have access, skip this data for user's digest email"))
|
||||
|
||||
start, end, companies = self._get_kpi_compute_parameters()
|
||||
|
||||
self._cr.execute("""
|
||||
SELECT line.company_id AS company, -SUM(line.balance) AS total
|
||||
FROM account_move_line line
|
||||
JOIN account_move move
|
||||
ON move.id = line.move_id
|
||||
JOIN account_account account
|
||||
ON account.id = line.account_id
|
||||
WHERE line.company_id = ANY(%s)
|
||||
AND line.date > %s::DATE
|
||||
AND line.date <= %s::DATE
|
||||
AND account.internal_group = 'income'
|
||||
AND move.state = 'posted'
|
||||
GROUP BY line.company_id
|
||||
""", [companies.ids, start, end])
|
||||
|
||||
result = self._cr.dictfetchall()
|
||||
total_per_companies = {
|
||||
values['company']: values['total']
|
||||
for values in result
|
||||
}
|
||||
|
||||
for record in self:
|
||||
start, end, company = record._get_kpi_compute_parameters()
|
||||
self._cr.execute('''
|
||||
SELECT -SUM(line.balance)
|
||||
FROM account_move_line line
|
||||
JOIN account_move move ON move.id = line.move_id
|
||||
JOIN account_account account ON account.id = line.account_id
|
||||
WHERE line.company_id = %s AND line.date > %s::DATE AND line.date <= %s::DATE
|
||||
AND account.internal_group = 'income'
|
||||
AND move.state = 'posted'
|
||||
''', [company.id, start, end])
|
||||
query_res = self._cr.fetchone()
|
||||
record.kpi_account_total_revenue_value = query_res and query_res[0] or 0.0
|
||||
company = record.company_id or self.env.company
|
||||
record.kpi_account_total_revenue_value = total_per_companies.get(company.id, 0)
|
||||
|
||||
def _compute_kpis_actions(self, company, user):
|
||||
res = super(Digest, self)._compute_kpis_actions(company, user)
|
||||
|
||||
@@ -22,6 +22,7 @@ from . import test_account_invoice_report
|
||||
from . import test_account_move_line_tax_details
|
||||
from . import test_account_journal_dashboard
|
||||
from . import test_chart_template
|
||||
from . import test_digest
|
||||
from . import test_fiscal_position
|
||||
from . import test_sequence_mixin
|
||||
from . import test_tax
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.digest.tests.common import TestDigestCommon
|
||||
from odoo.tools import mute_logger
|
||||
from odoo.tests import tagged
|
||||
|
||||
|
||||
@tagged('post_install')
|
||||
class TestAccountDigest(TestDigestCommon):
|
||||
|
||||
@classmethod
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
account1 = cls.env['account.account'].search([('internal_group', '=', 'income')], limit=1)
|
||||
account2 = cls.env['account.account'].search([('internal_group', '!=', 'income')], limit=1)
|
||||
cls.env['account.journal'].with_company(cls.company_2).create({
|
||||
'name': 'Test Journal',
|
||||
'code': 'code',
|
||||
'type': 'general',
|
||||
})
|
||||
|
||||
comp2_account, comp2_account2 = cls.env['account.account'].create([{
|
||||
'name': 'Account 1 Company 2',
|
||||
'internal_group': 'income',
|
||||
'company_id': cls.company_2.id,
|
||||
'user_type_id': cls.env.ref('account.data_account_type_revenue').id,
|
||||
'code': 'code_account_1_company_2',
|
||||
}, {
|
||||
'name': 'Account 2 Company 2',
|
||||
'internal_group': 'expense',
|
||||
'company_id': cls.company_2.id,
|
||||
'user_type_id': cls.env.ref('account.data_account_type_expenses').id,
|
||||
'code': 'code_account_2_company_2',
|
||||
}])
|
||||
|
||||
moves = cls.env['account.move'].create({
|
||||
'line_ids': [
|
||||
(0, 0, {'debit': 5, 'credit': 0, 'account_id': account1.id}),
|
||||
(0, 0, {'debit': 0, 'credit': 5, 'account_id': account2.id}),
|
||||
(0, 0, {'debit': 8, 'credit': 0, 'account_id': account1.id}),
|
||||
(0, 0, {'debit': 0, 'credit': 8, 'account_id': account2.id}),
|
||||
],
|
||||
})
|
||||
|
||||
moves |= cls.env['account.move'].with_company(cls.company_2).create({
|
||||
'line_ids': [
|
||||
(0, 0, {'debit': 0, 'credit': 2, 'account_id': comp2_account.id}),
|
||||
(0, 0, {'debit': 2, 'credit': 0, 'account_id': comp2_account2.id}),
|
||||
],
|
||||
})
|
||||
|
||||
moves.state = 'posted'
|
||||
|
||||
def test_kpi_account_total_revenue_value(self):
|
||||
self.assertEqual(int(self.digest_1.kpi_account_total_revenue_value), -13)
|
||||
self.assertEqual(int(self.digest_2.kpi_account_total_revenue_value), -2)
|
||||
self.assertEqual(int(self.digest_3.kpi_account_total_revenue_value), -13)
|
||||
|
||||
self.digest_3.invalidate_cache()
|
||||
self.assertEqual(
|
||||
int(self.digest_3.with_company(self.company_2).kpi_account_total_revenue_value),
|
||||
-2,
|
||||
msg='When no company is set, the KPI must be computed based on the current company',
|
||||
)
|
||||
@@ -16,26 +16,19 @@ class Digest(models.Model):
|
||||
def _compute_kpi_crm_lead_created_value(self):
|
||||
if not self.env.user.has_group('sales_team.group_sale_salesman'):
|
||||
raise AccessError(_("Do not have access, skip this data for user's digest email"))
|
||||
for record in self:
|
||||
start, end, company = record._get_kpi_compute_parameters()
|
||||
record.kpi_crm_lead_created_value = self.env['crm.lead'].search_count([
|
||||
('create_date', '>=', start),
|
||||
('create_date', '<', end),
|
||||
('company_id', '=', company.id)
|
||||
])
|
||||
|
||||
self._calculate_company_based_kpi('crm.lead', 'kpi_crm_lead_created_value')
|
||||
|
||||
def _compute_kpi_crm_opportunities_won_value(self):
|
||||
if not self.env.user.has_group('sales_team.group_sale_salesman'):
|
||||
raise AccessError(_("Do not have access, skip this data for user's digest email"))
|
||||
for record in self:
|
||||
start, end, company = record._get_kpi_compute_parameters()
|
||||
record.kpi_crm_opportunities_won_value = self.env['crm.lead'].search_count([
|
||||
('type', '=', 'opportunity'),
|
||||
('probability', '=', '100'),
|
||||
('date_closed', '>=', start),
|
||||
('date_closed', '<', end),
|
||||
('company_id', '=', company.id)
|
||||
])
|
||||
|
||||
self._calculate_company_based_kpi(
|
||||
'crm.lead',
|
||||
'kpi_crm_opportunities_won_value',
|
||||
date_field='date_closed',
|
||||
additional_domain=[('type', '=', 'opportunity'), ('probability', '=', '100')],
|
||||
)
|
||||
|
||||
def _compute_kpis_actions(self, company, user):
|
||||
res = super(Digest, self)._compute_kpis_actions(company, user)
|
||||
|
||||
@@ -14,6 +14,7 @@ from . import test_crm_lead_multicompany
|
||||
from . import test_crm_lead_smart_calendar
|
||||
from . import test_crm_ui
|
||||
from . import test_crm_pls
|
||||
from . import test_digest
|
||||
from . import test_performances
|
||||
from . import test_res_partner
|
||||
from . import test_sales_team_ui
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from odoo.addons.digest.tests.common import TestDigestCommon
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
class TestCrmDigest(TestDigestCommon):
|
||||
@classmethod
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
cls.env['crm.lead'].search([]).unlink()
|
||||
|
||||
cls.env['crm.lead'].create([{
|
||||
'name': 'Lead 1',
|
||||
'company_id': cls.company_1.id,
|
||||
'probability': 100,
|
||||
'type': 'opportunity',
|
||||
'date_closed': datetime.now(),
|
||||
}, {
|
||||
'name': 'Lead 2',
|
||||
'company_id': cls.company_1.id,
|
||||
'probability': 90,
|
||||
'type': 'opportunity',
|
||||
'date_closed': datetime.now(),
|
||||
}, {
|
||||
'name': 'Lead 3',
|
||||
'company_id': False,
|
||||
'probability': 100,
|
||||
'type': 'opportunity',
|
||||
'date_closed': datetime.now(),
|
||||
}, {
|
||||
'name': 'Lead 4',
|
||||
'company_id': cls.company_1.id,
|
||||
'probability': 100,
|
||||
'type': 'opportunity',
|
||||
'date_closed': datetime.now() - timedelta(days=700),
|
||||
}])
|
||||
|
||||
def test_kpi_crm_lead_created_value(self):
|
||||
self.assertEqual(self.digest_1.kpi_crm_lead_created_value, 3)
|
||||
self.assertEqual(self.digest_2.kpi_crm_lead_created_value, 0,
|
||||
msg='This digest is in a different company')
|
||||
self.assertEqual(self.digest_3.kpi_crm_lead_created_value, 3,
|
||||
msg='This digest has no company, should take the current one')
|
||||
|
||||
self.digest_3.invalidate_recordset()
|
||||
self.assertEqual(
|
||||
self.digest_3.with_company(self.company_2).kpi_crm_lead_created_value,
|
||||
0,
|
||||
)
|
||||
@@ -12,6 +12,7 @@ from werkzeug.urls import url_join
|
||||
from odoo import api, fields, models, tools, _
|
||||
from odoo.addons.base.models.ir_mail_server import MailDeliveryException
|
||||
from odoo.exceptions import AccessError
|
||||
from odoo.osv import expression
|
||||
from odoo.tools.float_utils import float_round
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
@@ -55,20 +56,33 @@ class Digest(models.Model):
|
||||
digest.available_fields = ', '.join(kpis_values_fields)
|
||||
|
||||
def _get_kpi_compute_parameters(self):
|
||||
return fields.Datetime.to_string(self._context.get('start_datetime')), fields.Datetime.to_string(self._context.get('end_datetime')), self.env.company
|
||||
"""Get the parameters used to computed the KPI value."""
|
||||
companies = self.company_id
|
||||
if any(not digest.company_id for digest in self):
|
||||
# No company: we will use the current company to compute the KPIs
|
||||
companies |= self.env.company
|
||||
|
||||
return (
|
||||
fields.Datetime.to_string(self.env.context.get('start_datetime')),
|
||||
fields.Datetime.to_string(self.env.context.get('end_datetime')),
|
||||
companies,
|
||||
)
|
||||
|
||||
def _compute_kpi_res_users_connected_value(self):
|
||||
for record in self:
|
||||
start, end, company = record._get_kpi_compute_parameters()
|
||||
user_connected = self.env['res.users'].search_count([('company_id', '=', company.id), ('login_date', '>=', start), ('login_date', '<', end)])
|
||||
record.kpi_res_users_connected_value = user_connected
|
||||
self._calculate_company_based_kpi(
|
||||
'res.users',
|
||||
'kpi_res_users_connected_value',
|
||||
date_field='login_date',
|
||||
)
|
||||
|
||||
def _compute_kpi_mail_message_total_value(self):
|
||||
discussion_subtype_id = self.env.ref('mail.mt_comment').id
|
||||
for record in self:
|
||||
start, end, company = record._get_kpi_compute_parameters()
|
||||
total_messages = self.env['mail.message'].search_count([('create_date', '>=', start), ('create_date', '<', end), ('subtype_id', '=', discussion_subtype_id), ('message_type', 'in', ['comment', 'email'])])
|
||||
record.kpi_mail_message_total_value = total_messages
|
||||
start, end, __ = self._get_kpi_compute_parameters()
|
||||
self.kpi_mail_message_total_value = self.env['mail.message'].search_count([
|
||||
('create_date', '>=', start),
|
||||
('create_date', '<', end),
|
||||
('subtype_id', '=', self.env.ref('mail.mt_comment').id),
|
||||
('message_type', 'in', ('comment', 'email')),
|
||||
])
|
||||
|
||||
@api.onchange('periodicity')
|
||||
def _onchange_periodicity(self):
|
||||
@@ -275,6 +289,9 @@ class Digest(models.Model):
|
||||
if self._fields['%s_value' % field_name].type == 'monetary':
|
||||
converted_amount = tools.format_decimalized_amount(compute_value)
|
||||
compute_value = self._format_currency_amount(converted_amount, company.currency_id)
|
||||
elif self._fields['%s_value' % field_name].type == 'float':
|
||||
compute_value = "%.2f" % compute_value
|
||||
|
||||
kpi_values['kpi_col%s' % (col_index + 1)].update({
|
||||
'value': compute_value,
|
||||
'margin': margin,
|
||||
@@ -374,6 +391,43 @@ class Digest(models.Model):
|
||||
# FORMATTING / TOOLS
|
||||
# ------------------------------------------------------------
|
||||
|
||||
def _calculate_company_based_kpi(self, model, digest_kpi_field, date_field='create_date',
|
||||
additional_domain=None, sum_field=None):
|
||||
"""Generic method that computes the KPI on a given model.
|
||||
|
||||
:param model: Model on which we will compute the KPI
|
||||
This model must have a "company_id" field
|
||||
:param digest_kpi_field: Field name on which we will write the KPI
|
||||
:param date_field: Field used for the date range
|
||||
:param additional_domain: Additional domain
|
||||
:param sum_field: Field to sum to obtain the KPI,
|
||||
if None it will count the number of records
|
||||
"""
|
||||
start, end, companies = self._get_kpi_compute_parameters()
|
||||
|
||||
base_domain = [
|
||||
('company_id', 'in', companies.ids),
|
||||
(date_field, '>=', start),
|
||||
(date_field, '<', end),
|
||||
]
|
||||
|
||||
if additional_domain:
|
||||
base_domain = expression.AND([base_domain, additional_domain])
|
||||
|
||||
values = self.env[model]._read_group(
|
||||
domain=base_domain,
|
||||
fields=[f'{sum_field}:sum'] if sum_field else None,
|
||||
groupby=['company_id'],
|
||||
)
|
||||
|
||||
values_per_company = {
|
||||
value['company_id'][0]: value[sum_field] if sum_field else value['company_id_count']
|
||||
for value in values
|
||||
}
|
||||
for digest in self:
|
||||
company = digest.company_id or self.env.company
|
||||
digest[digest_kpi_field] = values_per_company.get(company.id, 0)
|
||||
|
||||
def _get_kpi_fields(self):
|
||||
return [field_name for field_name, field in self._fields.items()
|
||||
if field.type == 'boolean' and field_name.startswith(('kpi_', 'x_kpi_', 'x_studio_kpi_')) and self[field_name]
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import itertools
|
||||
import random
|
||||
|
||||
from datetime import datetime
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from odoo import fields
|
||||
from odoo.addons.mail.tests import common as mail_test
|
||||
|
||||
|
||||
class TestDigestCommon(mail_test.MailCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
cls.company_1 = cls.env.company
|
||||
cls.company_2 = cls.env['res.company'].create({'name': 'Digest Company 2'})
|
||||
|
||||
context = {
|
||||
'start_datetime': datetime.now() - relativedelta(days=1),
|
||||
'end_datetime': datetime.now() + relativedelta(days=1),
|
||||
}
|
||||
|
||||
cls.all_digests = cls.env['digest.digest'].with_context(context).create([{
|
||||
'name': 'Digest 1',
|
||||
'company_id': cls.env.company.id,
|
||||
'kpi_mail_message_total': True,
|
||||
'kpi_res_users_connected': True,
|
||||
'periodicity': 'daily',
|
||||
}, {
|
||||
'name': 'Digest 2',
|
||||
'company_id': cls.company_2.id,
|
||||
}, {
|
||||
'name': 'Digest 3',
|
||||
'company_id': False,
|
||||
}])
|
||||
|
||||
cls.digest_1, cls.digest_2, cls.digest_3 = cls.all_digests
|
||||
|
||||
@classmethod
|
||||
def _setup_messages(cls):
|
||||
""" Remove all existing messages, then create a bunch of them on random
|
||||
partners with the correct types in correct time-bucket:
|
||||
|
||||
- 3 in the previous 24h
|
||||
- 5 more in the 6 days before that for a total of 8 in the previous week
|
||||
- 7 more in the 20 days before *that* (because digest doc lies and is
|
||||
based around weeks and months not days), for a total of 15 in the
|
||||
previous month
|
||||
"""
|
||||
# regular employee can't necessarily access "private" addresses
|
||||
partners = cls.env['res.partner'].search([('type', '!=', 'private')])
|
||||
messages = cls.env['mail.message']
|
||||
counter = itertools.count()
|
||||
|
||||
now = fields.Datetime.now()
|
||||
for count, (low, high) in [
|
||||
(3, (0 * 24, 1 * 24)),
|
||||
(5, (1 * 24, 7 * 24)),
|
||||
(7, (7 * 24, 27 * 24)),
|
||||
]:
|
||||
for __ in range(count):
|
||||
create_date = now - relativedelta(hours=random.randint(low + 1, high - 1))
|
||||
messages += random.choice(partners).message_post(
|
||||
author_id=cls.partner_admin.id,
|
||||
body=f"Awesome Partner! ({next(counter)})",
|
||||
email_from=cls.partner_admin.email_formatted,
|
||||
message_type='comment',
|
||||
subtype_xmlid='mail.mt_comment',
|
||||
# adjust top and bottom by 1h to avoid overlapping with the
|
||||
# range limit and dropping out of the digest's selection thing
|
||||
create_date=create_date,
|
||||
)
|
||||
cls.env.flush_all()
|
||||
@@ -1,21 +1,18 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import itertools
|
||||
import random
|
||||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from lxml import html
|
||||
from werkzeug.urls import url_encode, url_join
|
||||
|
||||
from odoo import fields, SUPERUSER_ID
|
||||
from odoo.addons.base.tests.common import HttpCaseWithUserDemo
|
||||
from odoo.addons.mail.tests import common as mail_test
|
||||
from odoo.addons.digest.tests.common import TestDigestCommon
|
||||
from odoo.tests import tagged
|
||||
from odoo.tests.common import users
|
||||
|
||||
|
||||
class TestDigest(mail_test.MailCommon):
|
||||
class TestDigest(TestDigestCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
@@ -34,51 +31,27 @@ class TestDigest(mail_test.MailCommon):
|
||||
# clean logs so that town down is activated
|
||||
cls.env['res.users.log'].search([('create_uid', 'in', (cls.user_admin + cls.user_employee).ids)]).unlink()
|
||||
|
||||
cls.test_digest = cls.env['digest.digest'].create({
|
||||
'kpi_mail_message_total': True,
|
||||
'kpi_res_users_connected': True,
|
||||
'name': "My Digest",
|
||||
'periodicity': 'daily',
|
||||
})
|
||||
@users('admin')
|
||||
def test_digest_kpi_res_users_connected_value(self):
|
||||
self.env['res.users.log'].search([]).unlink()
|
||||
# Sanity check
|
||||
initial_values = self.all_digests.mapped('kpi_res_users_connected_value')
|
||||
self.assertEqual(initial_values, [0, 0, 0])
|
||||
|
||||
@classmethod
|
||||
def _setup_messages(cls):
|
||||
""" Remove all existing messages, then create a bunch of them on random
|
||||
partners with the correct types in correct time-bucket:
|
||||
self.env['res.users.log'].with_user(self.user_employee).create({})
|
||||
self.env['res.users.log'].with_user(self.user_admin).create({})
|
||||
|
||||
- 3 in the previous 24h
|
||||
- 5 more in the 6 days before that for a total of 8 in the previous week
|
||||
- 7 more in the 20 days before *that* (because digest doc lies and is
|
||||
based around weeks and months not days), for a total of 15 in the
|
||||
previous month
|
||||
"""
|
||||
# regular employee can't necessarily access "private" addresses
|
||||
partners = cls.env['res.partner'].search([('type', '!=', 'private')])
|
||||
messages = cls.env['mail.message']
|
||||
counter = itertools.count()
|
||||
self.all_digests.invalidate_recordset()
|
||||
|
||||
now = fields.Datetime.now()
|
||||
for count, (low, high) in [(3, (0 * 24, 1 * 24)),
|
||||
(5, (1 * 24, 7 * 24)),
|
||||
(7, (7 * 24, 27 * 24)),
|
||||
]:
|
||||
for _ in range(count):
|
||||
create_date = now - relativedelta(hours=random.randint(low + 1, high - 1))
|
||||
messages += random.choice(partners).message_post(
|
||||
author_id=cls.partner_admin.id,
|
||||
body=f"Awesome Partner! ({next(counter)})",
|
||||
email_from=cls.partner_admin.email_formatted,
|
||||
message_type='comment',
|
||||
subtype_xmlid='mail.mt_comment',
|
||||
# adjust top and bottom by 1h to avoid overlapping with the
|
||||
# range limit and dropping out of the digest's selection thing
|
||||
create_date=create_date,
|
||||
)
|
||||
cls.env.flush_all()
|
||||
self.assertEqual(self.digest_1.kpi_res_users_connected_value, 2)
|
||||
self.assertEqual(self.digest_2.kpi_res_users_connected_value, 0,
|
||||
msg='This KPI is in an other company')
|
||||
self.assertEqual(self.digest_3.kpi_res_users_connected_value, 2,
|
||||
msg='This KPI has no company, should take the current one')
|
||||
|
||||
@users('admin')
|
||||
def test_digest_numbers(self):
|
||||
digest = self.env['digest.digest'].browse(self.test_digest.ids)
|
||||
digest = self.env['digest.digest'].browse(self.digest_1.ids)
|
||||
digest._action_subscribe_users(self.user_employee)
|
||||
|
||||
# digest creates its mails in auto_delete mode so we need to capture
|
||||
@@ -102,7 +75,7 @@ class TestDigest(mail_test.MailCommon):
|
||||
|
||||
@users('admin')
|
||||
def test_digest_subscribe(self):
|
||||
digest_user = self.test_digest.with_user(self.user_employee)
|
||||
digest_user = self.digest_1.with_user(self.user_employee)
|
||||
self.assertFalse(digest_user.is_subscribed)
|
||||
|
||||
# subscribe a user so at least one mail gets sent
|
||||
@@ -115,7 +88,7 @@ class TestDigest(mail_test.MailCommon):
|
||||
|
||||
@users('admin')
|
||||
def test_digest_tone_down(self):
|
||||
digest = self.env['digest.digest'].browse(self.test_digest.ids)
|
||||
digest = self.env['digest.digest'].browse(self.digest_1.ids)
|
||||
digest._action_subscribe_users(self.user_employee)
|
||||
|
||||
# initial data
|
||||
@@ -157,7 +130,7 @@ class TestDigest(mail_test.MailCommon):
|
||||
""",
|
||||
})
|
||||
with self.mock_mail_gateway():
|
||||
self.test_digest._action_send_to_user(self.user_employee)
|
||||
self.digest_1._action_send_to_user(self.user_employee)
|
||||
self.assertEqual(len(self._new_mails), 1, "A new Email should have been created")
|
||||
sent_mail_body = html.fromstring(self._new_mails.body_html)
|
||||
values_to_check = [
|
||||
@@ -173,7 +146,7 @@ class TestDigest(mail_test.MailCommon):
|
||||
|
||||
@users('admin')
|
||||
def test_digest_tone_down_wlogs(self):
|
||||
digest = self.env['digest.digest'].browse(self.test_digest.ids)
|
||||
digest = self.env['digest.digest'].browse(self.digest_1.ids)
|
||||
digest._action_subscribe_users(self.user_employee)
|
||||
|
||||
# initial data
|
||||
|
||||
@@ -14,14 +14,11 @@ class Digest(models.Model):
|
||||
def _compute_kpi_hr_recruitment_new_colleagues_value(self):
|
||||
if not self.env.user.has_group('hr_recruitment.group_hr_recruitment_user'):
|
||||
raise AccessError(_("Do not have access, skip this data for user's digest email"))
|
||||
for record in self:
|
||||
start, end, company = record._get_kpi_compute_parameters()
|
||||
new_colleagues = self.env['hr.employee'].search_count([
|
||||
('create_date', '>=', start),
|
||||
('create_date', '<', end),
|
||||
('company_id', '=', company.id)
|
||||
])
|
||||
record.kpi_hr_recruitment_new_colleagues_value = new_colleagues
|
||||
|
||||
self._calculate_company_based_kpi(
|
||||
'hr.employee',
|
||||
'kpi_hr_recruitment_new_colleagues_value',
|
||||
)
|
||||
|
||||
def _compute_kpis_actions(self, company, user):
|
||||
res = super(Digest, self)._compute_kpis_actions(company, user)
|
||||
|
||||
@@ -16,35 +16,34 @@ class Digest(models.Model):
|
||||
|
||||
def _compute_kpi_livechat_rating_value(self):
|
||||
channels = self.env['mail.channel'].search([('livechat_operator_id', '=', self.env.user.partner_id.id)])
|
||||
for record in self:
|
||||
start, end, company = record._get_kpi_compute_parameters()
|
||||
domain = [
|
||||
('create_date', '>=', start), ('create_date', '<', end),
|
||||
('rated_partner_id', '=', self.env.user.partner_id.id)
|
||||
]
|
||||
ratings = channels.rating_get_grades(domain)
|
||||
record.kpi_livechat_rating_value = ratings['great'] * 100 / sum(ratings.values()) if sum(ratings.values()) else 0
|
||||
start, end, __ = self._get_kpi_compute_parameters()
|
||||
domain = [
|
||||
('create_date', '>=', start),
|
||||
('create_date', '<', end),
|
||||
('rated_partner_id', '=', self.env.user.partner_id.id)
|
||||
]
|
||||
ratings = channels.rating_get_grades(domain)
|
||||
self.kpi_livechat_rating_value = (
|
||||
ratings['great'] * 100 / sum(ratings.values())
|
||||
if sum(ratings.values()) else 0
|
||||
)
|
||||
|
||||
def _compute_kpi_livechat_conversations_value(self):
|
||||
for record in self:
|
||||
start, end, company = record._get_kpi_compute_parameters()
|
||||
record.kpi_livechat_conversations_value = self.env['mail.channel'].search_count([
|
||||
('channel_type', '=', 'livechat'),
|
||||
('livechat_operator_id', '=', self.env.user.partner_id.id),
|
||||
('create_date', '>=', start), ('create_date', '<', end)
|
||||
])
|
||||
start, end, __ = self._get_kpi_compute_parameters()
|
||||
self.kpi_livechat_conversations_value = self.env['mail.channel'].search_count([
|
||||
('channel_type', '=', 'livechat'),
|
||||
('livechat_operator_id', '=', self.env.user.partner_id.id),
|
||||
('create_date', '>=', start), ('create_date', '<', end),
|
||||
])
|
||||
|
||||
def _compute_kpi_livechat_response_value(self):
|
||||
for record in self:
|
||||
start, end, company = record._get_kpi_compute_parameters()
|
||||
response_time = self.env['im_livechat.report.operator'].sudo()._read_group([
|
||||
('start_date', '>=', start), ('start_date', '<', end),
|
||||
('partner_id', '=', self.env.user.partner_id.id)], ['partner_id', 'time_to_answer'], ['partner_id'])
|
||||
record.kpi_livechat_response_value = sum(
|
||||
response['time_to_answer']
|
||||
for response in response_time
|
||||
if response['time_to_answer'] > 0
|
||||
)
|
||||
start, end, __ = self._get_kpi_compute_parameters()
|
||||
response_time = self.env['im_livechat.report.channel'].sudo()._read_group([
|
||||
('start_date', '>=', start),
|
||||
('start_date', '<', end),
|
||||
('partner_id', '=', self.env.user.partner_id.id)
|
||||
], ['time_to_answer:avg'], [])
|
||||
self.kpi_livechat_response_value = response_time[0]['time_to_answer']
|
||||
|
||||
def _compute_kpis_actions(self, company, user):
|
||||
res = super(Digest, self)._compute_kpis_actions(company, user)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
from . import chatbot_common
|
||||
from . import test_chatbot_form_ui
|
||||
from . import test_chatbot_internals
|
||||
from . import test_digest
|
||||
from . import test_get_mail_channel
|
||||
from . import test_im_livechat_report
|
||||
from . import test_im_livechat_support_page
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.digest.tests.common import TestDigestCommon
|
||||
from odoo.tools import mute_logger
|
||||
from odoo.tests import tagged
|
||||
|
||||
|
||||
@tagged('post_install')
|
||||
class TestLiveChatDigest(TestDigestCommon):
|
||||
|
||||
@classmethod
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
other_partner = cls.env['res.partner'].create({'name': 'Other Partner'})
|
||||
|
||||
cls.channels = cls.env['mail.channel'].create([{
|
||||
'name': 'Channel 1',
|
||||
'livechat_operator_id': cls.env.user.partner_id.id,
|
||||
'channel_type': 'livechat',
|
||||
}, {
|
||||
'name': 'Channel 2',
|
||||
'livechat_operator_id': cls.env.user.partner_id.id,
|
||||
'channel_type': 'livechat',
|
||||
}, {
|
||||
'name': 'Channel 3',
|
||||
'livechat_operator_id': other_partner.id,
|
||||
'channel_type': 'livechat',
|
||||
}])
|
||||
|
||||
cls.env['rating.rating'].search([]).unlink()
|
||||
|
||||
cls.env['rating.rating'].create([{
|
||||
'rated_partner_id': cls.env.user.partner_id.id,
|
||||
'res_id': cls.channels[0].id,
|
||||
'res_model_id': cls.env['ir.model']._get('mail.channel').id,
|
||||
'consumed': True,
|
||||
'rating': 5,
|
||||
}, {
|
||||
'rated_partner_id': cls.env.user.partner_id.id,
|
||||
'res_id': cls.channels[0].id,
|
||||
'res_model_id': cls.env['ir.model']._get('mail.channel').id,
|
||||
'consumed': True,
|
||||
'rating': 0,
|
||||
}, {
|
||||
'rated_partner_id': cls.env.user.partner_id.id,
|
||||
'res_id': cls.channels[0].id,
|
||||
'res_model_id': cls.env['ir.model']._get('mail.channel').id,
|
||||
'consumed': True,
|
||||
'rating': 3,
|
||||
}, {
|
||||
'rated_partner_id': cls.env.user.partner_id.id,
|
||||
'res_id': cls.channels[0].id,
|
||||
'res_model_id': cls.env['ir.model']._get('mail.channel').id,
|
||||
'consumed': True,
|
||||
'rating': 3,
|
||||
}])
|
||||
|
||||
def test_kpi_livechat_rating_value(self):
|
||||
# 1/3 of the ratings have 5/5 note (0 are ignored)
|
||||
self.assertEqual(round(self.digest_1.kpi_livechat_rating_value, 2), 33.33)
|
||||
@@ -14,14 +14,14 @@ class Digest(models.Model):
|
||||
def _compute_kpi_pos_total_value(self):
|
||||
if not self.env.user.has_group('point_of_sale.group_pos_user'):
|
||||
raise AccessError(_("Do not have access, skip this data for user's digest email"))
|
||||
for record in self:
|
||||
start, end, company = record._get_kpi_compute_parameters()
|
||||
record.kpi_pos_total_value = sum(self.env['pos.order'].search([
|
||||
('date_order', '>=', start),
|
||||
('date_order', '<', end),
|
||||
('state', 'not in', ['draft', 'cancel', 'invoiced']),
|
||||
('company_id', '=', company.id)
|
||||
]).mapped('amount_total'))
|
||||
|
||||
self._calculate_company_based_kpi(
|
||||
'pos.order',
|
||||
'kpi_pos_total_value',
|
||||
date_field='date_order',
|
||||
additional_domain=[('state', 'not in', ['draft', 'cancel', 'invoiced'])],
|
||||
sum_field='amount_total',
|
||||
)
|
||||
|
||||
def _compute_kpis_actions(self, company, user):
|
||||
res = super(Digest, self)._compute_kpis_actions(company, user)
|
||||
|
||||
@@ -14,15 +14,12 @@ class Digest(models.Model):
|
||||
def _compute_project_task_opened_value(self):
|
||||
if not self.env.user.has_group('project.group_project_user'):
|
||||
raise AccessError(_("Do not have access, skip this data for user's digest email"))
|
||||
for record in self:
|
||||
start, end, company = record._get_kpi_compute_parameters()
|
||||
record.kpi_project_task_opened_value = self.env['project.task'].search_count([
|
||||
('stage_id.fold', '=', False),
|
||||
('create_date', '>=', start),
|
||||
('create_date', '<', end),
|
||||
('company_id', '=', company.id),
|
||||
('project_id', '!=', False),
|
||||
])
|
||||
|
||||
self._calculate_company_based_kpi(
|
||||
'project.task',
|
||||
'kpi_project_task_opened_value',
|
||||
additional_domain=[('stage_id.fold', '=', False), ('project_id', '!=', False)],
|
||||
)
|
||||
|
||||
def _compute_kpis_actions(self, company, user):
|
||||
res = super(Digest, self)._compute_kpis_actions(company, user)
|
||||
|
||||
@@ -134,7 +134,7 @@ class RatingMixin(models.AbstractModel):
|
||||
if domain:
|
||||
base_domain += domain
|
||||
rg_data = self.env['rating.rating'].read_group(base_domain, ['rating'], ['rating', 'res_id'])
|
||||
# init dict with all posible rate value, except 0 (no value for the rating)
|
||||
# init dict with all possible rate value, except 0 (no value for the rating)
|
||||
values = dict.fromkeys(range(1, 6), 0)
|
||||
for rating_rg in rg_data:
|
||||
rating_val_round = float_round(rating_rg['rating'], precision_digits=1)
|
||||
|
||||
@@ -14,14 +14,14 @@ class Digest(models.Model):
|
||||
def _compute_kpi_sale_total_value(self):
|
||||
if not self.env.user.has_group('sales_team.group_sale_salesman_all_leads'):
|
||||
raise AccessError(_("Do not have access, skip this data for user's digest email"))
|
||||
for record in self:
|
||||
start, end, company = record._get_kpi_compute_parameters()
|
||||
all_channels_sales = self.env['sale.report']._read_group([
|
||||
('date', '>=', start),
|
||||
('date', '<', end),
|
||||
('state', 'not in', ['draft', 'cancel', 'sent']),
|
||||
('company_id', '=', company.id)], ['price_total'], ['company_id'])
|
||||
record.kpi_all_sale_total_value = sum([channel_sale['price_total'] for channel_sale in all_channels_sales])
|
||||
|
||||
self._calculate_company_based_kpi(
|
||||
'sale.report',
|
||||
'kpi_all_sale_total_value',
|
||||
date_field='date',
|
||||
additional_domain=[('state', 'not in', ['draft', 'cancel', 'sent'])],
|
||||
sum_field='price_total',
|
||||
)
|
||||
|
||||
def _compute_kpis_actions(self, company, user):
|
||||
res = super(Digest, self)._compute_kpis_actions(company, user)
|
||||
|
||||
@@ -14,16 +14,14 @@ class Digest(models.Model):
|
||||
def _compute_kpi_website_sale_total_value(self):
|
||||
if not self.env.user.has_group('sales_team.group_sale_salesman_all_leads'):
|
||||
raise AccessError(_("Do not have access, skip this data for user's digest email"))
|
||||
for record in self:
|
||||
start, end, company = record._get_kpi_compute_parameters()
|
||||
confirmed_website_sales = self.env['sale.order'].search([
|
||||
('date_order', '>=', start),
|
||||
('date_order', '<', end),
|
||||
('state', 'not in', ['draft', 'cancel', 'sent']),
|
||||
('website_id', '!=', False),
|
||||
('company_id', '=', company.id)
|
||||
])
|
||||
record.kpi_website_sale_total_value = sum(confirmed_website_sales.mapped('amount_total'))
|
||||
|
||||
self._calculate_company_based_kpi(
|
||||
'sale.order',
|
||||
'kpi_website_sale_total_value',
|
||||
date_field='date_order',
|
||||
additional_domain=[('state', 'not in', ['draft', 'cancel', 'sent']), ('website_id', '!=', False)],
|
||||
sum_field='amount_total',
|
||||
)
|
||||
|
||||
def _compute_kpis_actions(self, company, user):
|
||||
res = super(Digest, self)._compute_kpis_actions(company, user)
|
||||
|
||||
Reference in New Issue
Block a user