diff --git a/addons/mail/models/mail_activity.py b/addons/mail/models/mail_activity.py index 47161cb73f97bf086ff956a77d5df3deea6046a5..46481fa0412e5b1a66c1d49bc8ba40100051fdd2 100644 --- a/addons/mail/models/mail_activity.py +++ b/addons/mail/models/mail_activity.py @@ -795,3 +795,70 @@ class MailActivityMixin(models.AbstractModel): domain = ['&'] + domain + [('user_id', '=', user_id)] self.env['mail.activity'].search(domain).unlink() return True + + def _read_progress_bar(self, domain, group_by, progress_bar): + group_by_fname = group_by.partition(':')[0] + if not (progress_bar['field'] == 'activity_state' and self._fields[group_by_fname].store): + return super()._read_progress_bar(domain, group_by, progress_bar) + + # optimization for 'activity_state' + + # explicitly check access rights, since we bypass the ORM + self.check_access_rights('read') + query = self._where_calc(domain) + self._apply_ir_rules(query, 'read') + gb = group_by.partition(':')[0] + annotated_groupbys = [ + self._read_group_process_groupby(gb, query) + for gb in [group_by, 'activity_state'] + ] + groupby_dict = {gb['groupby']: gb for gb in annotated_groupbys} + for gb in annotated_groupbys: + if gb['field'] == 'activity_state': + gb['qualified_field'] = '"_last_activity_state"."activity_state"' + groupby_terms, orderby_terms = self._read_group_prepare('activity_state', [], annotated_groupbys, query) + select_terms = [ + '%s as "%s"' % (gb['qualified_field'], gb['groupby']) + for gb in annotated_groupbys + ] + from_clause, where_clause, where_params = query.get_sql() + tz = self._context.get('tz') or self.env.user.tz or 'UTC' + select_query = """ + SELECT 1 AS id, count(*) AS "__count", {fields} + FROM {from_clause} + JOIN ( + SELECT res_id, + CASE + WHEN min(date_deadline - (now() AT TIME ZONE COALESCE(res_partner.tz, %s))::date) > 0 THEN 'planned' + WHEN min(date_deadline - (now() AT TIME ZONE COALESCE(res_partner.tz, %s))::date) < 0 THEN 'overdue' + WHEN min(date_deadline - (now() AT TIME ZONE COALESCE(res_partner.tz, %s))::date) = 0 THEN 'today' + ELSE null + END AS activity_state + FROM mail_activity + JOIN res_users ON (res_users.id = mail_activity.user_id) + JOIN res_partner ON (res_partner.id = res_users.partner_id) + WHERE res_model = '{model}' + GROUP BY res_id + ) AS "_last_activity_state" ON ("{table}".id = "_last_activity_state".res_id) + WHERE {where_clause} + GROUP BY {group_by} + """.format( + fields=', '.join(select_terms), + from_clause=from_clause, + model=self._name, + table=self._table, + where_clause=where_clause or '1=1', + group_by=', '.join(groupby_terms), + ) + self.env.cr.execute(select_query, [tz] * 3 + where_params) + fetched_data = self.env.cr.dictfetchall() + self._read_group_resolve_many2one_fields(fetched_data, annotated_groupbys) + data = [ + {key: self._read_group_prepare_data(key, val, groupby_dict) + for key, val in row.items()} + for row in fetched_data + ] + return [ + self._read_group_format_result(vals, annotated_groupbys, [group_by], domain) + for vals in data + ] diff --git a/addons/test_mail/models/test_mail_models.py b/addons/test_mail/models/test_mail_models.py index 3347744987f9c2b8ec796dca4625cc6e5e0545d2..69b5a1c3be2ba9cf03f4112a4fc715fd0154ce3d 100644 --- a/addons/test_mail/models/test_mail_models.py +++ b/addons/test_mail/models/test_mail_models.py @@ -37,6 +37,7 @@ class MailTestActivity(models.Model): _inherit = ['mail.thread', 'mail.activity.mixin'] name = fields.Char() + date = fields.Date() email_from = fields.Char() active = fields.Boolean(default=True) diff --git a/addons/test_mail/tests/__init__.py b/addons/test_mail/tests/__init__.py index 2f63dbc73a23866deffafc2309f6c1e829773978..3ff3f2bb136b8a8cdee8122b6920f3fa18b35f2b 100644 --- a/addons/test_mail/tests/__init__.py +++ b/addons/test_mail/tests/__init__.py @@ -19,3 +19,4 @@ from . import test_discuss from . import test_performance from . import test_res_users from . import test_odoobot +from . import test_read_progress_bar diff --git a/addons/test_mail/tests/test_read_progress_bar.py b/addons/test_mail/tests/test_read_progress_bar.py new file mode 100644 index 0000000000000000000000000000000000000000..341438507b5d8dfd59e899375f6d4f849c984825 --- /dev/null +++ b/addons/test_mail/tests/test_read_progress_bar.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- +from odoo.tests import common +from odoo import fields +from datetime import timedelta + + +class TestReadProgressBar(common.TransactionCase): + """Test for read_progress_bar""" + + def setUp(self): + super(TestReadProgressBar, self).setUp() + self.Model = self.env['mail.test.activity'] + + def test_week_grouping(self): + """The labels associated to each record in read_progress_bar should match + the ones from read_group, even in edge cases like en_US locale on sundays + """ + context = {"lang": "en_US"} + model = self.Model.with_context(context) + groupby = "date:week" + sunday1 = '2021-05-02' + sunday2 = '2021-05-09' + sunday3 = '2021-05-16' + # Don't mistake fields date and date_deadline: + # * date is just a random value + # * date_deadline defines activity_state + self.Model.create({'date': sunday1, 'name': "Yesterday, all my troubles seemed so far away"}).activity_schedule( + 'test_mail.mail_act_test_todo', + summary="Make another test super asap (yesterday)", + date_deadline=fields.Date.context_today(model) - timedelta(days=7) + ) + self.Model.create({'date': sunday2, 'name': "Things we said today"}).activity_schedule( + 'test_mail.mail_act_test_todo', + summary="Make another test asap", + date_deadline=fields.Date.context_today(model) + ) + self.Model.create({'date': sunday3, 'name': "Tomorrow Never Knows"}).activity_schedule( + 'test_mail.mail_act_test_todo', + summary="Make a test tomorrow", + date_deadline=fields.Date.context_today(model) + timedelta(days=7) + ) + + progress_bar = { + 'field': 'activity_state', + 'colors': { + "overdue": 'danger', + "today": 'warning', + "planned": 'success', + } + } + + domain = [('date', "!=", False)] + # call read_group to compute group names + groups = model.read_group(domain, fields=['date'], groupby=[groupby]) + progressbars = model.read_progress_bar(domain, group_by=groupby, progress_bar=progress_bar) + self.assertEqual(len(groups), 3) + self.assertEqual(len(progressbars), 3) + + # format the read_progress_bar result to get a dictionary under this format : {activity_state: group_name} + # original format (after read_progress_bar) is : {group_name: {activity_state: count}} + pg_groups = { + next(activity_state for activity_state, count in data.items() if count): group_name \ + for group_name, data in progressbars.items() + } + + self.assertEqual(groups[0][groupby], pg_groups["overdue"]) + self.assertEqual(groups[1][groupby], pg_groups["today"]) + self.assertEqual(groups[2][groupby], pg_groups["planned"])