[19.0][MIG] mail_activity_team: Migration to 19.0#177
[19.0][MIG] mail_activity_team: Migration to 19.0#177
Conversation
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.
Replaced for web_domain_field module
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/
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/
…exists on server actions
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/
…le user and model IDs
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/
|
@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
da86519 to
e61bc83
Compare
CRogos
left a comment
There was a problem hiding this comment.
Some futher suggestions of the AI, I've removed.
| @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()) |
There was a problem hiding this comment.
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.
| @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) |
There was a problem hiding this comment.
I am struggling a little why this count changed?
There was a problem hiding this comment.
_get_activity_groups() This function now calculate on the bases of each (overdue, today) state separately. In odoo 19
There was a problem hiding this comment.
So leave it as is, change something?
There was a problem hiding this comment.
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.
supersede: #153