Skip to content

[19.0][MIG] mail_activity_team: Migration to 19.0#177

Open
CRogos wants to merge 60 commits intoOCA:19.0from
c4a8-odoo:copilot/migrate-mail-activity-team-18-0-to-19-0
Open

[19.0][MIG] mail_activity_team: Migration to 19.0#177
CRogos wants to merge 60 commits intoOCA:19.0from
c4a8-odoo:copilot/migrate-mail-activity-team-18-0-to-19-0

Conversation

@CRogos
Copy link
Copy Markdown
Contributor

@CRogos CRogos commented Apr 15, 2026

supersede: #153

JordiBForgeFlow and others added 30 commits April 14, 2026 21:02
Currently translated at 100.0% (24 of 24 strings)

Translation: social-13.0/social-13.0-mail_activity_team
Translate-URL: https://translation.odoo-community.org/projects/social-13-0/social-13-0-mail_activity_team/it/
When an activity is scheduled due to automatic processes the onchanges
won't adapt the proper user team, so we could have a mismatch that
provokes an exceptions due to the designed constaints
- If a user is archived but there's an automatic activity, the team
check wouldn't locate him. We have to ensure the context.
- When the automatic activity gets a user with no team, we should set
the team to a void one as well.

TT30470
If you schedule an activity and you don't force the team as False, errors might araise.
For example, on holidays.
The user couldn't see the proper count of his own activities. It was
allways showing the team activities counter.

TT35885
Currently translated at 71.8% (23 of 32 strings)

Translation: social-15.0/social-15.0-mail_activity_team
Translate-URL: https://translation.odoo-community.org/projects/social-15-0/social-15-0-mail_activity_team/it/
mymage and others added 23 commits April 15, 2026 08:47
Currently translated at 100.0% (33 of 33 strings)

Translation: social-16.0/social-16.0-mail_activity_team
Translate-URL: https://translation.odoo-community.org/projects/social-16-0/social-16-0-mail_activity_team/it/
Taken from the 16.0 branch after the merge of OCA/social#1280
which was picked for the migration of this module for 17.0 in an earlier stage.
Currently translated at 100.0% (32 of 32 strings)

Translation: social-17.0/social-17.0-mail_activity_team
Translate-URL: https://translation.odoo-community.org/projects/social-17-0/social-17-0-mail_activity_team/it/
Currently translated at 100.0% (34 of 34 strings)

Translation: social-17.0/social-17.0-mail_activity_team
Translate-URL: https://translation.odoo-community.org/projects/social-17-0/social-17-0-mail_activity_team/it/
A monkey patch is required on mail.activity's create method to prevent an
unboundError (see odoo/odoo#197864).

In order to open the team activities (Late/Today/Future) from the systray widget,
domain clause mangling is applied in mail.activity.mixin's web_search_read.

Includes OCA/social#1469 that was not ported to 17.0 yet.

Aligned ir.rule with upstream mail.activity rule wrt. creation
(https://github.com/odoo/odoo/blob/b1ffd68e4094d9456dd4ae8f0fa12b50e5ce9d67/addons/mail/security/mail_security.xml#L223-L232).

Co-authored-by: Wodran Van de Sande <wodran.vandesande@dynapps.be>
…s provided

mail.activity's `create` checks if a value for `team_id` is being passed, and
in that case drops values for `user_id`. However, a value for `team_id` will
always be present in the create values because there is a default method for
the field.

In case all teams have been deleted, or if team selection is restricted as per
customization, the value for `team_id` will still be present, but empty. In
that case, we don't want the value for `user_id` to be dropped silently from
the values.
mail_post_defer breaks tests for mail_tracking, so it's better to split it.
OCA/social@b782115
@moduon MT-10471
Currently translated at 100.0% (35 of 35 strings)

Translation: mail-18.0/mail-18.0-mail_activity_team
Translate-URL: https://translation.odoo-community.org/projects/mail-18-0/mail-18-0-mail_activity_team/it/
Currently translated at 100.0% (35 of 35 strings)

Translation: mail-18.0/mail-18.0-mail_activity_team
Translate-URL: https://translation.odoo-community.org/projects/mail-18-0/mail-18-0-mail_activity_team/it/
Currently translated at 100.0% (43 of 43 strings)

Translation: mail-18.0/mail-18.0-mail_activity_team
Translate-URL: https://translation.odoo-community.org/projects/mail-18-0/mail-18-0-mail_activity_team/it/
@OCA-git-bot OCA-git-bot added mod:mail_activity_team Module mail_activity_team series:19.0 labels Apr 15, 2026
@CRogos
Copy link
Copy Markdown
Contributor Author

CRogos commented Apr 15, 2026

@muaazsiddiq @yaazkal could you review and test carefully. There were some changes necessary. I also did not compare the final result with the previous PR.

Migration changes:
- Bump version to 19.0.1.0.0
- Remove legacy 18.0 migrations directory
- Replace _() with self.env._() for 19.0 translation API
- Replace groups_id with group_ids (field renamed in 19.0)
- Rewrite _get_activity_groups to match 19.0 ORM-based API
  (replaces raw SQL with ORM search, per-record counting,
  access rights checks, and new return format with domain/view_type)
- Update JS: fetchData → fetchStoreData for 19.0 store API
- Replace self._context with self.env.context
- Update tests for new per-record counting semantics
- Remove obsolete migration test
@CRogos CRogos force-pushed the copilot/migrate-mail-activity-team-18-0-to-19-0 branch from da86519 to e61bc83 Compare April 15, 2026 12:33
Copy link
Copy Markdown
Contributor Author

@CRogos CRogos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some futher suggestions of the AI, I've removed.

Comment on lines +15 to +77
@api.model
def _get_activity_groups(self):
if not self.env.context.get("team_activities"):
return super()._get_activity_groups()
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, act.user_id as user_id
FROM mail_activity AS act
JOIN ir_model AS m ON act.res_model_id = m.id
WHERE team_id in (
SELECT mail_activity_team_id
FROM mail_activity_team_users_rel
WHERE res_users_id = %(user_id)s
)
GROUP BY m.id, states, act.res_model, act.user_id;"""
user = self.env.uid
self.env.cr.execute(
query,
{
"today": fields.Date.context_today(self),
"user_id": user,
},
)
activity_data = self.env.cr.dictfetchall()
model_ids = [a["id"] for a in activity_data]
model_names = {
n["id"]: n["display_name"]
for n in self.env["ir.model"]
.sudo()
.browse(model_ids)
.read(["id", "display_name"])
}
user_activities = {}
for activity in activity_data:
if not user_activities.get(activity["model"]):
module = self.env[activity["model"]]._original_module
icon = module and modules.module.get_module_icon(module)
user_activities[activity["model"]] = {
"id": activity["id"],
"name": model_names[activity["id"]],
"model": activity["model"],
"type": "activity",
"icon": icon,
"total_count": 0,
"today_count": 0,
"overdue_count": 0,
"planned_count": 0,
}
user_activities[activity["model"]][f"{activity['states']}_count"] += (
activity["count"]
)
if (
activity["states"] in ("today", "overdue")
and activity["user_id"] != user
):
user_activities[activity["model"]]["total_count"] += activity["count"]
return list(user_activities.values())
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The AI suggested me this implementation, which is closer to the implementation of the super class, but also slightly changes the results of the counter.

Suggested change
@api.model
def _get_activity_groups(self):
if not self.env.context.get("team_activities"):
return super()._get_activity_groups()
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, act.user_id as user_id
FROM mail_activity AS act
JOIN ir_model AS m ON act.res_model_id = m.id
WHERE team_id in (
SELECT mail_activity_team_id
FROM mail_activity_team_users_rel
WHERE res_users_id = %(user_id)s
)
GROUP BY m.id, states, act.res_model, act.user_id;"""
user = self.env.uid
self.env.cr.execute(
query,
{
"today": fields.Date.context_today(self),
"user_id": user,
},
)
activity_data = self.env.cr.dictfetchall()
model_ids = [a["id"] for a in activity_data]
model_names = {
n["id"]: n["display_name"]
for n in self.env["ir.model"]
.sudo()
.browse(model_ids)
.read(["id", "display_name"])
}
user_activities = {}
for activity in activity_data:
if not user_activities.get(activity["model"]):
module = self.env[activity["model"]]._original_module
icon = module and modules.module.get_module_icon(module)
user_activities[activity["model"]] = {
"id": activity["id"],
"name": model_names[activity["id"]],
"model": activity["model"],
"type": "activity",
"icon": icon,
"total_count": 0,
"today_count": 0,
"overdue_count": 0,
"planned_count": 0,
}
user_activities[activity["model"]][f"{activity['states']}_count"] += (
activity["count"]
)
if (
activity["states"] in ("today", "overdue")
and activity["user_id"] != user
):
user_activities[activity["model"]]["total_count"] += activity["count"]
return list(user_activities.values())
@api.model
def _get_activity_groups(self):
if not self.env.context.get("team_activities"):
return super()._get_activity_groups()
search_limit = int(
self.env["ir.config_parameter"]
.sudo()
.get_param("mail.activity.systray.limit", 1000)
)
activities = self.env["mail.activity"].search(
[("team_id", "in", self.env.user.activity_team_ids.ids)],
order="id desc",
limit=search_limit,
)
user_company_ids = self.env.user.company_ids.ids
is_all_user_companies_allowed = set(user_company_ids) == set(
self.env.context.get("allowed_company_ids") or []
)
activities_model_groups = defaultdict(lambda: self.env["mail.activity"])
activities_rec_groups = defaultdict(
lambda: defaultdict(lambda: self.env["mail.activity"])
)
for activity in activities:
if activity.res_model:
activities_rec_groups[activity.res_model][activity.res_id] += activity
else:
activities_rec_groups["mail.activity"][activity.id] += activity
model_activity_states = {
"mail.activity": {
"overdue_count": 0,
"today_count": 0,
"planned_count": 0,
"total_count": 0,
}
}
for model_name, activities_by_record in activities_rec_groups.items():
res_ids = activities_by_record.keys()
Model = self.env[model_name]
has_model_access_right = Model.has_access("read")
existing = Model.browse(res_ids).exists()
if has_model_access_right:
allowed_records = existing._filtered_access("read")
else:
allowed_records = Model
unallowed_records = Model.browse(res_ids) - allowed_records
if (
has_model_access_right
and unallowed_records
and not is_all_user_companies_allowed
):
unallowed_records -= (
(unallowed_records & existing)
.with_context(allowed_company_ids=user_company_ids)
._filtered_access("read")
)
model_activity_states[model_name] = {
"overdue_count": 0,
"today_count": 0,
"planned_count": 0,
"total_count": 0,
}
for record_id, record_activities in activities_by_record.items():
if record_id in unallowed_records.ids:
model_key = "mail.activity"
activities_model_groups["mail.activity"] += record_activities
elif record_id in allowed_records.ids:
model_key = model_name
activities_model_groups[model_name] += record_activities
elif record_id:
continue
if "overdue" in record_activities.mapped("state"):
model_activity_states[model_key]["overdue_count"] += 1
model_activity_states[model_key]["total_count"] += 1
elif "today" in record_activities.mapped("state"):
model_activity_states[model_key]["today_count"] += 1
model_activity_states[model_key]["total_count"] += 1
else:
model_activity_states[model_key]["planned_count"] += 1
model_ids = [
self.env["ir.model"]._get_id(name) for name in activities_model_groups
]
user_activities = {}
for model_name, model_activities in activities_model_groups.items():
Model = self.env[model_name]
module = Model._original_module
icon = module and modules.module.get_module_icon(module)
model = self.env["ir.model"]._get(model_name).with_prefetch(model_ids)
user_activities[model_name] = {
"id": model.id,
"name": (
model.name
if model_name != "mail.activity"
else self.env._("Other activities")
),
"model": model_name,
"type": "activity",
"icon": icon,
"domain": (
[("active", "in", [True, False])]
if model_name != "mail.activity" and "active" in Model
else []
),
"total_count": model_activity_states[model_name]["total_count"],
"today_count": model_activity_states[model_name]["today_count"],
"overdue_count": model_activity_states[model_name]["overdue_count"],
"planned_count": model_activity_states[model_name]["planned_count"],
"view_type": getattr(Model, "_systray_view", "list"),
}
if model_name == "mail.activity":
user_activities[model_name]["activity_ids"] = model_activities.ids
return list(user_activities.values())

self.assertEqual(res[0]["today_count"], 2)
res = self.env["res.users"].with_user(self.employee.id)._get_activity_groups()
self.assertEqual(res[0]["total_count"], 2)
self.assertEqual(res[0]["total_count"], 1)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am struggling a little why this count changed?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_get_activity_groups() This function now calculate on the bases of each (overdue, today) state separately. In odoo 19

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So leave it as is, change something?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For me this fulfills the logic but you can also have a second opinion as I haven't got the chance to have a detailed looked on test.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

mod:mail_activity_team Module mail_activity_team series:19.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.