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:
parent
917e04d018
commit
15f96ec795
6 changed files with 203 additions and 2 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
131
bemade_sports_clinic/models/team_role_mass_assign_wizard.py
Normal file
131
bemade_sports_clinic/models/team_role_mass_assign_wizard.py
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
Loading…
Reference in a new issue