bemade-addons/bemade_sports_clinic/models/sports_team.py

339 lines
13 KiB
Python

from odoo import models, fields, api, _, Command
from odoo.exceptions import ValidationError
class SportsTeam(models.Model):
_name = "sports.team"
_description = "Sports Team"
_inherit = ["mail.thread", "mail.activity.mixin"]
name = fields.Char()
patient_ids = fields.Many2many(
comodel_name="sports.patient",
relation="sports_team_patient_rel",
column1="team_id",
column2="patient_id",
string="Players",
tracking=True,
)
player_count = fields.Integer(compute="_compute_player_counts")
injured_count = fields.Integer(compute="_compute_player_counts")
healthy_count = fields.Integer(compute="_compute_player_counts")
parent_id = fields.Many2one(
comodel_name="res.partner",
string="Parent Organization",
ondelete="restrict",
tracking=True,
)
staff_ids = fields.One2many(
comodel_name="sports.team.staff",
inverse_name="team_id",
tracking=True,
)
head_coach_id = fields.Many2one(
comodel_name="res.partner",
compute="_compute_head_coach",
store=True,
)
head_coach_name = fields.Char(
related="head_coach_id.name",
string="Head Coach Name",
)
head_therapist_id = fields.Many2one(
comodel_name="res.partner",
compute="_compute_head_therapist",
store=True,
string="Head Therapist",
)
head_therapist_name = fields.Char(
related="head_therapist_id.name",
string="Head Therapist Name",
)
website = fields.Char()
allowed_user_ids = fields.Many2many(
comodel_name="res.users",
compute="_compute_allowed_user_ids",
inverse="_inverse_allowed_user_ids",
)
def write(self, vals):
previous_patient_ids = self.sudo().patient_ids
res = super().write(vals)
if "staff_ids" in vals or "patient_ids" in vals:
(self.sudo().patient_ids | previous_patient_ids).recompute_followers()
return res
@api.model_create_multi
def create(self, vals_list):
res = super().create(vals_list)
for index, rec in enumerate(res):
if "staff_ids" in vals_list[index]:
rec.sudo().patient_ids.recompute_followers()
return res
def unlink(self):
to_recompute = self.patient_ids
res = super().unlink()
to_recompute.recompute_followers()
return res
@api.depends("patient_ids.is_injured")
def _compute_player_counts(self):
for rec in self:
rec.player_count = len(rec.patient_ids)
rec.injured_count = len(rec.patient_ids.filtered(lambda p: p.is_injured))
rec.healthy_count = rec.player_count - rec.injured_count
@api.depends("staff_ids.role")
def _compute_head_coach(self):
for rec in self:
staff = rec.staff_ids.filtered(lambda r: r.role == "head_coach")
rec.head_coach_id = staff.partner_id if staff else False
@api.depends("staff_ids.role")
def _compute_head_therapist(self):
for rec in self:
staff = rec.staff_ids.filtered(lambda r: r.role == "head_therapist")
rec.head_therapist_id = staff.partner_id if staff else False
def _compute_allowed_user_ids(self):
for rec in self:
rec.allowed_user_ids = rec.staff_ids.user_ids
def _inverse_allowed_user_ids(self):
for rec in self:
removed_staff = rec.staff_ids.filtered(
lambda staff: staff.user_ids not in rec.allowed_user_ids
)
added_users = rec.allowed_user_ids - rec.staff_ids.user_ids
removed_staff.unlink()
self.env["sports.team.staff"].create(
[
{
"team_id": rec.id,
"partner_id": user.partner_id.id,
"role": "other",
}
for user in added_users
]
)
def remove_access(self, user):
self.staff_ids.filtered(lambda staff: user in staff.user_ids).unlink()
class TeamStaff(models.Model):
_name = "sports.team.staff"
_description = "Sports Team Staff"
sequence = fields.Integer()
team_id = fields.Many2one(
comodel_name="sports.team",
string="Team",
required=True,
ondelete="cascade",
)
partner_id = fields.Many2one(
comodel_name="res.partner",
string="Staff Member",
required=True,
domain=[("is_company", "=", False)],
ondelete="cascade",
)
active = fields.Boolean(related="partner_id.active")
role = fields.Selection(
selection=[
("head_coach", "Head Coach"),
("head_therapist", "Head Therapist"),
("coach", "Coach"),
("therapist", "Therapist"),
("doctor", "Doctor"),
("other", "Other"),
],
required=True,
)
mobile = fields.Char(related="partner_id.mobile", readonly=False)
name = fields.Char(related="partner_id.name", readonly=False)
parent_id = fields.Many2one(
related="partner_id.parent_id",
readonly=False,
string="Organization",
domain=[("is_company", "=", True)],
)
email = fields.Char(related="partner_id.email", readonly=False)
user_ids = fields.One2many(related="partner_id.user_ids", readonly=True)
has_portal_access = fields.Boolean(
compute="_compute_has_portal_access", compute_sudo=True
)
_sql_constraints = [
(
"team_staff_unique",
"unique(team_id, partner_id)",
"Each partner can only be related to a given team once.",
)
]
@api.constrains("role")
def _constrain_role(self):
teams = self.mapped("team_id")
for team in teams:
if len(team.staff_ids.filtered(lambda r: r.role == "head_coach")) > 1:
raise ValidationError(_("A team can have only one head coach."))
if len(team.staff_ids.filtered(lambda r: r.role == "head_therapist")) > 1:
raise ValidationError(_("A team can have only one head therapist."))
@api.onchange("mobile")
def _onchange_mobile_validation(self):
if self.mobile:
self.mobile = self.partner_id._phone_format(
self.mobile, force_format="INTERNATIONAL"
)
@api.depends("user_ids", "user_ids.groups_id")
def _compute_has_portal_access(self):
for rec in self:
# Check if the partner has any active users with portal or internal access
rec.has_portal_access = (
bool(rec.user_ids.filtered(lambda r: r.has_group("base.group_portal")))
or bool(rec.user_ids.filtered(lambda r: r.has_group("base.group_user")))
)
def action_revoke_portal_access(self):
group_portal = self.env.ref("base.group_portal")
group_public = self.env.ref("base.group_public")
# Deactivate the user and remove from portal group
if self.user_ids:
self.user_ids.write(
{
"groups_id": [
Command.unlink(group_portal.id),
Command.link(group_public.id),
],
"active": False,
}
)
# If there's an active signup invitation, cancel it
# This uses the portal.wizard from Odoo core to handle all the details
if self.partner_id and self.has_portal_access:
self.env['res.partner'].sudo().invalidate_model(['signup_valid'])
users = self.env['res.users'].sudo().search([('partner_id', '=', self.partner_id.id)])
users.write({'active': False})
def action_grant_portal_access(self):
wiz = self.env["portal.wizard"].create(
{"partner_ids": [(4, self.partner_id.id)]}
)
return wiz._action_open_modal()
@api.model_create_multi
def create(self, vals_list):
res = super().create(vals_list)
# Update treatment professional group membership for new records
res._update_treatment_professional_group()
# Recompute the is_treatment_professional field on affected users
affected_users = res.mapped('user_ids')
if affected_users:
affected_users.sudo()._compute_is_treatment_professional()
# Handle follower recomputation
res.team_id.mapped("patient_ids").recompute_followers()
return res
def unlink(self):
# Store affected partners and users before deletion
affected_partners = self.mapped('partner_id')
affected_users = self.mapped('user_ids')
# Store roles - only therapist roles matter for the update
had_therapist_role = self.filtered(lambda s: s.role in ['head_therapist', 'therapist'])
therapist_partners = had_therapist_role.mapped('partner_id')
# Standard processing for follower recomputation
patients = self.team_id.mapped("patient_ids")
res = super().unlink()
patients.recompute_followers()
# After deletion, check if therapist partners still have any therapist roles
# and update their group membership accordingly
for partner in therapist_partners:
remaining_therapist_roles = self.env['sports.team.staff'].sudo().search_count([
('partner_id', '=', partner.id),
('role', 'in', ['head_therapist', 'therapist']),
])
if not remaining_therapist_roles:
# No therapist roles left, remove from treatment professional group
users = self.env['res.users'].sudo().search([('partner_id', '=', partner.id)])
treatment_prof_group = self.env.ref('bemade_sports_clinic.group_sports_clinic_treatment_professional')
for user in users:
if user.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional'):
user.sudo().write({'groups_id': [(3, treatment_prof_group.id)]})
# Recompute is_treatment_professional on all affected users
affected_users.sudo().invalidate_model(['is_treatment_professional'])
return res
def _update_treatment_professional_group(self):
"""Update treatment professional status based on staff role
For internal users, this adds them to the treatment professional group.
For portal users, we set the flag via recomputation.
"""
treatment_prof_group = self.env.ref('bemade_sports_clinic.group_sports_clinic_treatment_professional')
for staff in self:
# Skip if partner has no user accounts
if not staff.user_ids:
continue
# Check if this partner has any staff records with therapist roles
all_staff_records = self.env['sports.team.staff'].sudo().search([
('partner_id', '=', staff.partner_id.id),
('role', 'in', ['head_therapist', 'therapist'])
])
should_be_treatment_professional = bool(all_staff_records)
# Update all users linked to this partner
for user in staff.user_ids:
# Skip changes during module installation to avoid conflicts in demo data
if self.env.context.get('module'):
continue
# Always trigger a recomputation of is_treatment_professional
user.sudo().invalidate_model(['is_treatment_professional'])
# For internal users, we can directly manage group membership
if user.has_group('base.group_user'):
if should_be_treatment_professional and not user.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional'):
user.sudo().write({'groups_id': [(4, treatment_prof_group.id)]}) # Add to group
elif not should_be_treatment_professional and user.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional'):
user.sudo().write({'groups_id': [(3, treatment_prof_group.id)]}) # Remove from group
def write(self, values):
old_roles = {record.id: record.role for record in self}
result = super().write(values)
# If role changed or team changed, handle group membership updates
if 'role' in values or 'team_id' in values:
self._update_treatment_professional_group()
# Recompute the `is_treatment_professional` field on affected users
affected_users = self.mapped('user_ids')
if affected_users:
affected_users.sudo()._compute_is_treatment_professional()
# Handle team changes for follower recomputation
if "team_id" in values:
to_recompute = self.env["sports.patient"]
for rec in self:
if rec.team_id.id != values["team_id"]:
to_recompute |= rec.team_id.patient_ids
to_recompute.recompute_followers()
return result