diff --git a/addons/account/models/digest.py b/addons/account/models/digest.py index cb850ed3640..b71cc6979a6 100644 --- a/addons/account/models/digest.py +++ b/addons/account/models/digest.py @@ -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) diff --git a/addons/account/tests/__init__.py b/addons/account/tests/__init__.py index c5e7dfc81eb..7882338da67 100644 --- a/addons/account/tests/__init__.py +++ b/addons/account/tests/__init__.py @@ -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 diff --git a/addons/account/tests/test_digest.py b/addons/account/tests/test_digest.py new file mode 100644 index 00000000000..79288673b77 --- /dev/null +++ b/addons/account/tests/test_digest.py @@ -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', + ) diff --git a/addons/crm/models/digest.py b/addons/crm/models/digest.py index def92a4205e..25c4299f358 100644 --- a/addons/crm/models/digest.py +++ b/addons/crm/models/digest.py @@ -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) diff --git a/addons/crm/tests/__init__.py b/addons/crm/tests/__init__.py index 7d228158f15..f6a9231caa2 100644 --- a/addons/crm/tests/__init__.py +++ b/addons/crm/tests/__init__.py @@ -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 diff --git a/addons/crm/tests/test_digest.py b/addons/crm/tests/test_digest.py new file mode 100644 index 00000000000..06f3443cbd3 --- /dev/null +++ b/addons/crm/tests/test_digest.py @@ -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, + ) diff --git a/addons/digest/models/digest.py b/addons/digest/models/digest.py index 8717bd5d16c..897cde34878 100644 --- a/addons/digest/models/digest.py +++ b/addons/digest/models/digest.py @@ -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] diff --git a/addons/digest/tests/common.py b/addons/digest/tests/common.py new file mode 100644 index 00000000000..80b3a2542af --- /dev/null +++ b/addons/digest/tests/common.py @@ -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() diff --git a/addons/digest/tests/test_digest.py b/addons/digest/tests/test_digest.py index 920e03e16f6..c21de08a11d 100644 --- a/addons/digest/tests/test_digest.py +++ b/addons/digest/tests/test_digest.py @@ -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 diff --git a/addons/hr_recruitment/models/digest.py b/addons/hr_recruitment/models/digest.py index f7072815a18..7a6db70efca 100644 --- a/addons/hr_recruitment/models/digest.py +++ b/addons/hr_recruitment/models/digest.py @@ -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) diff --git a/addons/im_livechat/models/digest.py b/addons/im_livechat/models/digest.py index 7070f4fde6e..7ebf52070d0 100644 --- a/addons/im_livechat/models/digest.py +++ b/addons/im_livechat/models/digest.py @@ -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) diff --git a/addons/im_livechat/tests/__init__.py b/addons/im_livechat/tests/__init__.py index c74b8ea7df9..82d59cc26b2 100644 --- a/addons/im_livechat/tests/__init__.py +++ b/addons/im_livechat/tests/__init__.py @@ -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 diff --git a/addons/im_livechat/tests/test_digest.py b/addons/im_livechat/tests/test_digest.py new file mode 100644 index 00000000000..2a6113df946 --- /dev/null +++ b/addons/im_livechat/tests/test_digest.py @@ -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) diff --git a/addons/point_of_sale/models/digest.py b/addons/point_of_sale/models/digest.py index 21a65cc4fea..6834b50299d 100644 --- a/addons/point_of_sale/models/digest.py +++ b/addons/point_of_sale/models/digest.py @@ -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) diff --git a/addons/project/models/digest_digest.py b/addons/project/models/digest_digest.py index 013722e35a0..e125de3b469 100644 --- a/addons/project/models/digest_digest.py +++ b/addons/project/models/digest_digest.py @@ -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) diff --git a/addons/rating/models/rating_mixin.py b/addons/rating/models/rating_mixin.py index f9dabc1fafa..a638375026e 100644 --- a/addons/rating/models/rating_mixin.py +++ b/addons/rating/models/rating_mixin.py @@ -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) diff --git a/addons/sale_management/models/digest.py b/addons/sale_management/models/digest.py index ba577ff5b87..97359837bbc 100644 --- a/addons/sale_management/models/digest.py +++ b/addons/sale_management/models/digest.py @@ -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) diff --git a/addons/website_sale/models/digest.py b/addons/website_sale/models/digest.py index 4d03289c978..b66aace5948 100644 --- a/addons/website_sale/models/digest.py +++ b/addons/website_sale/models/digest.py @@ -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)