Completes iteration 4 without translations.
This commit is contained in:
parent
44f391802d
commit
d6c40a456e
8 changed files with 96 additions and 31 deletions
|
|
@ -37,7 +37,7 @@
|
||||||
'author': 'Bemade Inc.',
|
'author': 'Bemade Inc.',
|
||||||
'website': 'https://www.bemade.org',
|
'website': 'https://www.bemade.org',
|
||||||
'license': 'OPL-1',
|
'license': 'OPL-1',
|
||||||
'depends': ['base', 'mail', 'portal'],
|
'depends': ['portal', 'contacts'],
|
||||||
'data': [
|
'data': [
|
||||||
'security/sports_clinic_groups.xml',
|
'security/sports_clinic_groups.xml',
|
||||||
'security/ir.model.access.csv',
|
'security/ir.model.access.csv',
|
||||||
|
|
|
||||||
|
|
@ -88,10 +88,12 @@ class TeamStaffPortal(CustomerPortal):
|
||||||
'page_name': 'my_players',
|
'page_name': 'my_players',
|
||||||
})
|
})
|
||||||
|
|
||||||
@http.route(route=['/my/players/<int:player_id>'], type='http', auth='user', website=True)
|
@http.route(route=['/my/players/<int:player_id>', '/my/players/<int:player_id>/<int:team_id>'], type='http',
|
||||||
def view_player(self, player_id, **kw):
|
auth='user', website=True)
|
||||||
|
def view_player(self, player_id, team_id=None,**kw):
|
||||||
""" Display the active injuries for a given player. """
|
""" Display the active injuries for a given player. """
|
||||||
player = http.request.env['sports.patient'].browse(player_id)
|
player = http.request.env['sports.patient'].browse(player_id)
|
||||||
|
team = team_id and http.request.env['sports.team'].browse(player_id)
|
||||||
if not player:
|
if not player:
|
||||||
raise UserError(_('This player could not be found.'))
|
raise UserError(_('This player could not be found.'))
|
||||||
injuries = player.injury_ids.filtered(lambda r: r.stage == 'active')
|
injuries = player.injury_ids.filtered(lambda r: r.stage == 'active')
|
||||||
|
|
@ -100,6 +102,6 @@ class TeamStaffPortal(CustomerPortal):
|
||||||
qcontext={
|
qcontext={
|
||||||
'player': player,
|
'player': player,
|
||||||
'injuries': injuries,
|
'injuries': injuries,
|
||||||
'page_name': 'my_player_injuries',
|
'team': team
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@ -57,6 +57,18 @@ class Patient(models.Model):
|
||||||
last_consultation_date = fields.Date()
|
last_consultation_date = fields.Date()
|
||||||
active_injury_count = fields.Integer(compute='_compute_active_injury_count')
|
active_injury_count = fields.Integer(compute='_compute_active_injury_count')
|
||||||
|
|
||||||
|
@api.constrains('match_status', 'practice_status')
|
||||||
|
def constrain_match_and_practice_status(self):
|
||||||
|
""" Avoid invalid combinations of match and practice status:
|
||||||
|
- Yes (match), No (practice)
|
||||||
|
- Yes (match), No Contact (practice)
|
||||||
|
"""
|
||||||
|
# combinations of (match_status, practice_status) that are valid
|
||||||
|
valid_combinations = [('yes', 'yes'), ('no', 'yes'), ('no', 'no_contact'), ('no', 'no')]
|
||||||
|
for rec in self:
|
||||||
|
if (rec.match_status, rec.practice_status) not in valid_combinations:
|
||||||
|
raise ValidationError(_("Invalid combination of match and practice status."))
|
||||||
|
|
||||||
@api.depends('injury_ids.stage')
|
@api.depends('injury_ids.stage')
|
||||||
def _compute_active_injury_count(self):
|
def _compute_active_injury_count(self):
|
||||||
for rec in self:
|
for rec in self:
|
||||||
|
|
@ -64,13 +76,17 @@ class Patient(models.Model):
|
||||||
|
|
||||||
@api.depends('match_status', 'practice_status')
|
@api.depends('match_status', 'practice_status')
|
||||||
def _compute_stage(self):
|
def _compute_stage(self):
|
||||||
|
stage_map = {
|
||||||
|
('yes', 'yes'): 'healthy',
|
||||||
|
('no', 'yes'): 'practice_ok',
|
||||||
|
('no', 'no_contact'): 'practice_ok',
|
||||||
|
('no', 'no'): 'no_play',
|
||||||
|
}
|
||||||
for rec in self:
|
for rec in self:
|
||||||
if rec.match_status == 'yes' and rec.practice_status == 'yes':
|
if (rec.match_status, rec.practice_status) not in stage_map:
|
||||||
rec.stage = 'healthy'
|
rec.stage = False # not a valid combination, will be caught by constraint if save is attempted
|
||||||
elif rec.match_status == 'no' and rec.practice_status == 'no_contact':
|
continue
|
||||||
rec.stage = 'practice_ok'
|
rec.stage = stage_map[(rec.match_status, rec.practice_status)]
|
||||||
else:
|
|
||||||
rec.stage = 'no_play'
|
|
||||||
|
|
||||||
@api.depends('date_of_birth')
|
@api.depends('date_of_birth')
|
||||||
def _compute_age(self):
|
def _compute_age(self):
|
||||||
|
|
@ -91,9 +107,8 @@ class Patient(models.Model):
|
||||||
for rec in self:
|
for rec in self:
|
||||||
rec.is_injured = rec.practice_status != 'yes' or rec.match_status != 'yes'
|
rec.is_injured = rec.practice_status != 'yes' or rec.match_status != 'yes'
|
||||||
if rec.is_injured:
|
if rec.is_injured:
|
||||||
rec.injured_since = \
|
unresolved_injuries = rec.injury_ids.filtered(lambda r: not r.stage == 'resolved')
|
||||||
rec.injury_ids and rec.injury_ids.filtered(lambda r: not r.stage == 'resolved').sorted(
|
rec.injured_since = unresolved_injuries and unresolved_injuries[0].injury_date_time
|
||||||
'injury_date_time')[0].injury_date_time
|
|
||||||
else:
|
else:
|
||||||
rec.injured_since = False
|
rec.injured_since = False
|
||||||
|
|
||||||
|
|
@ -160,14 +175,15 @@ class PatientInjury(models.Model):
|
||||||
predicted_resolution_date = fields.Date(tracking=True)
|
predicted_resolution_date = fields.Date(tracking=True)
|
||||||
resolution_date = fields.Date(tracking=True,
|
resolution_date = fields.Date(tracking=True,
|
||||||
help="The date when the injury was actually resolved.")
|
help="The date when the injury was actually resolved.")
|
||||||
stage = fields.Selection(selection=[('active', 'Active'), ('resolved', 'Resolved')], tracking=True, required=True,
|
stage = fields.Selection(selection=[('active', 'Active'), ('resolved', 'Resolved')], compute='_compute_stage')
|
||||||
default='active', readonly=False)
|
|
||||||
|
|
||||||
@api.constrains('stage')
|
@api.depends('resolution_date')
|
||||||
def constrain_stage_on_resolution_date(self):
|
def _compute_stage(self):
|
||||||
for rec in self:
|
for rec in self:
|
||||||
if rec.stage == 'resolved' and not rec.resolution_date:
|
if rec.resolution_date and rec.resolution_date <= date.today():
|
||||||
raise ValidationError(_('Cannot set an injury as resolved without setting the resolution date first.'))
|
rec.stage = 'resolved'
|
||||||
|
else:
|
||||||
|
rec.stage = 'active'
|
||||||
|
|
||||||
def write(self, vals):
|
def write(self, vals):
|
||||||
super().write(vals)
|
super().write(vals)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from odoo import models, fields, api, _
|
from odoo import models, fields, api, _, Command
|
||||||
from odoo.exceptions import ValidationError
|
from odoo.exceptions import ValidationError
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -56,7 +56,7 @@ class TeamStaff(models.Model):
|
||||||
sequence = fields.Integer()
|
sequence = fields.Integer()
|
||||||
team_id = fields.Many2one(comodel_name='sports.team', string='Team', required=True)
|
team_id = fields.Many2one(comodel_name='sports.team', string='Team', required=True)
|
||||||
partner_id = fields.Many2one(comodel_name='res.partner', string='Staff Member',
|
partner_id = fields.Many2one(comodel_name='res.partner', string='Staff Member',
|
||||||
required=True)
|
required=True, domain=[('is_company', '=', False)])
|
||||||
role = fields.Selection(selection=[
|
role = fields.Selection(selection=[
|
||||||
('head_coach', 'Head Coach'),
|
('head_coach', 'Head Coach'),
|
||||||
('head_trainer', 'Head Trainer'),
|
('head_trainer', 'Head Trainer'),
|
||||||
|
|
@ -66,8 +66,11 @@ class TeamStaff(models.Model):
|
||||||
], required=True)
|
], required=True)
|
||||||
phone = fields.Char(related='partner_id.phone', readonly=False)
|
phone = fields.Char(related='partner_id.phone', readonly=False)
|
||||||
name = fields.Char(related='partner_id.name', readonly=False)
|
name = fields.Char(related='partner_id.name', readonly=False)
|
||||||
parent_id = fields.Many2one(related='partner_id.parent_id', readonly=False, string="Organization")
|
parent_id = fields.Many2one(related='partner_id.parent_id', readonly=False, string="Organization",
|
||||||
|
domain=[('is_company', '=', True)])
|
||||||
email = fields.Char(related='parent_id.email', readonly=False)
|
email = fields.Char(related='parent_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')
|
||||||
|
|
||||||
_sql_constraints = [('team_staff_unique', 'unique(team_id, partner_id)',
|
_sql_constraints = [('team_staff_unique', 'unique(team_id, partner_id)',
|
||||||
'Each partner can only be related to a given team once.')]
|
'Each partner can only be related to a given team once.')]
|
||||||
|
|
@ -85,3 +88,21 @@ class TeamStaff(models.Model):
|
||||||
def _onchange_phone_validation(self):
|
def _onchange_phone_validation(self):
|
||||||
if self.phone:
|
if self.phone:
|
||||||
self.phone = self.partner_id._phone_format(self.phone, force_format='INTERNATIONAL')
|
self.phone = self.partner_id._phone_format(self.phone, force_format='INTERNATIONAL')
|
||||||
|
|
||||||
|
@api.depends('user_ids', 'user_ids.groups_id')
|
||||||
|
def _compute_has_portal_access(self):
|
||||||
|
for rec in self:
|
||||||
|
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'))) or bool(rec.partner_id.signup_token)
|
||||||
|
|
||||||
|
def action_revoke_portal_access(self):
|
||||||
|
group_portal = self.env.ref('base.group_portal')
|
||||||
|
group_public = self.env.ref('base.group_public')
|
||||||
|
self.user_ids.write(
|
||||||
|
{'groups_id': [Command.unlink(group_portal.id), Command.link(group_public.id)], 'active': False})
|
||||||
|
# Remove the signup token, so it cannot be used
|
||||||
|
self.partner_id.sudo().signup_token = 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()
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,7 @@
|
||||||
<field name="groups" eval="[(6, 0, [ref('base.group_portal')])]"/>
|
<field name="groups" eval="[(6, 0, [ref('base.group_portal')])]"/>
|
||||||
<field name="domain_force">
|
<field name="domain_force">
|
||||||
[('id', 'in',
|
[('id', 'in',
|
||||||
user.partner_id.team_staff_rel_ids.mapped('team_id').mapped(
|
user.partner_id.team_staff_rel_ids.team_id.patient_ids.ids)]
|
||||||
'patient_ids').ids)]
|
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
</data>
|
</data>
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@
|
||||||
<tbody>
|
<tbody>
|
||||||
<t t-foreach="players" t-as="player">
|
<t t-foreach="players" t-as="player">
|
||||||
<t t-set="stage" t-value="player.stage"/>
|
<t t-set="stage" t-value="player.stage"/>
|
||||||
<t t-set="url" t-value="'/my/players/' + str(player.id)"/>
|
<t t-set="url" t-value="'/my/players/' + str(player.id) + '/' + str(team.id)"/>
|
||||||
<tr t-attf-class="{{ ('text-danger' if stage == 'no_play'
|
<tr t-attf-class="{{ ('text-danger' if stage == 'no_play'
|
||||||
else 'text-warning') if stage != 'healthy'
|
else 'text-warning') if stage != 'healthy'
|
||||||
else '' }}">
|
else '' }}">
|
||||||
|
|
@ -191,13 +191,26 @@
|
||||||
t-attf-href="/my/teams?{{ keep_query() }}">Teams</a>
|
t-attf-href="/my/teams?{{ keep_query() }}">Teams</a>
|
||||||
<t t-else="">Teams</t>
|
<t t-else="">Teams</t>
|
||||||
</li>
|
</li>
|
||||||
<li t-if="team" class="breadcrumb-item active" t-esc="team.name"/>
|
<li t-if="page_name == 'my_teams' and team" class="breadcrumb-item active" t-esc="team.name"/>
|
||||||
<li t-if="page_name == 'my_players'"
|
<li t-if="page_name == 'my_players'"
|
||||||
t-attf-class="breadcrumb-item #{'active ' if not player else ''}">
|
t-attf-class="breadcrumb-item #{'active ' if not player else ''}">
|
||||||
<a t-if="player"
|
<a t-if="player"
|
||||||
t-attf-href="/my/players?{{ keep_query() }}">Players</a>
|
t-attf-href="/my/players?{{ keep_query() }}">Players</a>
|
||||||
<t t-else="">Players</t>
|
<t t-else="">Players</t>
|
||||||
</li>
|
</li>
|
||||||
|
<li t-if="player and page_name == 'my_player'" class="breadcrumb-item active">
|
||||||
|
<a t-attf-href="/my/players?{{ keep_query() }}">Players</a>
|
||||||
|
<span t-field="player.name"/>
|
||||||
|
</li>
|
||||||
|
<li t-if="player and team" class="breadcrumb-item">
|
||||||
|
<a href="/my/teams">Teams</a>
|
||||||
|
</li>
|
||||||
|
<li t-if="player" class="breadcrumb-item">
|
||||||
|
<a t-if="team" t-attf-href="/my/team/{{ team.id }}?{{ keep_query() }}">
|
||||||
|
<span t-field="team.name"/>
|
||||||
|
</a>
|
||||||
|
<a t-else="" t-attf-href="/my/players/{{ keep_query() }}">Players</a>
|
||||||
|
</li>
|
||||||
<li t-if="player" class="breadcrumb-item active">
|
<li t-if="player" class="breadcrumb-item active">
|
||||||
<span t-field="player.name"/>
|
<span t-field="player.name"/>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<form>
|
<form>
|
||||||
<header>
|
<header>
|
||||||
<field name="stage" widget="statusbar" options="{'clickable': '1'}" />
|
<field name="stage" widget="statusbar" />
|
||||||
</header>
|
</header>
|
||||||
<sheet>
|
<sheet>
|
||||||
<div class="oe_button_box"></div>
|
<div class="oe_button_box"></div>
|
||||||
|
|
@ -129,7 +129,7 @@
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<form>
|
<form>
|
||||||
<header>
|
<header>
|
||||||
<field name="stage" widget="statusbar" options="{'clickable': '1'}"/>
|
<field name="stage" widget="statusbar"/>
|
||||||
</header>
|
</header>
|
||||||
<sheet>
|
<sheet>
|
||||||
<div class="oe_title">
|
<div class="oe_title">
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,20 @@
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="role"/>
|
<field name="role"/>
|
||||||
<field name="phone" widget="phone"/>
|
<field name="phone" widget="phone"/>
|
||||||
|
<field name="has_portal_access" invisible="1"/>
|
||||||
|
<field name="partner_id" invisible="1"/>
|
||||||
|
<button name="action_grant_portal_access"
|
||||||
|
type="object" icon="fa-user-plus"
|
||||||
|
class="oe_highlight"
|
||||||
|
attrs="{'invisible': [('has_portal_access', '=', True)]}"
|
||||||
|
title="Grant Portal Access"
|
||||||
|
groups="bemade_sports_clinic.group_sports_clinic_admin,base.group_system"/>
|
||||||
|
<button name="action_revoke_portal_access" type="object" icon="fa-ban"
|
||||||
|
class="oe_highlight"
|
||||||
|
attrs="{'invisible': [('has_portal_access', '=', False)]}"
|
||||||
|
title="Revoke Portal Access"
|
||||||
|
groups="bemade_sports_clinic.group_sports_clinic_admin,base.group_system"/>
|
||||||
|
|
||||||
</tree>
|
</tree>
|
||||||
</field>
|
</field>
|
||||||
</group>
|
</group>
|
||||||
|
|
@ -126,7 +140,7 @@
|
||||||
<sheet>
|
<sheet>
|
||||||
<group>
|
<group>
|
||||||
<group>
|
<group>
|
||||||
<field name="name"/>
|
<field name="partner_id"/>
|
||||||
<field name="parent_id"/>
|
<field name="parent_id"/>
|
||||||
<field name="role"/>
|
<field name="role"/>
|
||||||
</group>
|
</group>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue