From 60ad2fd7045cede66ba26bf9789a80cef033e868 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?V=C3=ADctor=20Mart=C3=ADnez?=
 <victor.martinez@tecnativa.com>
Date: Thu, 28 Sep 2023 16:39:02 +0200
Subject: [PATCH 1/4] [IMP-FIX] mail_activity_done: Re-do
 systray_get_activities() method + improve tests

---
 mail_activity_done/models/res_users.py        | 103 ++++++++++--------
 .../tests/test_mail_activity_done.py          |  15 ++-
 2 files changed, 68 insertions(+), 50 deletions(-)

diff --git a/mail_activity_done/models/res_users.py b/mail_activity_done/models/res_users.py
index 9696f53a2..05a3c4edb 100644
--- a/mail_activity_done/models/res_users.py
+++ b/mail_activity_done/models/res_users.py
@@ -1,6 +1,9 @@
 # Copyright 2018-22 ForgeFlow <http://www.forgeflow.com>
+# Copyright 2023 Tecnativa - Víctor Martínez
 # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
-from odoo import api, fields, models, modules
+from collections import defaultdict
+
+from odoo import api, fields, models
 
 
 class ResUsers(models.Model):
@@ -8,51 +11,61 @@ class ResUsers(models.Model):
 
     @api.model
     def systray_get_activities(self):
-        # Here we totally override the method. Not very nice, but
-        # we should perhaps ask Odoo to add a hook here.
-        query = """SELECT m.id, count(*), act.res_model as model,
-                        CASE
-                            WHEN %(today)s::date -
-                            act.date_deadline::date = 0 Then 'today'
-                            WHEN %(today)s::date -
-                            act.date_deadline::date > 0 Then 'overdue'
-                            WHEN %(today)s::date -
-                            act.date_deadline::date < 0 Then 'planned'
-                        END AS states
-                    FROM mail_activity AS act
-                    JOIN ir_model AS m ON act.res_model_id = m.id
-                    WHERE user_id = %(user_id)s
-                    AND act.done = False
-                    GROUP BY m.id, states, act.res_model;
-                    """
+        # TODO: Simplify if Odoo allows to modify query
+        res = super().systray_get_activities()
+        # Convert list to dict
+        user_activities = {}
+        for item in res:
+            user_activities[item["model"]] = item
+        # Redo the method only with the archived records and subtract them.
+        query = """SELECT array_agg(res_id) as res_ids, m.id, count(*),
+                    CASE
+                        WHEN %(today)s::date - act.date_deadline::date = 0 Then 'today'
+                        WHEN %(today)s::date - act.date_deadline::date > 0 Then 'overdue'
+                        WHEN %(today)s::date - act.date_deadline::date < 0 Then 'planned'
+                    END AS states
+                FROM mail_activity AS act
+                JOIN ir_model AS m ON act.res_model_id = m.id
+                WHERE user_id = %(user_id)s
+                AND act.active = False
+                GROUP BY m.id, states;
+                """
         self.env.cr.execute(
-            query, {"today": fields.Date.context_today(self), "user_id": self.env.uid}
+            query,
+            {
+                "today": fields.Date.context_today(self),
+                "user_id": self.env.uid,
+            },
         )
         activity_data = self.env.cr.dictfetchall()
-        model_ids = [a["id"] for a in activity_data]
-        model_names = {
-            n[0]: n[1] for n in self.env["ir.model"].sudo().browse(model_ids).name_get()
-        }
-
-        user_activities = {}
-        for activity in activity_data:
-            if not user_activities.get(activity["model"]):
-                user_activities[activity["model"]] = {
-                    "name": model_names[activity["id"]],
-                    "model": activity["model"],
-                    "icon": modules.module.get_module_icon(
-                        self.env[activity["model"]]._original_module
-                    ),
-                    "total_count": 0,
-                    "today_count": 0,
-                    "overdue_count": 0,
-                    "planned_count": 0,
-                    "type": "activity",
-                }
-            user_activities[activity["model"]][
-                "%s_count" % activity["states"]
-            ] += activity["count"]
-            if activity["states"] in ("today", "overdue"):
-                user_activities[activity["model"]]["total_count"] += activity["count"]
-
+        records_by_state_by_model = defaultdict(
+            lambda: {"today": set(), "overdue": set(), "planned": set(), "all": set()}
+        )
+        for data in activity_data:
+            records_by_state_by_model[data["id"]][data["states"]] = set(data["res_ids"])
+            records_by_state_by_model[data["id"]]["all"] = records_by_state_by_model[
+                data["id"]
+            ]["all"] | set(data["res_ids"])
+        for model_id in records_by_state_by_model:
+            model_dic = records_by_state_by_model[model_id]
+            model = (
+                self.env["ir.model"]
+                .sudo()
+                .browse(model_id)
+                .with_prefetch(tuple(records_by_state_by_model.keys()))
+            )
+            allowed_records = self.env[model.model].search(
+                [("id", "in", tuple(model_dic["all"]))]
+            )
+            if not allowed_records:
+                continue
+            today = len(model_dic["today"] & set(allowed_records.ids))
+            overdue = len(model_dic["overdue"] & set(allowed_records.ids))
+            # Decrease total
+            user_activities[model.model]["total_count"] -= today + overdue
+            user_activities[model.model]["today_count"] -= today
+            user_activities[model.model]["overdue_count"] -= overdue
+            user_activities[model.model]["planned_count"] -= len(
+                model_dic["planned"] & set(allowed_records.ids)
+            )
         return list(user_activities.values())
diff --git a/mail_activity_done/tests/test_mail_activity_done.py b/mail_activity_done/tests/test_mail_activity_done.py
index 1b4c8c86d..b76d00f63 100644
--- a/mail_activity_done/tests/test_mail_activity_done.py
+++ b/mail_activity_done/tests/test_mail_activity_done.py
@@ -1,5 +1,5 @@
 # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
-
+# Copyright 2023 Tecnativa - Víctor Martínez
 from datetime import date
 
 from odoo.tests.common import TransactionCase
@@ -35,7 +35,12 @@ class TestMailActivityDoneMethods(TransactionCase):
         self.assertEqual(self.act1.state, "done")
 
     def test_systray_get_activities(self):
-        act_count = self.employee.with_user(self.employee).systray_get_activities()
-        self.assertEqual(
-            len(act_count), 1, "Number of activities should be equal to one"
-        )
+        res = self.employee.with_user(self.employee).systray_get_activities()
+        self.assertEqual(res[0]["total_count"], 1)
+        self.act1.action_feedback()
+        self.assertFalse(self.act1.active)
+        self.assertEqual(self.act1.state, "done")
+        self.assertTrue(self.act1.done)
+        self.act1.flush()
+        res = self.employee.with_user(self.employee).systray_get_activities()
+        self.assertEqual(res[0]["total_count"], 0)

From b60d91089e7a68a8b3173b2d41ce9babaa8d6dc2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?V=C3=ADctor=20Mart=C3=ADnez?=
 <victor.martinez@tecnativa.com>
Date: Thu, 28 Sep 2023 16:44:29 +0200
Subject: [PATCH 2/4] [IMP] mail_activity_done: Speed up tests

---
 .../tests/test_mail_activity_done.py          | 40 +++++++++++--------
 1 file changed, 24 insertions(+), 16 deletions(-)

diff --git a/mail_activity_done/tests/test_mail_activity_done.py b/mail_activity_done/tests/test_mail_activity_done.py
index b76d00f63..b80f37b4c 100644
--- a/mail_activity_done/tests/test_mail_activity_done.py
+++ b/mail_activity_done/tests/test_mail_activity_done.py
@@ -2,30 +2,38 @@
 # Copyright 2023 Tecnativa - Víctor Martínez
 from datetime import date
 
-from odoo.tests.common import TransactionCase
+from odoo.tests.common import TransactionCase, new_test_user
 
 
 class TestMailActivityDoneMethods(TransactionCase):
-    def setUp(self):
-        super(TestMailActivityDoneMethods, self).setUp()
-
-        self.employee = self.env["res.users"].create(
-            {
-                "company_id": self.env.ref("base.main_company").id,
-                "name": "Test User",
-                "login": "testuser",
-                "groups_id": [(6, 0, [self.env.ref("base.group_user").id])],
-            }
+    @classmethod
+    def setUpClass(cls):
+        super().setUpClass()
+        cls.env = cls.env(
+            context=dict(
+                cls.env.context,
+                mail_activity_quick_update=True,
+                mail_create_nolog=True,
+                mail_create_nosubscribe=True,
+                mail_notrack=True,
+                no_reset_password=True,
+                tracking_disable=True,
+            )
         )
-        activity_type = self.env["mail.activity.type"].search(
+        cls.employee = new_test_user(
+            cls.env,
+            name="Test User",
+            login="testuser",
+        )
+        activity_type = cls.env["mail.activity.type"].search(
             [("name", "=", "Meeting")], limit=1
         )
-        self.act1 = self.env["mail.activity"].create(
+        cls.act1 = cls.env["mail.activity"].create(
             {
                 "activity_type_id": activity_type.id,
-                "res_id": self.env.ref("base.res_partner_1").id,
-                "res_model_id": self.env["ir.model"]._get("res.partner").id,
-                "user_id": self.employee.id,
+                "res_id": cls.env.ref("base.res_partner_1").id,
+                "res_model_id": cls.env["ir.model"]._get("res.partner").id,
+                "user_id": cls.employee.id,
                 "date_deadline": date.today(),
             }
         )

From 0e9d8013e576700b785e41aaacad0de412ed3ed5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?V=C3=ADctor=20Mart=C3=ADnez?=
 <victor.martinez@tecnativa.com>
Date: Thu, 28 Sep 2023 14:33:15 +0200
Subject: [PATCH 3/4] [IMP] mail_activity_team: Speed up tests

---
 .../tests/test_mail_activity_team.py          | 63 +++++++++----------
 1 file changed, 28 insertions(+), 35 deletions(-)

diff --git a/mail_activity_team/tests/test_mail_activity_team.py b/mail_activity_team/tests/test_mail_activity_team.py
index 861811581..18bf7175c 100644
--- a/mail_activity_team/tests/test_mail_activity_team.py
+++ b/mail_activity_team/tests/test_mail_activity_team.py
@@ -2,52 +2,45 @@
 # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
 
 from odoo.exceptions import ValidationError
-from odoo.tests.common import Form, TransactionCase
+from odoo.tests.common import Form, TransactionCase, new_test_user
 
 
 class TestMailActivityTeam(TransactionCase):
     @classmethod
     def setUpClass(cls):
         super().setUpClass()
-        cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True))
+        cls.env = cls.env(
+            context=dict(
+                cls.env.context,
+                mail_activity_quick_update=True,
+                mail_create_nolog=True,
+                mail_create_nosubscribe=True,
+                mail_notrack=True,
+                no_reset_password=True,
+                tracking_disable=True,
+            )
+        )
         # Start from a clean slate
         cls.env["mail.activity.team"].search([]).unlink()
         # Create Users
-        cls.employee = cls.env["res.users"].create(
-            {
-                "company_id": cls.env.ref("base.main_company").id,
-                "name": "Employee",
-                "login": "csu",
-                "email": "crmuser@yourcompany.com",
-                "groups_id": [
-                    (
-                        6,
-                        0,
-                        [
-                            cls.env.ref("base.group_user").id,
-                            cls.env.ref("base.group_partner_manager").id,
-                        ],
-                    )
-                ],
-            }
+        cls.employee = new_test_user(
+            cls.env,
+            name="Employee",
+            login="csu",
+            email="crmuser@yourcompany.com",
+            groups="base.group_user,base.group_partner_manager",
         )
-        cls.employee2 = cls.env["res.users"].create(
-            {
-                "company_id": cls.env.ref("base.main_company").id,
-                "name": "Employee 2",
-                "login": "csu2",
-                "email": "crmuser2@yourcompany.com",
-                "groups_id": [(6, 0, [cls.env.ref("base.group_user").id])],
-            }
+        cls.employee2 = new_test_user(
+            cls.env,
+            name="Employee 2",
+            login="csu2",
+            email="crmuser2@yourcompany.com",
         )
-        cls.employee3 = cls.env["res.users"].create(
-            {
-                "company_id": cls.env.ref("base.main_company").id,
-                "name": "Employee 3",
-                "login": "csu3",
-                "email": "crmuser3@yourcompany.com",
-                "groups_id": [(6, 0, [cls.env.ref("base.group_user").id])],
-            }
+        cls.employee3 = new_test_user(
+            cls.env,
+            name="Employee 3",
+            login="csu3",
+            email="crmuser3@yourcompany.com",
         )
         # Create Activity Types
         cls.activity1 = cls.env["mail.activity.type"].create(

From 7944d6609127bc0254c678ecfc46946aa51c4b6d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?V=C3=ADctor=20Mart=C3=ADnez?=
 <victor.martinez@tecnativa.com>
Date: Thu, 28 Sep 2023 14:34:22 +0200
Subject: [PATCH 4/4] [FIX] mail_activity_team: Fix tests from
 https://github.com/odoo/odoo/commit/d64fff459b745d247cc19d58994b9da4bd50292a

---
 mail_activity_team/tests/test_mail_activity_team.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/mail_activity_team/tests/test_mail_activity_team.py b/mail_activity_team/tests/test_mail_activity_team.py
index 18bf7175c..ad9cb6f6c 100644
--- a/mail_activity_team/tests/test_mail_activity_team.py
+++ b/mail_activity_team/tests/test_mail_activity_team.py
@@ -272,7 +272,7 @@ class TestMailActivityTeam(TransactionCase):
         self.assertEqual(res[0]["total_count"], 1)
         self.assertEqual(res[0]["today_count"], 2)
         res = self.env["res.users"].with_user(self.employee.id).systray_get_activities()
-        self.assertEqual(res[0]["total_count"], 2)
+        self.assertEqual(res[0]["total_count"], 1)
 
     def test_activity_schedule_next(self):
         self.activity1.write(