diff --git a/bemade_sports_clinic/__manifest__.py b/bemade_sports_clinic/__manifest__.py
index 52a5cd9..b07fa4c 100644
--- a/bemade_sports_clinic/__manifest__.py
+++ b/bemade_sports_clinic/__manifest__.py
@@ -92,8 +92,9 @@ This module provides a complete sports medicine clinic management solution with
"views/portal_event_create_template.xml",
"views/treatment_note_views.xml",
"views/res_partner_views.xml",
- "views/res_users_views.xml",
"views/task_to_event_wizard_views.xml",
+ "views/team_role_mass_assign_wizard_views.xml",
+ "views/res_users_views.xml",
],
"demo": ["data/demo/sports_clinic_demo_data.xml"],
"installable": True,
diff --git a/bemade_sports_clinic/models/__init__.py b/bemade_sports_clinic/models/__init__.py
index e56bbc4..5365dad 100644
--- a/bemade_sports_clinic/models/__init__.py
+++ b/bemade_sports_clinic/models/__init__.py
@@ -12,3 +12,4 @@ from . import project_task_security_test
from . import project_project
from . import sports_event
from . import base_partner_merge
+from . import team_role_mass_assign_wizard
diff --git a/bemade_sports_clinic/models/team_role_mass_assign_wizard.py b/bemade_sports_clinic/models/team_role_mass_assign_wizard.py
new file mode 100644
index 0000000..c305dfe
--- /dev/null
+++ b/bemade_sports_clinic/models/team_role_mass_assign_wizard.py
@@ -0,0 +1,131 @@
+from odoo import api, fields, models, _
+from odoo.exceptions import UserError
+
+
+class TeamRoleMassAssignWizard(models.TransientModel):
+ _name = "team.role.mass.assign.wizard"
+ _description = "Mass assign team roles to a user across teams"
+
+ user_id = fields.Many2one("res.users", required=True, readonly=True)
+ partner_id = fields.Many2one(
+ "res.partner", related="user_id.partner_id", readonly=True, store=False
+ )
+ bulk_role = fields.Selection(
+ selection=[
+ ("head_coach", "Head Coach"),
+ ("head_therapist", "Head Therapist"),
+ ("coach", "Coach"),
+ ("therapist", "Therapist"),
+ ("doctor", "Doctor"),
+ ("other", "Other"),
+ ],
+ string="Set role for selected",
+ help="Pick a role to apply to all selected teams (and as default for newly selected teams).",
+ )
+ line_ids = fields.One2many(
+ "team.role.mass.assign.line",
+ "wizard_id",
+ string="Teams",
+ )
+
+ @api.model
+ def default_get(self, fields_list):
+ res = super().default_get(fields_list)
+ user = self.env["res.users"].browse(self.env.context.get("default_user_id"))
+ if not user or not user.exists():
+ raise UserError(_("Wizard must be opened from a user."))
+ partner = user.partner_id
+ Team = self.env["sports.team"].sudo()
+ Staff = self.env["sports.team.staff"].sudo()
+ lines = []
+ # Prefetch existing staff roles for the partner
+ staff_by_team = {
+ s.team_id.id: s for s in Staff.search([("partner_id", "=", partner.id)])
+ }
+ for team in Team.search([], order="name"):
+ staff = staff_by_team.get(team.id)
+ lines.append(
+ (
+ 0,
+ 0,
+ {
+ "team_id": team.id,
+ "selected": bool(staff),
+ "role": staff.role if staff else False,
+ },
+ )
+ )
+ res.update({
+ "user_id": user.id,
+ "line_ids": lines,
+ })
+ return res
+
+ @api.onchange("bulk_role")
+ def _onchange_bulk_role(self):
+ if not self.bulk_role:
+ return
+ for line in self.line_ids:
+ if line.selected:
+ line.role = self.bulk_role
+
+ def action_apply(self):
+ self.ensure_one()
+ if not self.user_id or not self.partner_id:
+ raise UserError(_("No target user provided."))
+ Staff = self.env["sports.team.staff"].sudo()
+ partner = self.partner_id
+ # Apply selections
+ for line in self.line_ids:
+ if not line.selected:
+ continue
+ if not line.role:
+ # If not specified but bulk_role exists, use it
+ role = self.bulk_role
+ if not role:
+ raise UserError(
+ _("Please choose a role for team '%s' or set a bulk role.") % (line.team_id.display_name,)
+ )
+ else:
+ role = line.role
+ # Upsert staff record
+ staff = Staff.search([
+ ("team_id", "=", line.team_id.id),
+ ("partner_id", "=", partner.id),
+ ], limit=1)
+ if staff:
+ staff.write({"role": role})
+ else:
+ Staff.create({
+ "team_id": line.team_id.id,
+ "partner_id": partner.id,
+ "role": role,
+ })
+ # Do not remove existing team assignments that were unselected - explicit non-destructive behavior
+ return {"type": "ir.actions.act_window_close"}
+
+
+class TeamRoleMassAssignLine(models.TransientModel):
+ _name = "team.role.mass.assign.line"
+ _description = "Mass assign team roles - line"
+
+ wizard_id = fields.Many2one("team.role.mass.assign.wizard", required=True, ondelete="cascade")
+ selected = fields.Boolean(string="Assign")
+ team_id = fields.Many2one("sports.team", required=True, ondelete="restrict")
+ role = fields.Selection(
+ selection=[
+ ("head_coach", "Head Coach"),
+ ("head_therapist", "Head Therapist"),
+ ("coach", "Coach"),
+ ("therapist", "Therapist"),
+ ("doctor", "Doctor"),
+ ("other", "Other"),
+ ],
+ string="Role",
+ )
+
+ @api.onchange("selected")
+ def _onchange_selected(self):
+ # If user toggles selection on and no role set yet, use wizard bulk role as default
+ if self.selected and not self.role and self.wizard_id.bulk_role:
+ self.role = self.wizard_id.bulk_role
diff --git a/bemade_sports_clinic/security/ir.model.access.csv b/bemade_sports_clinic/security/ir.model.access.csv
index 4bd2250..239ef30 100644
--- a/bemade_sports_clinic/security/ir.model.access.csv
+++ b/bemade_sports_clinic/security/ir.model.access.csv
@@ -66,4 +66,7 @@ access_project_tags_portal_tp,Portal TP Access for Project Tags,project.model_pr
access_project_milestone_portal_tp,Portal TP Access for Project Milestones,project.model_project_milestone,bemade_sports_clinic.group_portal_treatment_professional,1,0,0,0
access_snailmail_letter_portal_tp,Portal TP Access for Snailmail Letters,snailmail.model_snailmail_letter,bemade_sports_clinic.group_portal_treatment_professional,1,0,0,0
access_snailmail_letter_portal_coach,Portal Coach Access for Snailmail Letters,snailmail.model_snailmail_letter,bemade_sports_clinic.group_portal_team_coach,1,0,0,0
-
+access_team_role_mass_assign_wizard_admin,Admin Access for Team Role Mass Assign Wizard,model_team_role_mass_assign_wizard,base.group_system,1,1,1,1
+access_team_role_mass_assign_wizard_admin2,Clinic Admin Access for Team Role Mass Assign Wizard,model_team_role_mass_assign_wizard,bemade_sports_clinic.group_sports_clinic_admin,1,1,1,1
+access_team_role_mass_assign_line_admin,Admin Access for Team Role Mass Assign Line,model_team_role_mass_assign_line,base.group_system,1,1,1,1
+access_team_role_mass_assign_line_admin2,Clinic Admin Access for Team Role Mass Assign Line,model_team_role_mass_assign_line,bemade_sports_clinic.group_sports_clinic_admin,1,1,1,1
diff --git a/bemade_sports_clinic/views/res_users_views.xml b/bemade_sports_clinic/views/res_users_views.xml
index 02f9d45..4e10253 100644
--- a/bemade_sports_clinic/views/res_users_views.xml
+++ b/bemade_sports_clinic/views/res_users_views.xml
@@ -6,6 +6,20 @@
res.users
+
+
+
+
+
+
+
+ team.role.mass.assign.wizard.form
+ team.role.mass.assign.wizard
+
+
+
+
+
+
+
+ team.role.mass.assign.line.tree
+ team.role.mass.assign.line
+
+
+
+
+
+
+
+
+
+
+
+ Assign Team Roles
+ team.role.mass.assign.wizard
+ form
+ new
+ {"default_user_id": active_id}
+
+