feat(bemade_sports_clinic): add Team Role Mass Assign wizard, views, and ACLs

- Add wizard models: team_role_mass_assign_wizard.py (wizard + line)
- Register in models/__init__.py
- Add views/team_role_mass_assign_wizard_views.xml
- Update res_users_views.xml to expose the wizard (button/action)
- Define ACLs for wizard and lines in security/ir.model.access.csv
- Update __manifest__.py to load new security and views
This commit is contained in:
Denis Durepos 2025-09-03 20:52:12 -04:00
parent 917e04d018
commit 15f96ec795
6 changed files with 203 additions and 2 deletions

View file

@ -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,

View file

@ -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

View file

@ -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

View file

@ -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

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
66 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
67 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
68 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
69 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
70 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
71 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
72

View file

@ -6,6 +6,20 @@
<field name="inherit_id" ref="base.view_users_form"/>
<field name="model">res.users</field>
<field name="arch" type="xml">
<!-- Smart button to open mass team role assignment wizard -->
<xpath expr="//div[@name='button_box']" position="inside">
<button
class="oe_stat_button"
type="action"
name="%(bemade_sports_clinic.action_team_role_mass_assign)d"
icon="fa-users"
groups="base.group_system,bemade_sports_clinic.group_sports_clinic_admin"
>
<div class="o_stat_info">
<span class="o_stat_text">Assign Team Roles</span>
</div>
</button>
</xpath>
<!-- Add the existing page for team access -->
<page name="preferences" position="before">
<page name="sports_team_access" string="Sports Team Access"

View file

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Wizard Form View -->
<record id="view_team_role_mass_assign_wizard_form" model="ir.ui.view">
<field name="name">team.role.mass.assign.wizard.form</field>
<field name="model">team.role.mass.assign.wizard</field>
<field name="arch" type="xml">
<form string="Assign Team Roles" create="false" edit="false" delete="false">
<sheet>
<group>
<field name="user_id"/>
<field name="partner_id"/>
<field name="bulk_role" placeholder="Set role for selected teams"/>
</group>
<group>
<field name="line_ids"
context="{'tree_view_ref': 'bemade_sports_clinic.view_team_role_mass_assign_line_tree'}"
widget="one2many_list"
editable="bottom"/>
</group>
</sheet>
<footer>
<button string="Apply" type="object" name="action_apply" class="btn-primary"/>
<button string="Cancel" class="btn-secondary" special="cancel"/>
</footer>
</form>
</field>
</record>
<!-- Dedicated Tree View for Lines -->
<record id="view_team_role_mass_assign_line_tree" model="ir.ui.view">
<field name="name">team.role.mass.assign.line.tree</field>
<field name="model">team.role.mass.assign.line</field>
<field name="arch" type="xml">
<list editable="bottom" string="Teams">
<field name="selected"/>
<field name="team_id"/>
<field name="role"/>
</list>
</field>
</record>
<!-- Window Action -->
<record id="action_team_role_mass_assign" model="ir.actions.act_window">
<field name="name">Assign Team Roles</field>
<field name="res_model">team.role.mass.assign.wizard</field>
<field name="view_mode">form</field>
<field name="target">new</field>
<field name="context">{"default_user_id": active_id}</field>
</record>
</odoo>