314 lines
11 KiB
Python
314 lines
11 KiB
Python
"""
|
|
Views for HR app.
|
|
"""
|
|
|
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
|
from django.urls import reverse_lazy
|
|
from django.views.generic import ListView, CreateView, DetailView, UpdateView, TemplateView
|
|
from django.utils.translation import gettext_lazy as _
|
|
from django.contrib import messages
|
|
from django.utils import timezone
|
|
from datetime import datetime, timedelta
|
|
|
|
from core.mixins import TenantFilterMixin
|
|
from .models import Attendance, Schedule, Holiday
|
|
from .forms import AttendanceForm, ScheduleForm, HolidayForm
|
|
|
|
|
|
# ============================================================================
|
|
# Attendance Views
|
|
# ============================================================================
|
|
|
|
class AttendanceListView(LoginRequiredMixin, TenantFilterMixin, ListView):
|
|
"""List all attendance records."""
|
|
model = Attendance
|
|
template_name = 'hr/attendance_list.html'
|
|
context_object_name = 'attendances'
|
|
paginate_by = 20
|
|
|
|
def get_queryset(self):
|
|
queryset = super().get_queryset().filter(
|
|
tenant=self.request.user.tenant
|
|
).select_related('employee')
|
|
|
|
# Filter by employee if provided
|
|
employee_id = self.request.GET.get('employee')
|
|
if employee_id:
|
|
queryset = queryset.filter(employee_id=employee_id)
|
|
|
|
# Filter by status if provided
|
|
status = self.request.GET.get('status')
|
|
if status:
|
|
queryset = queryset.filter(status=status)
|
|
|
|
# Filter by date range if provided
|
|
date_from = self.request.GET.get('date_from')
|
|
date_to = self.request.GET.get('date_to')
|
|
if date_from:
|
|
queryset = queryset.filter(date__gte=date_from)
|
|
if date_to:
|
|
queryset = queryset.filter(date__lte=date_to)
|
|
|
|
return queryset.order_by('-date', 'employee')
|
|
|
|
|
|
class AttendanceCreateView(LoginRequiredMixin, TenantFilterMixin, CreateView):
|
|
"""Create a new attendance record."""
|
|
model = Attendance
|
|
form_class = AttendanceForm
|
|
template_name = 'hr/attendance_form.html'
|
|
success_url = reverse_lazy('hr:attendance-list')
|
|
|
|
def form_valid(self, form):
|
|
form.instance.tenant = self.request.user.tenant
|
|
messages.success(self.request, _('Attendance record created successfully.'))
|
|
return super().form_valid(form)
|
|
|
|
|
|
class AttendanceDetailView(LoginRequiredMixin, TenantFilterMixin, DetailView):
|
|
"""View attendance record details."""
|
|
model = Attendance
|
|
template_name = 'hr/attendance_detail.html'
|
|
context_object_name = 'attendance'
|
|
|
|
def get_queryset(self):
|
|
return super().get_queryset().filter(
|
|
tenant=self.request.user.tenant
|
|
).select_related('employee')
|
|
|
|
|
|
class AttendanceUpdateView(LoginRequiredMixin, TenantFilterMixin, UpdateView):
|
|
"""Update an attendance record."""
|
|
model = Attendance
|
|
form_class = AttendanceForm
|
|
template_name = 'hr/attendance_form.html'
|
|
|
|
def get_queryset(self):
|
|
return super().get_queryset().filter(tenant=self.request.user.tenant)
|
|
|
|
def get_success_url(self):
|
|
return reverse_lazy('hr:attendance-detail', kwargs={'pk': self.object.pk})
|
|
|
|
def form_valid(self, form):
|
|
messages.success(self.request, _('Attendance record updated successfully.'))
|
|
return super().form_valid(form)
|
|
|
|
|
|
class AttendanceKioskView(LoginRequiredMixin, TemplateView):
|
|
"""Kiosk view for quick clock in/out."""
|
|
template_name = 'hr/attendance_kiosk.html'
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
today = timezone.now().date()
|
|
|
|
# Get today's attendance for current user
|
|
try:
|
|
attendance = Attendance.objects.get(
|
|
employee=self.request.user,
|
|
date=today,
|
|
tenant=self.request.user.tenant
|
|
)
|
|
context['attendance'] = attendance
|
|
except Attendance.DoesNotExist:
|
|
context['attendance'] = None
|
|
|
|
return context
|
|
|
|
def post(self, request, *args, **kwargs):
|
|
"""Handle clock in/out."""
|
|
today = timezone.now().date()
|
|
# Get current time in Riyadh timezone
|
|
now = timezone.localtime(timezone.now()).time()
|
|
|
|
attendance, created = Attendance.objects.get_or_create(
|
|
employee=request.user,
|
|
date=today,
|
|
tenant=request.user.tenant,
|
|
defaults={'check_in': now, 'status': Attendance.Status.PRESENT}
|
|
)
|
|
|
|
if not created and not attendance.check_out:
|
|
# Clock out
|
|
attendance.check_out = now
|
|
attendance.save()
|
|
messages.success(request, _('Clocked out successfully.'))
|
|
elif created:
|
|
messages.success(request, _('Clocked in successfully.'))
|
|
else:
|
|
messages.warning(request, _('You have already clocked out for today.'))
|
|
|
|
return self.get(request, *args, **kwargs)
|
|
|
|
|
|
# ============================================================================
|
|
# Schedule Views
|
|
# ============================================================================
|
|
|
|
class ScheduleListView(LoginRequiredMixin, TenantFilterMixin, ListView):
|
|
"""List all schedules."""
|
|
model = Schedule
|
|
template_name = 'hr/schedule_list.html'
|
|
context_object_name = 'schedules'
|
|
paginate_by = 50
|
|
|
|
def get_queryset(self):
|
|
queryset = super().get_queryset().filter(
|
|
tenant=self.request.user.tenant
|
|
).select_related('employee')
|
|
|
|
# Filter by employee if provided
|
|
employee_id = self.request.GET.get('employee')
|
|
if employee_id:
|
|
queryset = queryset.filter(employee_id=employee_id)
|
|
|
|
# Filter by day if provided
|
|
day = self.request.GET.get('day')
|
|
if day:
|
|
queryset = queryset.filter(day_of_week=day)
|
|
|
|
# Filter by active status
|
|
is_active = self.request.GET.get('is_active')
|
|
if is_active:
|
|
queryset = queryset.filter(is_active=is_active == 'true')
|
|
|
|
return queryset.order_by('employee', 'day_of_week')
|
|
|
|
|
|
class ScheduleCreateView(LoginRequiredMixin, TenantFilterMixin, CreateView):
|
|
"""Create a new schedule."""
|
|
model = Schedule
|
|
form_class = ScheduleForm
|
|
template_name = 'hr/schedule_form.html'
|
|
success_url = reverse_lazy('hr:schedule-list')
|
|
|
|
def form_valid(self, form):
|
|
form.instance.tenant = self.request.user.tenant
|
|
messages.success(self.request, _('Schedule created successfully.'))
|
|
return super().form_valid(form)
|
|
|
|
|
|
class ScheduleDetailView(LoginRequiredMixin, TenantFilterMixin, DetailView):
|
|
"""View schedule details."""
|
|
model = Schedule
|
|
template_name = 'hr/schedule_detail.html'
|
|
context_object_name = 'schedule'
|
|
|
|
def get_queryset(self):
|
|
return super().get_queryset().filter(
|
|
tenant=self.request.user.tenant
|
|
).select_related('employee')
|
|
|
|
|
|
class ScheduleUpdateView(LoginRequiredMixin, TenantFilterMixin, UpdateView):
|
|
"""Update a schedule."""
|
|
model = Schedule
|
|
form_class = ScheduleForm
|
|
template_name = 'hr/schedule_form.html'
|
|
|
|
def get_queryset(self):
|
|
return super().get_queryset().filter(tenant=self.request.user.tenant)
|
|
|
|
def get_success_url(self):
|
|
return reverse_lazy('hr:schedule-detail', kwargs={'pk': self.object.pk})
|
|
|
|
def form_valid(self, form):
|
|
messages.success(self.request, _('Schedule updated successfully.'))
|
|
return super().form_valid(form)
|
|
|
|
|
|
class ScheduleGridView(LoginRequiredMixin, TenantFilterMixin, TemplateView):
|
|
"""Grid view of all schedules."""
|
|
template_name = 'hr/schedule_grid.html'
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
|
|
# Get all active schedules
|
|
schedules = Schedule.objects.filter(
|
|
tenant=self.request.user.tenant,
|
|
is_active=True
|
|
).select_related('employee').order_by('employee', 'day_of_week')
|
|
|
|
# Organize by employee and day
|
|
schedule_grid = {}
|
|
for schedule in schedules:
|
|
employee_name = schedule.employee.get_full_name()
|
|
if employee_name not in schedule_grid:
|
|
schedule_grid[employee_name] = {}
|
|
schedule_grid[employee_name][schedule.day_of_week] = schedule
|
|
|
|
context['schedule_grid'] = schedule_grid
|
|
context['days'] = Schedule.DayOfWeek.choices
|
|
|
|
return context
|
|
|
|
|
|
# ============================================================================
|
|
# Holiday Views
|
|
# ============================================================================
|
|
|
|
class HolidayListView(LoginRequiredMixin, TenantFilterMixin, ListView):
|
|
"""List all holidays."""
|
|
model = Holiday
|
|
template_name = 'hr/holiday_list.html'
|
|
context_object_name = 'holidays'
|
|
paginate_by = 20
|
|
|
|
def get_queryset(self):
|
|
queryset = super().get_queryset().filter(
|
|
tenant=self.request.user.tenant
|
|
)
|
|
|
|
# Filter by recurring status
|
|
is_recurring = self.request.GET.get('is_recurring')
|
|
if is_recurring:
|
|
queryset = queryset.filter(is_recurring=is_recurring == 'true')
|
|
|
|
# Filter by year
|
|
year = self.request.GET.get('year')
|
|
if year:
|
|
queryset = queryset.filter(date__year=year)
|
|
|
|
return queryset.order_by('date')
|
|
|
|
|
|
class HolidayCreateView(LoginRequiredMixin, TenantFilterMixin, CreateView):
|
|
"""Create a new holiday."""
|
|
model = Holiday
|
|
form_class = HolidayForm
|
|
template_name = 'hr/holiday_form.html'
|
|
success_url = reverse_lazy('hr:holiday-list')
|
|
|
|
def form_valid(self, form):
|
|
form.instance.tenant = self.request.user.tenant
|
|
messages.success(self.request, _('Holiday created successfully.'))
|
|
return super().form_valid(form)
|
|
|
|
|
|
class HolidayDetailView(LoginRequiredMixin, TenantFilterMixin, DetailView):
|
|
"""View holiday details."""
|
|
model = Holiday
|
|
template_name = 'hr/holiday_detail.html'
|
|
context_object_name = 'holiday'
|
|
|
|
def get_queryset(self):
|
|
return super().get_queryset().filter(tenant=self.request.user.tenant)
|
|
|
|
|
|
class HolidayUpdateView(LoginRequiredMixin, TenantFilterMixin, UpdateView):
|
|
"""Update a holiday."""
|
|
model = Holiday
|
|
form_class = HolidayForm
|
|
template_name = 'hr/holiday_form.html'
|
|
|
|
def get_queryset(self):
|
|
return super().get_queryset().filter(tenant=self.request.user.tenant)
|
|
|
|
def get_success_url(self):
|
|
return reverse_lazy('hr:holiday-detail', kwargs={'pk': self.object.pk})
|
|
|
|
def form_valid(self, form):
|
|
messages.success(self.request, _('Holiday updated successfully.'))
|
|
return super().form_valid(form)
|