You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
198 lines
7.0 KiB
198 lines
7.0 KiB
# -*- coding: utf-8 -*-
|
|
from odoo import api, fields, models, _
|
|
from odoo.exceptions import UserError
|
|
|
|
|
|
class GymAttendance(models.Model):
|
|
"""Simple Gym Attendance Model"""
|
|
_name = 'gym.attendance'
|
|
_description = 'Gym Attendance'
|
|
_order = 'check_in desc'
|
|
_rec_name = 'member_id'
|
|
|
|
member_id = fields.Many2one('res.partner', string='Member', required=True,
|
|
domain="[('is_gym_member', '=', True)]")
|
|
check_in = fields.Datetime(string='Check In', required=True,
|
|
default=fields.Datetime.now)
|
|
check_out = fields.Datetime(string='Check Out')
|
|
|
|
duration = fields.Float(string='Duration (Hours)',
|
|
compute='_compute_duration', store=True)
|
|
|
|
state = fields.Selection([
|
|
('checked_in', 'Checked In'),
|
|
('checked_out', 'Checked Out')
|
|
], string='State', compute='_compute_state', store=True)
|
|
|
|
@api.depends('check_out')
|
|
def _compute_state(self):
|
|
for record in self:
|
|
record.state = 'checked_out' if record.check_out else 'checked_in'
|
|
|
|
@api.depends('check_in', 'check_out')
|
|
def _compute_duration(self):
|
|
for record in self:
|
|
if record.check_in and record.check_out:
|
|
delta = record.check_out - record.check_in
|
|
record.duration = delta.total_seconds() / 3600
|
|
else:
|
|
record.duration = 0.0
|
|
|
|
@api.model
|
|
def create(self, vals):
|
|
"""Override create to validate BEFORE creating the record"""
|
|
if 'member_id' in vals:
|
|
member_id = vals['member_id']
|
|
|
|
existing_checkin = self.search([
|
|
('member_id', '=', member_id),
|
|
('check_out', '=', False)
|
|
])
|
|
|
|
if existing_checkin:
|
|
member_name = self.env['res.partner'].browse(member_id).name
|
|
raise UserError(_('%s is already checked in at %s. Please check out first.') %
|
|
(member_name, existing_checkin.check_in.strftime('%Y-%m-%d %H:%M:%S')))
|
|
|
|
member = self.env['res.partner'].browse(member_id)
|
|
validation = self._validate_member_can_checkin(member)
|
|
if not validation['can_checkin']:
|
|
raise UserError(validation['message'])
|
|
|
|
return super(GymAttendance, self).create(vals)
|
|
|
|
def write(self, vals):
|
|
"""Override write to validate member changes"""
|
|
if 'member_id' in vals:
|
|
existing_checkin = self.env['gym.attendance'].search([
|
|
('member_id', '=', vals['member_id']),
|
|
('check_out', '=', False),
|
|
('id', 'not in', self.ids)
|
|
])
|
|
|
|
if existing_checkin:
|
|
member_name = self.env['res.partner'].browse(vals['member_id']).name
|
|
raise UserError(_('%s is already checked in. Cannot change to this member.') % member_name)
|
|
|
|
return super(GymAttendance, self).write(vals)
|
|
|
|
def action_check_in(self):
|
|
"""Check in a member - simplified since validation is now in create()"""
|
|
self.ensure_one()
|
|
|
|
return {
|
|
'type': 'ir.actions.client',
|
|
'tag': 'display_notification',
|
|
'params': {
|
|
'message': f'Welcome {self.member_id.name}! Check-in successful.',
|
|
'type': 'success',
|
|
'sticky': False,
|
|
}
|
|
}
|
|
|
|
def _validate_member_can_checkin(self, member):
|
|
"""Validate if member can check in - returns dict with can_checkin boolean and message"""
|
|
|
|
active_memberships = self.env['gym.membership'].search([
|
|
('member_id', '=', member.id),
|
|
('state', '=', 'active')
|
|
])
|
|
|
|
active_membership = None
|
|
if active_memberships:
|
|
if len(active_memberships) == 1:
|
|
active_membership = active_memberships
|
|
else:
|
|
active_membership = max(active_memberships,
|
|
key=lambda m: m.effective_end_date or fields.Date.today())
|
|
|
|
if active_membership:
|
|
if active_membership.effective_end_date and active_membership.effective_end_date < fields.Date.today():
|
|
active_membership.action_expire()
|
|
else:
|
|
return {
|
|
'can_checkin': True,
|
|
'message': _('Check-in allowed.')
|
|
}
|
|
|
|
any_membership = self.env['gym.membership'].search([
|
|
('member_id', '=', member.id)
|
|
], order='id desc', limit=1)
|
|
|
|
if not any_membership:
|
|
return {
|
|
'can_checkin': False,
|
|
'message': _('No membership found for this member.')
|
|
}
|
|
|
|
if any_membership.state == 'paused':
|
|
return {
|
|
'can_checkin': False,
|
|
'message': _(
|
|
'Cannot check in. Your latest membership is PAUSED.\n'
|
|
'Please resume your membership to check in.'
|
|
)
|
|
}
|
|
elif any_membership.state == 'expired':
|
|
return {
|
|
'can_checkin': False,
|
|
'message': _(
|
|
'Cannot check in. Your latest membership has EXPIRED.\n'
|
|
'Please renew your membership to continue.'
|
|
)
|
|
}
|
|
elif any_membership.state in ['draft', 'confirm']:
|
|
return {
|
|
'can_checkin': False,
|
|
'message': _(
|
|
'Cannot check in. Your membership is not yet active.\n'
|
|
'Please wait for activation or contact support.'
|
|
)
|
|
}
|
|
else:
|
|
return {
|
|
'can_checkin': False,
|
|
'message': _(
|
|
'Cannot check in. Membership status: %s'
|
|
) % any_membership.state.title()
|
|
}
|
|
|
|
def action_check_out(self):
|
|
"""Check out manually"""
|
|
self.ensure_one()
|
|
if self.check_out:
|
|
raise UserError(_('Already checked out.'))
|
|
|
|
self.check_out = fields.Datetime.now()
|
|
|
|
return {
|
|
'type': 'ir.actions.client',
|
|
'tag': 'display_notification',
|
|
'params': {
|
|
'message': f'Goodbye {self.member_id.name}! Duration: {self.duration:.2f} hours',
|
|
'type': 'success',
|
|
'sticky': False,
|
|
}
|
|
}
|
|
|
|
@api.model
|
|
def quick_checkin(self, member_id):
|
|
"""Method for quick check-in from external calls"""
|
|
member = self.env['res.partner'].browse(member_id)
|
|
if not member.exists():
|
|
raise UserError(_('Member not found.'))
|
|
|
|
attendance = self.create({
|
|
'member_id': member_id,
|
|
'check_in': fields.Datetime.now(),
|
|
})
|
|
|
|
return {
|
|
'type': 'ir.actions.client',
|
|
'tag': 'display_notification',
|
|
'params': {
|
|
'message': f'Welcome {member.name}! Check-in successful.',
|
|
'type': 'success',
|
|
'sticky': False,
|
|
}
|
|
}
|