3478 lines
116 KiB
Python
3478 lines
116 KiB
Python
"""
|
||
HR app views with comprehensive CRUD operations following healthcare best practices.
|
||
"""
|
||
|
||
from django.shortcuts import render, get_object_or_404, redirect
|
||
from django.contrib.auth.decorators import login_required
|
||
from django.views.decorators.http import require_POST, require_GET
|
||
from django.template.loader import render_to_string
|
||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||
from django.contrib import messages
|
||
from django.views.generic import (
|
||
ListView, DetailView, CreateView, UpdateView, DeleteView, TemplateView
|
||
)
|
||
from django.urls import reverse_lazy, reverse
|
||
from django.http import JsonResponse, HttpResponse
|
||
from django.db.models import Q, Count, Avg, Sum
|
||
from django.utils import timezone
|
||
from django.core.paginator import Paginator
|
||
from django.db import transaction
|
||
from datetime import datetime, timedelta, date
|
||
import json
|
||
from accounts.models import User
|
||
from .models import (
|
||
Employee, Department, Schedule, ScheduleAssignment,
|
||
TimeEntry, PerformanceReview, TrainingRecord
|
||
)
|
||
from .forms import (
|
||
EmployeeForm, DepartmentForm, ScheduleForm, ScheduleAssignmentForm,
|
||
TimeEntryForm, PerformanceReviewForm, TrainingRecordForm
|
||
)
|
||
from core.utils import AuditLogger
|
||
|
||
|
||
|
||
class HRDashboardView(LoginRequiredMixin, TemplateView):
|
||
"""
|
||
Main HR dashboard with comprehensive statistics and recent activity.
|
||
"""
|
||
template_name = 'hr/dashboard.html'
|
||
|
||
def get_context_data(self, **kwargs):
|
||
context = super().get_context_data(**kwargs)
|
||
|
||
# Basic statistics
|
||
context.update({
|
||
'total_employees': Employee.objects.filter(tenant=self.request.user.tenant).count(),
|
||
'active_employees': Employee.objects.filter(
|
||
tenant=self.request.user.tenant,
|
||
employment_status='ACTIVE'
|
||
).count(),
|
||
'departments': Department.objects.filter(tenant=self.request.user.tenant),
|
||
'pending_reviews': PerformanceReview.objects.filter(
|
||
employee__tenant=self.request.user.tenant,
|
||
status='PENDING'
|
||
).count(),
|
||
})
|
||
|
||
# Recent activity
|
||
context.update({
|
||
'recent_hires': Employee.objects.filter(
|
||
tenant=self.request.user.tenant,
|
||
hire_date__gte=timezone.now().date() - timedelta(days=30)
|
||
).order_by('-hire_date')[:5],
|
||
|
||
'recent_reviews': PerformanceReview.objects.filter(
|
||
employee__tenant=self.request.user.tenant
|
||
).order_by('-review_date')[:5],
|
||
|
||
'recent_training': TrainingRecord.objects.filter(
|
||
employee__tenant=self.request.user.tenant
|
||
).order_by('-completion_date')[:5],
|
||
})
|
||
|
||
# Attendance statistics
|
||
today = timezone.now().date()
|
||
context.update({
|
||
'employees_clocked_in': TimeEntry.objects.filter(
|
||
employee__tenant=self.request.user.tenant,
|
||
clock_in_time__date=today,
|
||
clock_out_time__isnull=True
|
||
).count(),
|
||
|
||
'total_hours_today': TimeEntry.objects.filter(
|
||
employee__tenant=self.request.user.tenant,
|
||
clock_in_time__date=today,
|
||
clock_out_time__isnull=False
|
||
).aggregate(
|
||
total=Sum('total_hours')
|
||
)['total'] or 0,
|
||
})
|
||
|
||
return context
|
||
|
||
|
||
class EmployeeListView(LoginRequiredMixin, ListView):
|
||
"""
|
||
List all employees with filtering and search capabilities.
|
||
"""
|
||
model = Employee
|
||
template_name = 'hr/employees/employee_list.html'
|
||
context_object_name = 'employees'
|
||
paginate_by = 20
|
||
|
||
def get_queryset(self):
|
||
queryset = Employee.objects.filter(tenant=self.request.user.tenant)
|
||
|
||
# Search functionality
|
||
search = self.request.GET.get('search')
|
||
if search:
|
||
queryset = queryset.filter(
|
||
Q(first_name__icontains=search) |
|
||
Q(last_name__icontains=search) |
|
||
Q(employee_id__icontains=search) |
|
||
Q(email__icontains=search)
|
||
)
|
||
|
||
# Filter by department
|
||
department = self.request.GET.get('department')
|
||
if department:
|
||
queryset = queryset.filter(department_id=department)
|
||
|
||
# Filter by employment status
|
||
status = self.request.GET.get('status')
|
||
if status:
|
||
queryset = queryset.filter(employment_status=status)
|
||
|
||
# Filter by position
|
||
position = self.request.GET.get('position')
|
||
if position:
|
||
queryset = queryset.filter(job_title__icontains=position)
|
||
|
||
return queryset.select_related('department').order_by('last_name', 'first_name')
|
||
|
||
def get_context_data(self, **kwargs):
|
||
context = super().get_context_data(**kwargs)
|
||
context['departments'] = Department.objects.filter(
|
||
tenant=self.request.user.tenant
|
||
).order_by('name')
|
||
context['search'] = self.request.GET.get('search', '')
|
||
context['selected_department'] = self.request.GET.get('department', '')
|
||
context['selected_status'] = self.request.GET.get('status', '')
|
||
context['selected_position'] = self.request.GET.get('position', '')
|
||
return context
|
||
|
||
|
||
class EmployeeDetailView(LoginRequiredMixin, DetailView):
|
||
"""
|
||
Display detailed information about a specific employee.
|
||
"""
|
||
model = Employee
|
||
template_name = 'hr/employees/employee_detail.html'
|
||
context_object_name = 'employee'
|
||
|
||
def get_queryset(self):
|
||
return Employee.objects.filter(tenant=self.request.user.tenant)
|
||
|
||
def get_context_data(self, **kwargs):
|
||
context = super().get_context_data(**kwargs)
|
||
employee = self.get_object()
|
||
|
||
# Recent time entries
|
||
context['recent_time_entries'] = TimeEntry.objects.filter(
|
||
employee=employee
|
||
).order_by('-clock_in_time')[:10]
|
||
|
||
# Recent performance reviews
|
||
context['recent_reviews'] = PerformanceReview.objects.filter(
|
||
employee=employee
|
||
).order_by('-review_date')[:5]
|
||
|
||
# Training records
|
||
context['training_records'] = TrainingRecord.objects.filter(
|
||
employee=employee
|
||
).order_by('-completion_date')[:10]
|
||
|
||
# Schedule assignments
|
||
context['current_schedules'] = Schedule.objects.filter(
|
||
employee=employee,
|
||
effective_date__lte=timezone.now().date(),
|
||
end_date__gte=timezone.now().date() if Schedule.end_date else True
|
||
)
|
||
|
||
return context
|
||
|
||
|
||
class EmployeeCreateView(LoginRequiredMixin, CreateView):
|
||
"""
|
||
Create a new employee record.
|
||
"""
|
||
model = Employee
|
||
form_class = EmployeeForm
|
||
template_name = 'hr/employees/employee_form.html'
|
||
success_url = reverse_lazy('hr:employee_list')
|
||
|
||
def form_valid(self, form):
|
||
form.instance.tenant = self.request.user.tenant
|
||
messages.success(self.request, 'Employee created successfully.')
|
||
return super().form_valid(form)
|
||
|
||
def get_form_kwargs(self):
|
||
kwargs = super().get_form_kwargs()
|
||
kwargs['user'] = self.request.user
|
||
return kwargs
|
||
|
||
|
||
class EmployeeUpdateView(LoginRequiredMixin, UpdateView):
|
||
"""
|
||
Update an existing employee record.
|
||
"""
|
||
model = Employee
|
||
form_class = EmployeeForm
|
||
template_name = 'hr/employees/employee_form.html'
|
||
|
||
def get_queryset(self):
|
||
return Employee.objects.filter(tenant=self.request.user.tenant)
|
||
|
||
def get_success_url(self):
|
||
return reverse('hr:employee_detail', kwargs={'pk': self.object.pk})
|
||
|
||
def form_valid(self, form):
|
||
messages.success(self.request, 'Employee updated successfully.')
|
||
return super().form_valid(form)
|
||
|
||
def get_form_kwargs(self):
|
||
kwargs = super().get_form_kwargs()
|
||
kwargs['user'] = self.request.user
|
||
return kwargs
|
||
|
||
|
||
class EmployeeDeleteView(LoginRequiredMixin, DeleteView):
|
||
"""
|
||
Soft delete an employee record (healthcare compliance).
|
||
"""
|
||
model = Employee
|
||
template_name = 'hr/employees/employee_confirm_delete.html'
|
||
success_url = reverse_lazy('hr:employee_list')
|
||
|
||
def get_queryset(self):
|
||
return Employee.objects.filter(tenant=self.request.user.tenant)
|
||
|
||
def delete(self, request, *args, **kwargs):
|
||
# Soft delete - mark as inactive instead of actual deletion
|
||
employee = self.get_object()
|
||
employee.employment_status = 'INACTIVE'
|
||
employee.termination_date = timezone.now().date()
|
||
employee.save()
|
||
|
||
messages.success(request, f'Employee {employee.get_full_name()} has been deactivated.')
|
||
return redirect(self.success_url)
|
||
|
||
|
||
class DepartmentListView(LoginRequiredMixin, ListView):
|
||
"""
|
||
List all departments with employee counts.
|
||
"""
|
||
model = Department
|
||
template_name = 'hr/departments/department_list.html'
|
||
context_object_name = 'departments'
|
||
paginate_by = 20
|
||
|
||
def get_queryset(self):
|
||
queryset = Department.objects.filter(tenant=self.request.user.tenant)
|
||
|
||
# Search functionality
|
||
search = self.request.GET.get('search')
|
||
if search:
|
||
queryset = queryset.filter(
|
||
Q(name__icontains=search) |
|
||
Q(department_code__icontains=search) |
|
||
Q(description__icontains=search)
|
||
)
|
||
|
||
return queryset
|
||
|
||
|
||
class DepartmentDetailView(LoginRequiredMixin, DetailView):
|
||
"""
|
||
Display detailed information about a specific department.
|
||
"""
|
||
model = Department
|
||
template_name = 'hr/departments/department_detail.html'
|
||
context_object_name = 'department'
|
||
|
||
def get_queryset(self):
|
||
return Department.objects.filter(tenant=self.request.user.tenant)
|
||
|
||
def get_context_data(self, **kwargs):
|
||
context = super().get_context_data(**kwargs)
|
||
department = self.get_object()
|
||
|
||
# Department employees
|
||
context['employees'] = Employee.objects.filter(
|
||
department=department
|
||
).order_by('last_name', 'first_name')
|
||
|
||
# Department statistics
|
||
context['active_employees'] = Employee.objects.filter(
|
||
department=department,
|
||
employment_status='ACTIVE'
|
||
).count()
|
||
|
||
return context
|
||
|
||
|
||
class DepartmentCreateView(LoginRequiredMixin, CreateView):
|
||
"""
|
||
Create a new department.
|
||
"""
|
||
model = Department
|
||
form_class = DepartmentForm
|
||
template_name = 'hr/departments/department_form.html'
|
||
success_url = reverse_lazy('hr:department_list')
|
||
|
||
def form_valid(self, form):
|
||
form.instance.tenant = self.request.user.tenant
|
||
messages.success(self.request, 'Department created successfully.')
|
||
return super().form_valid(form)
|
||
|
||
def get_form_kwargs(self):
|
||
kwargs = super().get_form_kwargs()
|
||
kwargs['user'] = self.request.user
|
||
return kwargs
|
||
|
||
|
||
class DepartmentUpdateView(LoginRequiredMixin, UpdateView):
|
||
"""
|
||
Update an existing department.
|
||
"""
|
||
model = Department
|
||
form_class = DepartmentForm
|
||
template_name = 'hr/departments/department_form.html'
|
||
|
||
def get_queryset(self):
|
||
return Department.objects.filter(tenant=self.request.user.tenant)
|
||
|
||
def get_success_url(self):
|
||
return reverse('hr:department_detail', kwargs={'pk': self.object.pk})
|
||
|
||
def form_valid(self, form):
|
||
messages.success(self.request, 'Department updated successfully.')
|
||
return super().form_valid(form)
|
||
|
||
def get_form_kwargs(self):
|
||
kwargs = super().get_form_kwargs()
|
||
kwargs['user'] = self.request.user
|
||
return kwargs
|
||
|
||
|
||
class DepartmentDeleteView(LoginRequiredMixin, DeleteView):
|
||
"""
|
||
Delete a department (only if no employees assigned).
|
||
"""
|
||
model = Department
|
||
template_name = 'hr/departments/department_confirm_delete.html'
|
||
success_url = reverse_lazy('hr:department_list')
|
||
|
||
def get_queryset(self):
|
||
return Department.objects.filter(tenant=self.request.user.tenant)
|
||
|
||
def delete(self, request, *args, **kwargs):
|
||
department = self.get_object()
|
||
|
||
# Check if department has employees
|
||
if Employee.objects.filter(department=department).exists():
|
||
messages.error(request, 'Cannot delete department with assigned employees.')
|
||
return redirect('hr:department_detail', pk=department.pk)
|
||
|
||
messages.success(request, f'Department {department.name} deleted successfully.')
|
||
return super().delete(request, *args, **kwargs)
|
||
|
||
|
||
class ScheduleListView(LoginRequiredMixin, ListView):
|
||
"""
|
||
List all schedules with filtering capabilities.
|
||
"""
|
||
model = Schedule
|
||
template_name = 'hr/schedules/schedule_list.html'
|
||
context_object_name = 'schedules'
|
||
paginate_by = 20
|
||
|
||
def get_queryset(self):
|
||
queryset = Schedule.objects.filter(employee__tenant=self.request.user.tenant)
|
||
|
||
# Filter by status
|
||
is_active = self.request.GET.get('is_active')
|
||
if is_active:
|
||
queryset = queryset.filter(is_active=(is_active == 'true'))
|
||
|
||
# Filter by date range
|
||
effective_date = self.request.GET.get('effective_date')
|
||
end_date = self.request.GET.get('end_date')
|
||
if effective_date:
|
||
queryset = queryset.filter(effective_date__gte=effective_date)
|
||
if end_date:
|
||
queryset = queryset.filter(end_date__lte=end_date)
|
||
|
||
return queryset.select_related('employee').order_by('-effective_date')
|
||
|
||
|
||
class ScheduleDetailView(LoginRequiredMixin, DetailView):
|
||
"""
|
||
Display detailed information about a specific schedule.
|
||
"""
|
||
model = Schedule
|
||
template_name = 'hr/schedules/schedule_detail.html'
|
||
context_object_name = 'schedule'
|
||
|
||
def get_queryset(self):
|
||
return Schedule.objects.filter(employee__tenant=self.request.user.tenant)
|
||
|
||
def get_context_data(self, **kwargs):
|
||
context = super().get_context_data(**kwargs)
|
||
schedule = self.get_object()
|
||
|
||
# Schedule assignments
|
||
context['assignments'] = ScheduleAssignment.objects.filter(
|
||
schedule=schedule
|
||
).order_by('assignment_date', 'start_time')
|
||
|
||
return context
|
||
|
||
|
||
class ScheduleCreateView(LoginRequiredMixin, CreateView):
|
||
"""
|
||
Create a new schedule.
|
||
"""
|
||
model = Schedule
|
||
form_class = ScheduleForm
|
||
template_name = 'hr/schedules/schedule_form.html'
|
||
success_url = reverse_lazy('hr:schedule_list')
|
||
|
||
def form_valid(self, form):
|
||
form.instance.created_by = self.request.user
|
||
messages.success(self.request, 'Schedule created successfully.')
|
||
return super().form_valid(form)
|
||
|
||
def get_form_kwargs(self):
|
||
kwargs = super().get_form_kwargs()
|
||
kwargs['user'] = self.request.user
|
||
return kwargs
|
||
|
||
|
||
class ScheduleUpdateView(LoginRequiredMixin, UpdateView):
|
||
"""
|
||
Update an existing schedule (limited after publication).
|
||
"""
|
||
model = Schedule
|
||
form_class = ScheduleForm
|
||
template_name = 'hr/schedules/schedule_form.html'
|
||
|
||
def get_queryset(self):
|
||
return Schedule.objects.filter(employee__tenant=self.request.user.tenant)
|
||
|
||
def get_success_url(self):
|
||
return reverse('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)
|
||
|
||
def get_form_kwargs(self):
|
||
kwargs = super().get_form_kwargs()
|
||
kwargs['user'] = self.request.user
|
||
return kwargs
|
||
|
||
|
||
class ScheduleAssignmentListView(LoginRequiredMixin, ListView):
|
||
"""
|
||
List all schedule assignments with filtering capabilities.
|
||
"""
|
||
model = ScheduleAssignment
|
||
template_name = 'hr/assignments/schedule_assignment_list.html'
|
||
context_object_name = 'assignments'
|
||
paginate_by = 20
|
||
|
||
def get_queryset(self):
|
||
queryset = ScheduleAssignment.objects.filter(
|
||
schedule__employee__tenant=self.request.user.tenant
|
||
)
|
||
|
||
# Filter by employee
|
||
employee = self.request.GET.get('employee')
|
||
if employee:
|
||
queryset = queryset.filter(schedule__employee_id=employee)
|
||
|
||
# Filter by date range
|
||
start_date = self.request.GET.get('start_date')
|
||
end_date = self.request.GET.get('end_date')
|
||
if start_date:
|
||
queryset = queryset.filter(assignment_date__gte=start_date)
|
||
if end_date:
|
||
queryset = queryset.filter(assignment_date__lte=end_date)
|
||
|
||
# Filter by status
|
||
status = self.request.GET.get('status')
|
||
if status:
|
||
queryset = queryset.filter(status=status)
|
||
|
||
return queryset.select_related('schedule__employee', 'department').order_by('assignment_date', 'start_time')
|
||
|
||
|
||
class ScheduleAssignmentCreateView(LoginRequiredMixin, CreateView):
|
||
"""
|
||
Create a new schedule assignment.
|
||
"""
|
||
model = ScheduleAssignment
|
||
form_class = ScheduleAssignmentForm
|
||
template_name = 'hr/assignments/schedule_assignment_form.html'
|
||
success_url = reverse_lazy('hr:schedule_assignment_list')
|
||
|
||
def form_valid(self, form):
|
||
messages.success(self.request, 'Schedule assignment created successfully.')
|
||
return super().form_valid(form)
|
||
|
||
def get_form_kwargs(self):
|
||
kwargs = super().get_form_kwargs()
|
||
kwargs['user'] = self.request.user
|
||
return kwargs
|
||
|
||
|
||
class ScheduleAssignmentUpdateView(LoginRequiredMixin, UpdateView):
|
||
"""
|
||
Update an existing schedule assignment.
|
||
"""
|
||
model = ScheduleAssignment
|
||
form_class = ScheduleAssignmentForm
|
||
template_name = 'hr/assignments/schedule_assignment_form.html'
|
||
success_url = reverse_lazy('hr:schedule_assignment_list')
|
||
|
||
def get_queryset(self):
|
||
return ScheduleAssignment.objects.filter(schedule__employee__tenant=self.request.user.tenant)
|
||
|
||
def form_valid(self, form):
|
||
messages.success(self.request, 'Schedule assignment updated successfully.')
|
||
return super().form_valid(form)
|
||
|
||
def get_form_kwargs(self):
|
||
kwargs = super().get_form_kwargs()
|
||
kwargs['user'] = self.request.user
|
||
return kwargs
|
||
|
||
|
||
class TimeEntryListView(LoginRequiredMixin, ListView):
|
||
"""
|
||
List time entries with filtering capabilities.
|
||
"""
|
||
model = TimeEntry
|
||
template_name = 'hr/time_entries/time_entry_list.html'
|
||
context_object_name = 'time_entries'
|
||
paginate_by = 20
|
||
|
||
def get_queryset(self):
|
||
queryset = TimeEntry.objects.filter(
|
||
employee__tenant=self.request.user.tenant
|
||
).select_related('employee')
|
||
|
||
# Filter by employee
|
||
employee = self.request.GET.get('employee')
|
||
if employee:
|
||
queryset = queryset.filter(employee_id=employee)
|
||
|
||
# Filter by date range
|
||
start_date = self.request.GET.get('start_date')
|
||
end_date = self.request.GET.get('end_date')
|
||
if start_date:
|
||
queryset = queryset.filter(work_date__gte=start_date)
|
||
if end_date:
|
||
queryset = queryset.filter(work_date__lte=end_date)
|
||
|
||
# Filter by approval status
|
||
status = self.request.GET.get('status')
|
||
if status:
|
||
queryset = queryset.filter(status=status)
|
||
|
||
return queryset.order_by('-work_date')
|
||
|
||
|
||
class TimeEntryDetailView(LoginRequiredMixin, DetailView):
|
||
"""
|
||
Display detailed information about a specific time entry.
|
||
"""
|
||
model = TimeEntry
|
||
template_name = 'hr/time_entries/time_entry_detail.html'
|
||
context_object_name = 'time_entry'
|
||
|
||
def get_queryset(self):
|
||
return TimeEntry.objects.filter(employee__tenant=self.request.user.tenant)
|
||
|
||
|
||
class TimeEntryCreateView(LoginRequiredMixin, CreateView):
|
||
"""
|
||
Create a new time entry (manual entry).
|
||
"""
|
||
model = TimeEntry
|
||
form_class = TimeEntryForm
|
||
template_name = 'hr/time_entries/time_entry_form.html'
|
||
success_url = reverse_lazy('hr:time_entry_list')
|
||
|
||
def form_valid(self, form):
|
||
messages.success(self.request, 'Time entry created successfully.')
|
||
return super().form_valid(form)
|
||
|
||
def get_form_kwargs(self):
|
||
kwargs = super().get_form_kwargs()
|
||
kwargs['user'] = self.request.user
|
||
return kwargs
|
||
|
||
|
||
class TimeEntryUpdateView(LoginRequiredMixin, UpdateView):
|
||
"""
|
||
Update an existing time entry (limited fields).
|
||
"""
|
||
model = TimeEntry
|
||
form_class = TimeEntryForm
|
||
template_name = 'hr/time_entries/time_entry_form.html'
|
||
success_url = reverse_lazy('hr:time_entry_list')
|
||
|
||
def get_queryset(self):
|
||
return TimeEntry.objects.filter(employee__tenant=self.request.user.tenant)
|
||
|
||
def form_valid(self, form):
|
||
# Restrict updates for approved entries
|
||
if self.object.status == 'APPROVED' and not self.request.user.is_superuser:
|
||
messages.error(self.request, 'Cannot modify approved time entries.')
|
||
return redirect('hr:time_entry_detail', pk=self.object.pk)
|
||
|
||
messages.success(self.request, 'Time entry updated successfully.')
|
||
return super().form_valid(form)
|
||
|
||
def get_form_kwargs(self):
|
||
kwargs = super().get_form_kwargs()
|
||
kwargs['user'] = self.request.user
|
||
return kwargs
|
||
|
||
|
||
class PerformanceReviewListView(LoginRequiredMixin, ListView):
|
||
"""
|
||
List performance reviews with filtering capabilities.
|
||
"""
|
||
model = PerformanceReview
|
||
template_name = 'hr/reviews/performance_review_list.html'
|
||
context_object_name = 'reviews'
|
||
paginate_by = 20
|
||
|
||
def get_queryset(self):
|
||
queryset = PerformanceReview.objects.filter(
|
||
employee__tenant=self.request.user.tenant
|
||
).select_related('employee', 'reviewer')
|
||
|
||
# Filter by employee
|
||
employee = self.request.GET.get('employee')
|
||
if employee:
|
||
queryset = queryset.filter(employee_id=employee)
|
||
|
||
# Filter by status
|
||
status = self.request.GET.get('status')
|
||
if status:
|
||
queryset = queryset.filter(status=status)
|
||
|
||
# Filter by review type
|
||
review_type = self.request.GET.get('review_type')
|
||
if review_type:
|
||
queryset = queryset.filter(review_type=review_type)
|
||
|
||
return queryset.order_by('-review_date')
|
||
|
||
|
||
class PerformanceReviewDetailView(LoginRequiredMixin, DetailView):
|
||
"""
|
||
Display detailed information about a specific performance review.
|
||
"""
|
||
model = PerformanceReview
|
||
template_name = 'hr/reviews/performance_review_detail.html'
|
||
context_object_name = 'review'
|
||
|
||
def get_queryset(self):
|
||
# Eager load employee + department + supervisor to reduce queries
|
||
return (PerformanceReview.objects
|
||
.select_related('employee', 'employee__department', 'employee__supervisor', 'reviewer')
|
||
.filter(employee__tenant=self.request.user.tenant))
|
||
|
||
def get_object(self, queryset=None):
|
||
queryset = queryset or self.get_queryset()
|
||
return get_object_or_404(queryset, pk=self.kwargs.get('pk') or self.kwargs.get('id'))
|
||
|
||
@staticmethod
|
||
def _split_lines(text):
|
||
if not text:
|
||
return []
|
||
parts = []
|
||
for line in str(text).replace('\r', '').split('\n'):
|
||
for piece in line.split(';'):
|
||
piece = piece.strip()
|
||
if piece:
|
||
parts.append(piece)
|
||
return parts
|
||
|
||
def get_context_data(self, **kwargs):
|
||
ctx = super().get_context_data(**kwargs)
|
||
review = ctx['review']
|
||
|
||
# Build categories from competency_ratings JSON: { "Teamwork": 4.0, ... }
|
||
# Make a list of dicts the template can iterate through.
|
||
categories = []
|
||
ratings = review.competency_ratings or {}
|
||
# Keep a stable order (by key) to avoid chart jitter
|
||
for name in sorted(ratings.keys()):
|
||
try:
|
||
score = float(ratings[name])
|
||
except (TypeError, ValueError):
|
||
score = 0.0
|
||
categories.append({'name': name, 'score': score, 'comments': ''})
|
||
|
||
# Previous reviews (same employee, exclude current)
|
||
previous_reviews = (
|
||
PerformanceReview.objects
|
||
.filter(employee=review.employee)
|
||
.exclude(pk=review.pk)
|
||
.order_by('-review_date')[:5]
|
||
.select_related('employee')
|
||
)
|
||
|
||
# Strengths / AFI lists for bullet rendering
|
||
strengths_list = self._split_lines(review.strengths)
|
||
afi_list = self._split_lines(review.areas_for_improvement)
|
||
|
||
# Goals blocks as lists for nicer display
|
||
goals_achieved_list = self._split_lines(review.goals_achieved)
|
||
goals_not_achieved_list = self._split_lines(review.goals_not_achieved)
|
||
future_goals_list = self._split_lines(review.future_goals)
|
||
|
||
ctx.update({
|
||
'categories': categories,
|
||
'previous_reviews': previous_reviews,
|
||
'review_strengths_list': strengths_list,
|
||
'review_afi_list': afi_list,
|
||
'goals_achieved_list': goals_achieved_list,
|
||
'goals_not_achieved_list': goals_not_achieved_list,
|
||
'future_goals_list': future_goals_list,
|
||
})
|
||
|
||
# convenience on the review object (template already expects these list props sometimes)
|
||
setattr(review, 'strengths_list', strengths_list)
|
||
setattr(review, 'areas_for_improvement_list', afi_list)
|
||
|
||
return ctx
|
||
|
||
|
||
class PerformanceReviewCreateView(LoginRequiredMixin, CreateView):
|
||
"""
|
||
Create a new performance review.
|
||
"""
|
||
model = PerformanceReview
|
||
form_class = PerformanceReviewForm
|
||
template_name = 'hr/reviews/performance_review_form.html'
|
||
success_url = reverse_lazy('hr:performance_review_list')
|
||
|
||
def form_valid(self, form):
|
||
form.instance.reviewer = self.request.user
|
||
messages.success(self.request, 'Performance review created successfully.')
|
||
return super().form_valid(form)
|
||
|
||
def get_form_kwargs(self):
|
||
kwargs = super().get_form_kwargs()
|
||
kwargs['user'] = self.request.user
|
||
return kwargs
|
||
|
||
|
||
class PerformanceReviewUpdateView(LoginRequiredMixin, UpdateView):
|
||
"""
|
||
Update an existing performance review.
|
||
"""
|
||
model = PerformanceReview
|
||
form_class = PerformanceReviewForm
|
||
template_name = 'hr/reviews/performance_review_form.html'
|
||
|
||
def get_queryset(self):
|
||
return PerformanceReview.objects.filter(employee__tenant=self.request.user.tenant)
|
||
|
||
def get_success_url(self):
|
||
return reverse('hr:performance_review_detail', kwargs={'pk': self.object.pk})
|
||
|
||
def form_valid(self, form):
|
||
messages.success(self.request, 'Performance review updated successfully.')
|
||
return super().form_valid(form)
|
||
|
||
def get_form_kwargs(self):
|
||
kwargs = super().get_form_kwargs()
|
||
kwargs['user'] = self.request.user
|
||
return kwargs
|
||
|
||
|
||
class PerformanceReviewDeleteView(LoginRequiredMixin, DeleteView):
|
||
"""
|
||
Delete a performance review (only if not finalized).
|
||
"""
|
||
model = PerformanceReview
|
||
template_name = 'hr/reviews/performance_review_confirm_delete.html'
|
||
success_url = reverse_lazy('hr:performance_review_list')
|
||
|
||
def get_queryset(self):
|
||
return PerformanceReview.objects.filter(employee__tenant=self.request.user.tenant)
|
||
|
||
def delete(self, request, *args, **kwargs):
|
||
review = self.get_object()
|
||
|
||
# Check if review is finalized
|
||
if review.status == 'COMPLETED':
|
||
messages.error(request, 'Cannot delete completed performance reviews.')
|
||
return redirect('hr:performance_review_detail', pk=review.pk)
|
||
|
||
messages.success(request, 'Performance review deleted successfully.')
|
||
return super().delete(request, *args, **kwargs)
|
||
|
||
|
||
class TrainingManagementView(LoginRequiredMixin, ListView):
|
||
model = TrainingRecord
|
||
template_name = 'hr/training/training_management.html'
|
||
context_object_name = 'training_records'
|
||
paginate_by = 20
|
||
|
||
def get_queryset(self):
|
||
qs = (TrainingRecord.objects
|
||
.filter(employee__tenant=self.request.user.tenant)
|
||
.select_related('employee', 'employee__department')
|
||
.order_by('-training_date', '-completion_date'))
|
||
# optional GET filters (works with the template’s inputs)
|
||
if emp := self.request.GET.get('employee'):
|
||
qs = qs.filter(employee_id=emp)
|
||
if ttype := self.request.GET.get('training_type'):
|
||
qs = qs.filter(training_type=ttype)
|
||
if status := self.request.GET.get('status'):
|
||
qs = qs.filter(status=status)
|
||
return qs
|
||
|
||
def get_context_data(self, **kwargs):
|
||
ctx = super().get_context_data(**kwargs)
|
||
tenant = self.request.user.tenant
|
||
today = timezone.now().date()
|
||
|
||
base = TrainingRecord.objects.filter(employee__tenant=tenant)
|
||
|
||
total_records = base.count()
|
||
completed_trainings = base.filter(status='COMPLETED').count()
|
||
pending_trainings = base.filter(status__in=['SCHEDULED', 'IN_PROGRESS']).count()
|
||
overdue_trainings = base.filter(expiry_date__lt=today, expiry_date__isnull=False).exclude(
|
||
status='COMPLETED').count()
|
||
|
||
# compliance rate = “not expired” among trainings that have an expiry_date
|
||
with_expiry = base.filter(expiry_date__isnull=False)
|
||
valid_now = with_expiry.filter(expiry_date__gte=today).count()
|
||
compliance_rate = round((valid_now / with_expiry.count()) * 100, 1) if with_expiry.exists() else 100.0
|
||
|
||
# expiring soon = within 30 days
|
||
expiring_soon_count = with_expiry.filter(expiry_date__gte=today,
|
||
expiry_date__lte=today + timedelta(days=30)).count()
|
||
|
||
# department compliance (simple example: percent of non-expired per dept among those with expiry)
|
||
dept_rows = []
|
||
departments = Department.objects.filter(tenant=tenant).order_by('name')
|
||
for d in departments:
|
||
d_qs = with_expiry.filter(employee__department=d)
|
||
if not d_qs.exists():
|
||
rate = 100
|
||
else:
|
||
ok = d_qs.filter(expiry_date__gte=today).count()
|
||
rate = round((ok / d_qs.count()) * 100)
|
||
color = 'success' if rate >= 90 else 'warning' if rate >= 70 else 'danger'
|
||
dept_rows.append({'name': d.name, 'compliance_rate': rate, 'compliance_color': color})
|
||
|
||
# “compliance alerts” demo (overdue/expiring soon)
|
||
alerts = []
|
||
for tr in base.select_related('employee'):
|
||
if tr.expiry_date:
|
||
if tr.expiry_date < today:
|
||
alerts.append({
|
||
'id': tr.id,
|
||
'employee': tr.employee,
|
||
'requirement': tr.training_name,
|
||
'due_date': tr.expiry_date,
|
||
'priority_color': 'danger',
|
||
'urgency_color': 'danger',
|
||
'get_priority_display': 'Overdue',
|
||
})
|
||
elif today <= tr.expiry_date <= today + timedelta(days=30):
|
||
alerts.append({
|
||
'id': tr.id,
|
||
'employee': tr.employee,
|
||
'requirement': tr.training_name,
|
||
'due_date': tr.expiry_date,
|
||
'priority_color': 'warning',
|
||
'urgency_color': 'warning',
|
||
'get_priority_display': 'Expiring Soon',
|
||
})
|
||
|
||
ctx.update({
|
||
'total_records': total_records,
|
||
'completed_trainings': completed_trainings,
|
||
'pending_trainings': pending_trainings,
|
||
'overdue_trainings': overdue_trainings,
|
||
'departments': departments,
|
||
'compliance_rate': compliance_rate,
|
||
'expiring_soon_count': expiring_soon_count,
|
||
'department_compliance': dept_rows,
|
||
'compliance_alerts': alerts,
|
||
})
|
||
return ctx
|
||
|
||
class TrainingRecordListView(LoginRequiredMixin, ListView):
|
||
"""
|
||
List training records with filtering capabilities.
|
||
"""
|
||
model = TrainingRecord
|
||
template_name = 'hr/training/training_record_list.html'
|
||
context_object_name = 'training_records'
|
||
paginate_by = 20
|
||
|
||
def get_queryset(self):
|
||
queryset = TrainingRecord.objects.filter(
|
||
employee__tenant=self.request.user.tenant
|
||
).select_related('employee')
|
||
|
||
# Filter by employee
|
||
employee = self.request.GET.get('employee')
|
||
if employee:
|
||
queryset = queryset.filter(employee_id=employee)
|
||
|
||
# Filter by training type
|
||
training_type = self.request.GET.get('training_type')
|
||
if training_type:
|
||
queryset = queryset.filter(training_type=training_type)
|
||
|
||
# Filter by completion status
|
||
status = self.request.GET.get('status')
|
||
if status:
|
||
queryset = queryset.filter(status=status)
|
||
|
||
return queryset.order_by('-completion_date')
|
||
|
||
|
||
class TrainingRecordDetailView(LoginRequiredMixin, DetailView):
|
||
"""
|
||
Display detailed information about a specific training record.
|
||
"""
|
||
model = TrainingRecord
|
||
template_name = 'hr/training/training_record_detail.html'
|
||
context_object_name = 'record'
|
||
|
||
def get_queryset(self):
|
||
return TrainingRecord.objects.filter(employee__tenant=self.request.user.tenant)
|
||
|
||
|
||
class TrainingRecordCreateView(LoginRequiredMixin, CreateView):
|
||
"""
|
||
Create a new training record.
|
||
"""
|
||
model = TrainingRecord
|
||
form_class = TrainingRecordForm
|
||
template_name = 'hr/training/training_record_form.html'
|
||
success_url = reverse_lazy('hr:training_record_list')
|
||
|
||
def form_valid(self, form):
|
||
form.instance.created_by = self.request.user
|
||
messages.success(self.request, 'Training record created successfully.')
|
||
return super().form_valid(form)
|
||
|
||
def get_form_kwargs(self):
|
||
kwargs = super().get_form_kwargs()
|
||
kwargs['user'] = self.request.user
|
||
return kwargs
|
||
|
||
|
||
class TrainingRecordUpdateView(LoginRequiredMixin, UpdateView):
|
||
"""
|
||
Update an existing training record.
|
||
"""
|
||
model = TrainingRecord
|
||
form_class = TrainingRecordForm
|
||
template_name = 'hr/training/training_record_form.html'
|
||
|
||
def get_queryset(self):
|
||
return TrainingRecord.objects.filter(employee__tenant=self.request.user.tenant)
|
||
|
||
def get_success_url(self):
|
||
return reverse('hr:training_record_detail', kwargs={'pk': self.object.pk})
|
||
|
||
def form_valid(self, form):
|
||
messages.success(self.request, 'Training record updated successfully.')
|
||
return super().form_valid(form)
|
||
|
||
def get_form_kwargs(self):
|
||
kwargs = super().get_form_kwargs()
|
||
kwargs['user'] = self.request.user
|
||
return kwargs
|
||
|
||
|
||
class TrainingRecordDeleteView(LoginRequiredMixin, DeleteView):
|
||
"""
|
||
Delete a training record.
|
||
"""
|
||
model = TrainingRecord
|
||
template_name = 'hr/training/training_record_confirm_delete.html'
|
||
success_url = reverse_lazy('hr:training_record_list')
|
||
|
||
def get_queryset(self):
|
||
return TrainingRecord.objects.filter(employee__tenant=self.request.user.tenant)
|
||
|
||
def delete(self, request, *args, **kwargs):
|
||
messages.success(request, 'Training record deleted successfully.')
|
||
return super().delete(request, *args, **kwargs)
|
||
|
||
|
||
@login_required
|
||
def complete_performance_review(request, review_id):
|
||
review = get_object_or_404(PerformanceReview, pk=review_id)
|
||
review.status = 'COMPLETED'
|
||
review.completed_by = request.user
|
||
review.save()
|
||
|
||
messages.success(request, 'Performance review completed successfully.')
|
||
return redirect('hr:performance_review_detail', pk=review.pk)
|
||
|
||
@login_required
|
||
def hr_stats(request):
|
||
"""
|
||
Return HR statistics for dashboard updates.
|
||
"""
|
||
context = {
|
||
'total_employees': Employee.objects.filter(tenant=request.user.tenant).count(),
|
||
'active_employees': Employee.objects.filter(
|
||
tenant=request.user.tenant,
|
||
employment_status='ACTIVE'
|
||
).count(),
|
||
'total_departments': Department.objects.filter(tenant=request.user.tenant).count(),
|
||
'pending_reviews': PerformanceReview.objects.filter(
|
||
employee__tenant=request.user.tenant,
|
||
status='IN_PROGRESS'
|
||
).count(),
|
||
'employees_clocked_in': TimeEntry.objects.filter(
|
||
employee__tenant=request.user.tenant,
|
||
clock_in_time__date=timezone.now().date(),
|
||
clock_out_time__isnull=True
|
||
).count(),
|
||
}
|
||
|
||
return render(request, 'hr/partials/hr_stats.html', context)
|
||
|
||
|
||
@login_required
|
||
def employee_search(request):
|
||
"""
|
||
Search employees for HTMX updates.
|
||
"""
|
||
search = request.GET.get('search', '')
|
||
employees = Employee.objects.filter(
|
||
tenant=request.user.tenant
|
||
)
|
||
|
||
if search:
|
||
employees = employees.filter(
|
||
Q(first_name__icontains=search) |
|
||
Q(last_name__icontains=search) |
|
||
Q(employee_id__icontains=search)
|
||
)
|
||
|
||
employees = employees.select_related('department')[:20]
|
||
|
||
return render(request, 'hr/partials/employee_list.html', {
|
||
'employees': employees
|
||
})
|
||
|
||
|
||
@login_required
|
||
def attendance_summary(request):
|
||
"""
|
||
Return attendance summary for dashboard updates.
|
||
"""
|
||
today = timezone.now().date()
|
||
|
||
context = {
|
||
'employees_clocked_in': TimeEntry.objects.filter(
|
||
employee__tenant=request.user.tenant,
|
||
clock_in_time__date=today,
|
||
clock_out_time__isnull=True
|
||
).count(),
|
||
|
||
'total_hours_today': TimeEntry.objects.filter(
|
||
employee__tenant=request.user.tenant,
|
||
work_date=today,
|
||
clock_out_time__isnull=False
|
||
).aggregate(
|
||
total=Sum('total_hours')
|
||
)['total'] or 0,
|
||
|
||
'recent_clock_ins': TimeEntry.objects.filter(
|
||
employee__tenant=request.user.tenant,
|
||
clock_in_time__date=today
|
||
).select_related('employee').order_by('-clock_in_time')[:5],
|
||
}
|
||
|
||
return render(request, 'hr/partials/attendance_summary.html', context)
|
||
|
||
|
||
|
||
@require_GET
|
||
def clock_controls(request, employee_id):
|
||
"""Return the clock controls partial for today's state (HTMX GET)."""
|
||
employee = get_object_or_404(Employee, id=employee_id, tenant=request.user.tenant)
|
||
today = timezone.localdate()
|
||
|
||
# Prefer the open entry for today; otherwise the last entry today (finished)
|
||
time_entry = (
|
||
TimeEntry.objects
|
||
.filter(employee=employee, work_date=today)
|
||
.order_by('clock_out_time', '-clock_in_time') # open first (clock_out_time NULL), else latest finished
|
||
.first()
|
||
)
|
||
|
||
return render(request, 'hr/partials/clock_controls.html', {
|
||
'employee': employee,
|
||
'time_entry': time_entry,
|
||
})
|
||
|
||
|
||
def _render_controls(request, employee, time_entry):
|
||
html = render_to_string('hr/partials/clock_controls.html',
|
||
{'employee': employee, 'time_entry': time_entry},
|
||
request=request)
|
||
return HttpResponse(html)
|
||
|
||
@require_POST
|
||
def clock_in(request, employee_id):
|
||
employee = get_object_or_404(Employee, id=employee_id, tenant=request.user.tenant)
|
||
today = timezone.localdate()
|
||
|
||
open_entry = TimeEntry.objects.filter(
|
||
employee=employee, work_date=today, clock_out_time__isnull=True
|
||
).first()
|
||
if open_entry:
|
||
return _render_controls(request, employee, open_entry)
|
||
|
||
time_entry = TimeEntry.objects.create(
|
||
employee=employee, work_date=today, clock_in_time=timezone.now(), status='DRAFT'
|
||
)
|
||
|
||
if request.headers.get('HX-Request'):
|
||
return _render_controls(request, employee, time_entry)
|
||
return JsonResponse({'success': True, 'time_entry_id': time_entry.id})
|
||
|
||
@require_POST
|
||
def clock_out(request, employee_id):
|
||
employee = get_object_or_404(Employee, id=employee_id, tenant=request.user.tenant)
|
||
today = timezone.localdate()
|
||
|
||
time_entry = TimeEntry.objects.filter(
|
||
employee=employee, work_date=today, clock_out_time__isnull=True
|
||
).first()
|
||
if not time_entry:
|
||
# Re-render to default state (will show "Clock In")
|
||
if request.headers.get('HX-Request'):
|
||
return _render_controls(request, employee, None)
|
||
return JsonResponse({'success': False, 'message': 'No active clock-in found.'}, status=400)
|
||
|
||
time_entry.clock_out_time = timezone.now()
|
||
time_entry.save()
|
||
|
||
if request.headers.get('HX-Request'):
|
||
return _render_controls(request, employee, time_entry)
|
||
return JsonResponse({'success': True, 'time_entry_id': time_entry.id})
|
||
|
||
# @login_required
|
||
# @require_POST
|
||
# def clock_in(request, employee_id):
|
||
# """
|
||
# Clock in an employee. If HTMX, return the updated controls partial (button -> time).
|
||
# Otherwise return JSON.
|
||
# """
|
||
# # Prefer the URL param; fall back to POST field for safety
|
||
# emp_id = employee_id or request.POST.get('employee_id')
|
||
#
|
||
# try:
|
||
# employee = Employee.objects.get(id=emp_id, tenant=request.user.tenant)
|
||
# except Employee.DoesNotExist:
|
||
# if request.headers.get('HX-Request'):
|
||
# return HttpResponse("Employee not found.", status=404)
|
||
# return JsonResponse({'success': False, 'message': 'Employee not found.'}, status=404)
|
||
#
|
||
# today = timezone.now().date()
|
||
# open_entry = TimeEntry.objects.filter(
|
||
# employee=employee,
|
||
# work_date=today,
|
||
# clock_out_time__isnull=True
|
||
# ).first()
|
||
#
|
||
# if open_entry:
|
||
# # Already clocked in — just re-render the controls so the UI shows the time
|
||
# if request.headers.get('HX-Request'):
|
||
# html = render_to_string(
|
||
# 'hr/partials/clock_controls.html',
|
||
# {'employee': employee, 'time_entry': open_entry},
|
||
# request=request
|
||
# )
|
||
# return HttpResponse(html)
|
||
# return JsonResponse({'success': False, 'message': 'Employee is already clocked in.'}, status=400)
|
||
#
|
||
# # Create new time entry
|
||
# time_entry = TimeEntry.objects.create(
|
||
# employee=employee,
|
||
# work_date=today,
|
||
# clock_in_time=timezone.now(),
|
||
# status='DRAFT'
|
||
# )
|
||
#
|
||
# if request.headers.get('HX-Request'):
|
||
# html = render_to_string(
|
||
# 'hr/partials/clock_controls.html',
|
||
# {'employee': employee, 'time_entry': time_entry},
|
||
# request=request
|
||
# )
|
||
# return HttpResponse(html)
|
||
#
|
||
# return JsonResponse({
|
||
# 'success': True,
|
||
# 'message': f'{employee.get_full_name()} clocked in successfully.',
|
||
# 'time_entry_id': time_entry.id
|
||
# })
|
||
#
|
||
#
|
||
# @login_required
|
||
# @require_POST
|
||
# def clock_out(request, employee_id):
|
||
# """
|
||
# Clock out an employee.
|
||
# If HTMX, return the updated controls partial (button -> times).
|
||
# Otherwise, return JSON.
|
||
# """
|
||
# emp_id = employee_id or request.POST.get('employee_id')
|
||
#
|
||
# # Find employee in same tenant
|
||
# try:
|
||
# employee = Employee.objects.get(id=emp_id, tenant=request.user.tenant)
|
||
# except Employee.DoesNotExist:
|
||
# if request.headers.get('HX-Request'):
|
||
# # Re-render controls without an active entry (will show "Clock In")
|
||
# html = render_to_string(
|
||
# 'hr/partials/clock_controls.html',
|
||
# {'employee': None, 'time_entry': None}, # or pass the real employee if you want the button still visible
|
||
# request=request
|
||
# )
|
||
# return HttpResponse(html, status=404)
|
||
# return JsonResponse({'success': False, 'message': 'Employee not found.'}, status=404)
|
||
#
|
||
# # Locate today's open entry
|
||
# today = timezone.now().date()
|
||
# time_entry = TimeEntry.objects.filter(
|
||
# employee=employee,
|
||
# work_date=today,
|
||
# clock_out_time__isnull=True
|
||
# ).first()
|
||
#
|
||
# if not time_entry:
|
||
# if request.headers.get('HX-Request'):
|
||
# # No open entry → re-render to show "Clock In"
|
||
# html = render_to_string(
|
||
# 'hr/partials/clock_controls.html',
|
||
# {'employee': employee, 'time_entry': None},
|
||
# request=request
|
||
# )
|
||
# return HttpResponse(html, status=400)
|
||
# return JsonResponse({'success': False, 'message': 'No active clock-in found for this employee.'}, status=400)
|
||
#
|
||
# # Close the entry
|
||
# time_entry.clock_out_time = timezone.now()
|
||
# time_entry.save() # if your model computes total_hours on save
|
||
#
|
||
# if request.headers.get('HX-Request'):
|
||
# html = render_to_string(
|
||
# 'hr/partials/clock_controls.html',
|
||
# {'employee': employee, 'time_entry': time_entry},
|
||
# request=request
|
||
# )
|
||
# return HttpResponse(html)
|
||
#
|
||
# return JsonResponse({
|
||
# 'success': True,
|
||
# 'message': f'{employee.get_full_name()} clocked out successfully.',
|
||
# 'time_entry_id': time_entry.id,
|
||
# # If total_hours is Decimal, this makes it JSON-safe:
|
||
# 'hours_worked': float(getattr(time_entry, 'total_hours', 0) or 0)
|
||
# })
|
||
|
||
|
||
@login_required
|
||
def approve_time_entry(request, entry_id):
|
||
"""
|
||
Approve a time entry.
|
||
"""
|
||
time_entry = get_object_or_404(
|
||
TimeEntry,
|
||
id=entry_id,
|
||
employee__tenant=request.user.tenant
|
||
)
|
||
|
||
if request.method == 'POST':
|
||
# Check if entry is complete
|
||
if not time_entry.clock_out_time:
|
||
messages.error(request, 'Cannot approve incomplete time entry.')
|
||
return redirect('hr:time_entry_detail', pk=time_entry.id)
|
||
|
||
# Update time entry
|
||
time_entry.status = 'APPROVED'
|
||
time_entry.approved_by = request.user
|
||
time_entry.approval_date = timezone.now()
|
||
time_entry.save()
|
||
|
||
messages.success(request, 'Time entry approved successfully.')
|
||
|
||
# Redirect based on source
|
||
next_url = request.POST.get('next', reverse('hr:time_entry_detail', kwargs={'pk': time_entry.id}))
|
||
return redirect(next_url)
|
||
|
||
return render(request, 'hr/time_entries/time_entry_approve.html', {
|
||
'time_entry': time_entry
|
||
})
|
||
|
||
|
||
@login_required
|
||
def publish_schedule(request, schedule_id):
|
||
"""
|
||
Publish a schedule.
|
||
"""
|
||
schedule = get_object_or_404(
|
||
Schedule,
|
||
id=schedule_id,
|
||
employee__tenant=request.user.tenant
|
||
)
|
||
|
||
if request.method == 'POST':
|
||
# Check if schedule has assignments
|
||
if not ScheduleAssignment.objects.filter(schedule=schedule).exists():
|
||
messages.error(request, 'Cannot publish empty schedule.')
|
||
return redirect('hr:schedule_detail', pk=schedule.id)
|
||
|
||
# Update schedule
|
||
schedule.approved_by = request.user
|
||
schedule.approval_date = timezone.now()
|
||
schedule.save()
|
||
|
||
messages.success(request, 'Schedule published successfully.')
|
||
return redirect('hr:schedule_detail', pk=schedule.id)
|
||
|
||
return render(request, 'hr/schedules/schedule_publish.html', {
|
||
'schedule': schedule
|
||
})
|
||
|
||
|
||
@login_required
|
||
def api_employee_list(request):
|
||
"""
|
||
API endpoint for employee list.
|
||
"""
|
||
employees = Employee.objects.filter(
|
||
tenant=request.user.tenant
|
||
).values('employee_id', 'first_name', 'father_name', 'grandfather_name', 'last_name', 'job_title')
|
||
|
||
return JsonResponse({'employees': list(employees)})
|
||
|
||
|
||
@login_required
|
||
def api_department_list(request):
|
||
"""
|
||
API endpoint for department list.
|
||
"""
|
||
departments = Department.objects.filter(
|
||
tenant=request.user.tenant
|
||
).values('id', 'name', 'department_code')
|
||
|
||
return JsonResponse({'departments': list(departments)})
|
||
|
||
|
||
def department_tree(request):
|
||
"""
|
||
HTMX view for department tree structure.
|
||
"""
|
||
departments = Department.objects.filter(
|
||
tenant=request.user.tenant,
|
||
parent=None
|
||
).prefetch_related('children')
|
||
|
||
return render(request, 'core/partials/department_tree.html', {
|
||
'departments': departments
|
||
})
|
||
|
||
|
||
def activate_department(request, pk):
|
||
"""
|
||
Activate a department.
|
||
"""
|
||
department = get_object_or_404(Department, department_id=pk)
|
||
department.is_active = True
|
||
department.save()
|
||
|
||
messages.success(request, f'Department "{department.name}" has been activated.')
|
||
return redirect('hr:department_detail', pk=pk)
|
||
|
||
|
||
def deactivate_department(request, pk):
|
||
"""
|
||
Deactivate a department.
|
||
"""
|
||
department = get_object_or_404(Department, department_id=pk)
|
||
department.is_active = False
|
||
department.save()
|
||
|
||
messages.success(request, f'Department "{department.name}" has been deactivated.')
|
||
return redirect('hr:department_detail', pk=pk)
|
||
|
||
|
||
def department_search(request):
|
||
"""
|
||
AJAX search for departments.
|
||
"""
|
||
query = request.GET.get('q', '')
|
||
departments = []
|
||
|
||
if query:
|
||
departments = Department.objects.filter(
|
||
tenant=request.user.tenant,
|
||
name__icontains=query
|
||
).values('department_id', 'name', 'department_type')[:10]
|
||
|
||
return JsonResponse({'departments': list(departments)})
|
||
|
||
|
||
def bulk_activate_departments(request):
|
||
"""
|
||
Bulk activate departments.
|
||
"""
|
||
if request.method == 'POST':
|
||
department_ids = request.POST.getlist('department_ids')
|
||
count = Department.objects.filter(
|
||
tenant=request.user.tenant,
|
||
department_id__in=department_ids
|
||
).update(is_active=True)
|
||
|
||
messages.success(request, f'{count} departments have been activated.')
|
||
|
||
return redirect('hr:department_list')
|
||
|
||
|
||
def bulk_deactivate_departments(request):
|
||
"""
|
||
Bulk deactivate departments.
|
||
"""
|
||
if request.method == 'POST':
|
||
department_ids = request.POST.getlist('department_ids')
|
||
count = Department.objects.filter(
|
||
tenant=request.user.tenant,
|
||
department_id__in=department_ids
|
||
).update(is_active=False)
|
||
|
||
messages.success(request, f'{count} departments have been deactivated.')
|
||
|
||
return redirect('hr:department_list')
|
||
|
||
|
||
def get_department_hierarchy(request):
|
||
"""
|
||
Get department hierarchy as JSON.
|
||
"""
|
||
departments = Department.objects.filter(
|
||
tenant=request.user.tenant,
|
||
is_active=True
|
||
).select_related('parent')
|
||
|
||
def build_tree(parent=None):
|
||
children = []
|
||
for dept in departments:
|
||
if dept.parent == parent:
|
||
children.append({
|
||
'id': str(dept.department_id),
|
||
'name': dept.name,
|
||
'type': dept.department_type,
|
||
'children': build_tree(dept)
|
||
})
|
||
return children
|
||
|
||
hierarchy = build_tree()
|
||
return JsonResponse({'hierarchy': hierarchy})
|
||
|
||
|
||
@login_required
|
||
def assign_department_head(request, pk):
|
||
"""
|
||
Assign a department head to a department.
|
||
"""
|
||
department = get_object_or_404(Department, pk=pk, tenant=request.user.tenant)
|
||
|
||
if request.method == 'POST':
|
||
user_id = request.POST.get('user_id')
|
||
|
||
if user_id:
|
||
try:
|
||
user = User.objects.get(id=user_id, tenant=request.user.tenant)
|
||
|
||
# Remove current department head if exists
|
||
if department.department_head:
|
||
old_head = department.department_head
|
||
AuditLogger.log_event(
|
||
request=request,
|
||
event_type='UPDATE',
|
||
event_category='SYSTEM_ADMINISTRATION',
|
||
action=f'Removed department head from {department.name}',
|
||
description=f'Removed {old_head.get_full_name()} as head of {department.name}',
|
||
content_object=department,
|
||
additional_data={
|
||
'old_department_head_id': old_head.id,
|
||
'old_department_head_name': old_head.get_full_name()
|
||
}
|
||
)
|
||
|
||
# Assign new department head
|
||
department.department_head = user
|
||
department.save()
|
||
|
||
# Log the assignment
|
||
AuditLogger.log_event(
|
||
request=request,
|
||
event_type='UPDATE',
|
||
event_category='SYSTEM_ADMINISTRATION',
|
||
action=f'Assigned department head to {department.name}',
|
||
description=f'Assigned {user.get_full_name()} as head of {department.name}',
|
||
content_object=department,
|
||
additional_data={
|
||
'new_department_head_id': user.id,
|
||
'new_department_head_name': user.get_full_name()
|
||
}
|
||
)
|
||
|
||
messages.success(
|
||
request,
|
||
f'{user.get_full_name()} has been assigned as head of {department.name}.'
|
||
)
|
||
return redirect('core:department_detail', pk=department.pk)
|
||
|
||
except User.DoesNotExist:
|
||
messages.error(request, 'Selected user not found.')
|
||
else:
|
||
# Remove department head
|
||
if department.department_head:
|
||
old_head = department.department_head
|
||
department.department_head = None
|
||
department.save()
|
||
|
||
# Log the removal
|
||
AuditLogger.log_event(
|
||
request=request,
|
||
event_type='UPDATE',
|
||
event_category='SYSTEM_ADMINISTRATION',
|
||
action=f'Removed department head from {department.name}',
|
||
description=f'Removed {old_head.get_full_name()} as head of {department.name}',
|
||
content_object=department,
|
||
additional_data={
|
||
'removed_department_head_id': old_head.id,
|
||
'removed_department_head_name': old_head.get_full_name()
|
||
}
|
||
)
|
||
|
||
messages.success(
|
||
request,
|
||
f'Department head has been removed from {department.name}.'
|
||
)
|
||
else:
|
||
messages.info(request, 'No department head was assigned.')
|
||
|
||
return redirect('core:department_detail', pk=department.pk)
|
||
|
||
# Get eligible users (staff members who can be department heads)
|
||
eligible_users = User.objects.filter(
|
||
tenant=request.user.tenant,
|
||
is_active=True,
|
||
is_staff=True
|
||
).exclude(
|
||
id=department.department_head.id if department.department_head else None
|
||
).order_by('first_name', 'last_name')
|
||
|
||
context = {
|
||
'department': department,
|
||
'eligible_users': eligible_users,
|
||
'current_head': department.department_head,
|
||
}
|
||
|
||
return render(request, 'hr/departments/assign_department_head.html', context)
|
||
|
||
|
||
|
||
# Query patterns to use if needed
|
||
# # All upcoming sessions for a tenant (next 30 days)
|
||
# TrainingSession.objects.filter(
|
||
# tenant=request.user.tenant, start_at__gte=timezone.now(),
|
||
# start_at__lte=timezone.now() + timedelta(days=30)
|
||
# ).select_related('program', 'instructor')
|
||
#
|
||
# # Employees due for renewal in 30 days
|
||
# TrainingCertificates.objects.filter(
|
||
# tenant=request.user.tenant,
|
||
# expiry_date__lte=date.today() + timedelta(days=30),
|
||
# expiry_date__gte=date.today()
|
||
# ).select_related('employee', 'program')
|
||
#
|
||
# # Enroll an employee, respecting capacity
|
||
# session = TrainingSession.objects.select_for_update().get(pk=session_pk, tenant=tenant)
|
||
# if session.capacity and session.enrollments.count() >= session.capacity:
|
||
# status = 'WAITLISTED'
|
||
# else:
|
||
# status = 'SCHEDULED'
|
||
# enrollment = TrainingRecord.objects.create(
|
||
# tenant=tenant, employee=emp, program=session.program, session=session,
|
||
# status=status, created_by=request.user
|
||
# )
|
||
#
|
||
# # Mark completion + pass (auto-certificate will fire via signal)
|
||
# enrollment.status = 'COMPLETED'
|
||
# enrollment.passed = True
|
||
# enrollment.completion_date = date.today()
|
||
# enrollment.credits_earned = enrollment.hours
|
||
# enrollment.save()
|
||
|
||
|
||
#
|
||
# from django.shortcuts import render, redirect, get_object_or_404
|
||
# from django.views.generic import (
|
||
# ListView, DetailView, CreateView, UpdateView, DeleteView
|
||
# )
|
||
# from django.urls import reverse, reverse_lazy
|
||
# from django.contrib.auth.decorators import login_required
|
||
# from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
|
||
# from django.contrib import messages
|
||
# from django.utils import timezone
|
||
# from django.http import HttpResponse, JsonResponse
|
||
# from django.db.models import Q, Count, Sum, Avg, F, ExpressionWrapper, fields
|
||
# from django.template.loader import render_to_string
|
||
# from django.core.paginator import Paginator
|
||
#
|
||
# from .models import (
|
||
# Department, Employee, Schedule, ScheduleAssignment,
|
||
# TimeEntry, PerformanceReview, TrainingRecord
|
||
# )
|
||
# from .forms import (
|
||
# DepartmentForm, EmployeeForm, ScheduleForm, ScheduleAssignmentForm,
|
||
# TimeEntryForm, PerformanceReviewForm, TrainingRecordForm,
|
||
# HRSearchForm, TimeEntrySearchForm
|
||
# )
|
||
#
|
||
#
|
||
# class DashboardView(LoginRequiredMixin, ListView):
|
||
# """
|
||
# HR dashboard view displaying summary statistics.
|
||
# """
|
||
# template_name = 'hr/dashboard.html'
|
||
# context_object_name = 'employees'
|
||
# paginate_by = 10
|
||
#
|
||
# def get_queryset(self):
|
||
# """
|
||
# Get recent employees as primary queryset.
|
||
# """
|
||
# return Employee.objects.filter(
|
||
# tenant=self.request.user.tenant,
|
||
# is_active=True
|
||
# ).order_by('-hire_date')[:10]
|
||
#
|
||
# def get_context_data(self, **kwargs):
|
||
# context = super().get_context_data(**kwargs)
|
||
# tenant = self.request.user.tenant
|
||
#
|
||
# # Employee statistics
|
||
# context['total_employees'] = Employee.objects.filter(
|
||
# tenant=tenant
|
||
# ).count()
|
||
#
|
||
# context['active_employees'] = Employee.objects.filter(
|
||
# tenant=tenant,
|
||
# is_active=True
|
||
# ).count()
|
||
#
|
||
# context['department_stats'] = Department.objects.filter(
|
||
# tenant=tenant
|
||
# ).annotate(
|
||
# employee_count=Count('employees', filter=Q(employees__is_active=True))
|
||
# ).order_by('-employee_count')[:5]
|
||
#
|
||
# # Recent hires
|
||
# context['recent_hires'] = Employee.objects.filter(
|
||
# tenant=tenant
|
||
# ).order_by('-hire_date')[:5]
|
||
#
|
||
# # Upcoming reviews
|
||
# context['upcoming_reviews'] = PerformanceReview.objects.filter(
|
||
# tenant=tenant,
|
||
# status='PENDING',
|
||
# scheduled_date__gte=timezone.now().date()
|
||
# ).order_by('scheduled_date')[:5]
|
||
#
|
||
# # Employees on leave
|
||
# context['employees_on_leave'] = Employee.objects.filter(
|
||
# tenant=tenant,
|
||
# employment_status='LEAVE'
|
||
# ).order_by('last_name')
|
||
#
|
||
# # Training expiring soon
|
||
# context['expiring_trainings'] = TrainingRecord.objects.filter(
|
||
# tenant=tenant,
|
||
# status='COMPLETED',
|
||
# expiry_date__gte=timezone.now().date(),
|
||
# expiry_date__lte=timezone.now().date() + timezone.timedelta(days=30)
|
||
# ).order_by('expiry_date')[:5]
|
||
#
|
||
# # Time entry approvals pending
|
||
# context['pending_time_approvals'] = TimeEntry.objects.filter(
|
||
# tenant=tenant,
|
||
# is_approved=False,
|
||
# entry_date__gte=timezone.now().date() - timezone.timedelta(days=14)
|
||
# ).count()
|
||
#
|
||
# # Search form
|
||
# context['search_form'] = HRSearchForm(tenant=tenant)
|
||
#
|
||
# return context
|
||
#
|
||
#
|
||
# class EmployeeListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
|
||
# """
|
||
# List view for employees.
|
||
# """
|
||
# model = Employee
|
||
# template_name = 'hr/employee_list.html'
|
||
# context_object_name = 'employees'
|
||
# paginate_by = 20
|
||
# permission_required = 'hr.view_employee'
|
||
#
|
||
# def get_queryset(self):
|
||
# """
|
||
# Filter employees by tenant and apply search filters.
|
||
# """
|
||
# queryset = Employee.objects.filter(tenant=self.request.user.tenant)
|
||
#
|
||
# # Apply search filters
|
||
# form = HRSearchForm(self.request.GET, tenant=self.request.user.tenant)
|
||
# if form.is_valid():
|
||
# data = form.cleaned_data
|
||
#
|
||
# # Search term
|
||
# if data.get('search'):
|
||
# search_term = data['search']
|
||
# queryset = queryset.filter(
|
||
# Q(first_name__icontains=search_term) |
|
||
# Q(last_name__icontains=search_term) |
|
||
# Q(employee_number__icontains=search_term) |
|
||
# Q(position__icontains=search_term) |
|
||
# Q(personal_email__icontains=search_term) |
|
||
# Q(work_email__icontains=search_term)
|
||
# )
|
||
#
|
||
# # Department filter
|
||
# if data.get('department'):
|
||
# queryset = queryset.filter(department=data['department'])
|
||
#
|
||
# # Employment status filter
|
||
# if data.get('employment_status'):
|
||
# queryset = queryset.filter(employment_status=data['employment_status'])
|
||
#
|
||
# # Employment type filter
|
||
# if data.get('employment_type'):
|
||
# queryset = queryset.filter(employment_type=data['employment_type'])
|
||
#
|
||
# # Position filter
|
||
# if data.get('position'):
|
||
# queryset = queryset.filter(position__icontains=data['position'])
|
||
#
|
||
# # Hire date range
|
||
# if data.get('hire_date_from'):
|
||
# queryset = queryset.filter(hire_date__gte=data['hire_date_from'])
|
||
#
|
||
# if data.get('hire_date_to'):
|
||
# queryset = queryset.filter(hire_date__lte=data['hire_date_to'])
|
||
#
|
||
# # Active status
|
||
# if data.get('is_active') == 'True':
|
||
# queryset = queryset.filter(is_active=True)
|
||
# elif data.get('is_active') == 'False':
|
||
# queryset = queryset.filter(is_active=False)
|
||
#
|
||
# return queryset.select_related('department', 'supervisor')
|
||
#
|
||
# def get_context_data(self, **kwargs):
|
||
# """
|
||
# Add search form to context.
|
||
# """
|
||
# context = super().get_context_data(**kwargs)
|
||
# context['search_form'] = HRSearchForm(
|
||
# self.request.GET or None,
|
||
# tenant=self.request.user.tenant
|
||
# )
|
||
# return context
|
||
#
|
||
#
|
||
# class EmployeeDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
|
||
# """
|
||
# Detail view for employees.
|
||
# """
|
||
# model = Employee
|
||
# template_name = 'hr/employee_detail.html'
|
||
# context_object_name = 'employee'
|
||
# permission_required = 'hr.view_employee'
|
||
#
|
||
# def get_queryset(self):
|
||
# """
|
||
# Filter employee by tenant.
|
||
# """
|
||
# return Employee.objects.filter(
|
||
# tenant=self.request.user.tenant
|
||
# ).select_related(
|
||
# 'department', 'supervisor', 'user', 'created_by'
|
||
# )
|
||
#
|
||
# def get_context_data(self, **kwargs):
|
||
# """
|
||
# Add related objects to context.
|
||
# """
|
||
# context = super().get_context_data(**kwargs)
|
||
# employee = self.object
|
||
#
|
||
# # Current schedule
|
||
# context['current_schedule'] = Schedule.objects.filter(
|
||
# employee=employee,
|
||
# is_current=True,
|
||
# is_active=True
|
||
# ).first()
|
||
#
|
||
# # Recent time entries
|
||
# context['recent_time_entries'] = TimeEntry.objects.filter(
|
||
# employee=employee
|
||
# ).order_by('-entry_date', '-created_at')[:10]
|
||
#
|
||
# # Performance reviews
|
||
# context['performance_reviews'] = PerformanceReview.objects.filter(
|
||
# employee=employee
|
||
# ).order_by('-scheduled_date')[:5]
|
||
#
|
||
# # Training records
|
||
# context['training_records'] = TrainingRecord.objects.filter(
|
||
# employee=employee
|
||
# ).order_by('-start_date')[:10]
|
||
#
|
||
# # Subordinates
|
||
# context['subordinates'] = Employee.objects.filter(
|
||
# supervisor=employee,
|
||
# is_active=True
|
||
# ).order_by('last_name', 'first_name')
|
||
#
|
||
# return context
|
||
#
|
||
#
|
||
# class EmployeeCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
|
||
# """
|
||
# Create view for employees.
|
||
# """
|
||
# model = Employee
|
||
# form_class = EmployeeForm
|
||
# template_name = 'hr/employee_form.html'
|
||
# permission_required = 'hr.add_employee'
|
||
#
|
||
# def get_form_kwargs(self):
|
||
# """
|
||
# Pass tenant and user to form.
|
||
# """
|
||
# kwargs = super().get_form_kwargs()
|
||
# kwargs['tenant'] = self.request.user.tenant
|
||
# kwargs['user'] = self.request.user
|
||
# return kwargs
|
||
#
|
||
# def form_valid(self, form):
|
||
# """
|
||
# Set tenant and user before saving.
|
||
# """
|
||
# form.instance.tenant = self.request.user.tenant
|
||
# form.instance.created_by = self.request.user
|
||
#
|
||
# response = super().form_valid(form)
|
||
#
|
||
# messages.success(
|
||
# self.request,
|
||
# f"Employee {form.instance.get_full_name()} created successfully."
|
||
# )
|
||
#
|
||
# return response
|
||
#
|
||
# def get_success_url(self):
|
||
# """
|
||
# Redirect to employee detail view.
|
||
# """
|
||
# return reverse('hr:employee_detail', kwargs={'pk': self.object.pk})
|
||
#
|
||
#
|
||
# class EmployeeUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
|
||
# """
|
||
# Update view for employees.
|
||
# """
|
||
# model = Employee
|
||
# form_class = EmployeeForm
|
||
# template_name = 'hr/employee_form.html'
|
||
# permission_required = 'hr.change_employee'
|
||
#
|
||
# def get_queryset(self):
|
||
# """
|
||
# Filter employee by tenant.
|
||
# """
|
||
# return Employee.objects.filter(tenant=self.request.user.tenant)
|
||
#
|
||
# def get_form_kwargs(self):
|
||
# """
|
||
# Pass tenant and user to form.
|
||
# """
|
||
# kwargs = super().get_form_kwargs()
|
||
# kwargs['tenant'] = self.request.user.tenant
|
||
# kwargs['user'] = self.request.user
|
||
# return kwargs
|
||
#
|
||
# def form_valid(self, form):
|
||
# """
|
||
# Handle successful form validation.
|
||
# """
|
||
# response = super().form_valid(form)
|
||
#
|
||
# messages.success(
|
||
# self.request,
|
||
# f"Employee {form.instance.get_full_name()} updated successfully."
|
||
# )
|
||
#
|
||
# return response
|
||
#
|
||
# def get_success_url(self):
|
||
# """
|
||
# Redirect to employee detail view.
|
||
# """
|
||
# return reverse('hr:employee_detail', kwargs={'pk': self.object.pk})
|
||
#
|
||
#
|
||
# class EmployeeDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteView):
|
||
# """
|
||
# Delete view for employees.
|
||
# """
|
||
# model = Employee
|
||
# template_name = 'hr/employee_confirm_delete.html'
|
||
# permission_required = 'hr.delete_employee'
|
||
#
|
||
# def get_queryset(self):
|
||
# """
|
||
# Filter employee by tenant.
|
||
# """
|
||
# return Employee.objects.filter(tenant=self.request.user.tenant)
|
||
#
|
||
# def delete(self, request, *args, **kwargs):
|
||
# """
|
||
# Override delete to show success message.
|
||
# """
|
||
# self.object = self.get_object()
|
||
# name = self.object.get_full_name()
|
||
#
|
||
# # Check if employee can be safely deleted
|
||
# if self.object.managed_departments.exists():
|
||
# messages.error(
|
||
# request,
|
||
# f"Cannot delete {name} because they are assigned as a department manager."
|
||
# )
|
||
# return redirect('hr:employee_detail', pk=self.object.pk)
|
||
#
|
||
# if self.object.subordinates.exists():
|
||
# messages.error(
|
||
# request,
|
||
# f"Cannot delete {name} because they are assigned as a supervisor."
|
||
# )
|
||
# return redirect('hr:employee_detail', pk=self.object.pk)
|
||
#
|
||
# success_url = self.get_success_url()
|
||
# self.object.delete()
|
||
#
|
||
# messages.success(request, f"Employee {name} deleted successfully.")
|
||
#
|
||
# return redirect(success_url)
|
||
#
|
||
# def get_success_url(self):
|
||
# """
|
||
# Redirect to employee list view.
|
||
# """
|
||
# return reverse_lazy('hr:employee_list')
|
||
#
|
||
#
|
||
# class DepartmentListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
|
||
# """
|
||
# List view for departments.
|
||
# """
|
||
# model = Department
|
||
# template_name = 'hr/department_list.html'
|
||
# context_object_name = 'departments'
|
||
# paginate_by = 20
|
||
# permission_required = 'hr.view_department'
|
||
#
|
||
# def get_queryset(self):
|
||
# """
|
||
# Filter departments by tenant.
|
||
# """
|
||
# queryset = Department.objects.filter(tenant=self.request.user.tenant)
|
||
#
|
||
# # Apply search filters
|
||
# search = self.request.GET.get('search', '')
|
||
# if search:
|
||
# queryset = queryset.filter(
|
||
# Q(name__icontains=search) |
|
||
# Q(code__icontains=search) |
|
||
# Q(description__icontains=search)
|
||
# )
|
||
#
|
||
# department_type = self.request.GET.get('department_type', '')
|
||
# if department_type:
|
||
# queryset = queryset.filter(department_type=department_type)
|
||
#
|
||
# is_active = self.request.GET.get('is_active', '')
|
||
# if is_active == 'True':
|
||
# queryset = queryset.filter(is_active=True)
|
||
# elif is_active == 'False':
|
||
# queryset = queryset.filter(is_active=False)
|
||
#
|
||
# return queryset.select_related('manager', 'parent_department')
|
||
#
|
||
# def get_context_data(self, **kwargs):
|
||
# """
|
||
# Add filter choices to context.
|
||
# """
|
||
# context = super().get_context_data(**kwargs)
|
||
# context['search'] = self.request.GET.get('search', '')
|
||
# context['department_types'] = Department.DEPARTMENT_TYPES
|
||
# context['selected_type'] = self.request.GET.get('department_type', '')
|
||
# context['selected_active'] = self.request.GET.get('is_active', '')
|
||
# return context
|
||
#
|
||
#
|
||
# class DepartmentDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
|
||
# """
|
||
# Detail view for departments.
|
||
# """
|
||
# model = Department
|
||
# template_name = 'hr/department_detail.html'
|
||
# context_object_name = 'department'
|
||
# permission_required = 'hr.view_department'
|
||
#
|
||
# def get_queryset(self):
|
||
# """
|
||
# Filter department by tenant.
|
||
# """
|
||
# return Department.objects.filter(
|
||
# tenant=self.request.user.tenant
|
||
# ).select_related(
|
||
# 'manager', 'parent_department', 'created_by'
|
||
# )
|
||
#
|
||
# def get_context_data(self, **kwargs):
|
||
# """
|
||
# Add related objects to context.
|
||
# """
|
||
# context = super().get_context_data(**kwargs)
|
||
# department = self.object
|
||
#
|
||
# # Employees in department
|
||
# context['employees'] = Employee.objects.filter(
|
||
# department=department,
|
||
# is_active=True
|
||
# ).order_by('last_name', 'first_name')
|
||
#
|
||
# # Sub-departments
|
||
# context['sub_departments'] = Department.objects.filter(
|
||
# parent_department=department,
|
||
# is_active=True
|
||
# ).order_by('name')
|
||
#
|
||
# return context
|
||
#
|
||
#
|
||
# class DepartmentCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
|
||
# """
|
||
# Create view for departments.
|
||
# """
|
||
# model = Department
|
||
# form_class = DepartmentForm
|
||
# template_name = 'hr/department_form.html'
|
||
# permission_required = 'hr.add_department'
|
||
#
|
||
# def get_form_kwargs(self):
|
||
# """
|
||
# Pass tenant and user to form.
|
||
# """
|
||
# kwargs = super().get_form_kwargs()
|
||
# kwargs['tenant'] = self.request.user.tenant
|
||
# kwargs['user'] = self.request.user
|
||
# return kwargs
|
||
#
|
||
# def form_valid(self, form):
|
||
# """
|
||
# Set tenant and user before saving.
|
||
# """
|
||
# form.instance.tenant = self.request.user.tenant
|
||
# form.instance.created_by = self.request.user
|
||
#
|
||
# response = super().form_valid(form)
|
||
#
|
||
# messages.success(
|
||
# self.request,
|
||
# f"Department {form.instance.name} created successfully."
|
||
# )
|
||
#
|
||
# return response
|
||
#
|
||
# def get_success_url(self):
|
||
# """
|
||
# Redirect to department detail view.
|
||
# """
|
||
# return reverse('hr:department_detail', kwargs={'pk': self.object.pk})
|
||
#
|
||
#
|
||
# class DepartmentUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
|
||
# """
|
||
# Update view for departments.
|
||
# """
|
||
# model = Department
|
||
# form_class = DepartmentForm
|
||
# template_name = 'hr/department_form.html'
|
||
# permission_required = 'hr.change_department'
|
||
#
|
||
# def get_queryset(self):
|
||
# """
|
||
# Filter department by tenant.
|
||
# """
|
||
# return Department.objects.filter(tenant=self.request.user.tenant)
|
||
#
|
||
# def get_form_kwargs(self):
|
||
# """
|
||
# Pass tenant and user to form.
|
||
# """
|
||
# kwargs = super().get_form_kwargs()
|
||
# kwargs['tenant'] = self.request.user.tenant
|
||
# kwargs['user'] = self.request.user
|
||
# return kwargs
|
||
#
|
||
# def form_valid(self, form):
|
||
# """
|
||
# Handle successful form validation.
|
||
# """
|
||
# response = super().form_valid(form)
|
||
#
|
||
# messages.success(
|
||
# self.request,
|
||
# f"Department {form.instance.name} updated successfully."
|
||
# )
|
||
#
|
||
# return response
|
||
#
|
||
# def get_success_url(self):
|
||
# """
|
||
# Redirect to department detail view.
|
||
# """
|
||
# return reverse('hr:department_detail', kwargs={'pk': self.object.pk})
|
||
#
|
||
#
|
||
# class DepartmentDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteView):
|
||
# """
|
||
# Delete view for departments.
|
||
# """
|
||
# model = Department
|
||
# template_name = 'hr/department_confirm_delete.html'
|
||
# permission_required = 'hr.delete_department'
|
||
#
|
||
# def get_queryset(self):
|
||
# """
|
||
# Filter department by tenant.
|
||
# """
|
||
# return Department.objects.filter(tenant=self.request.user.tenant)
|
||
#
|
||
# def delete(self, request, *args, **kwargs):
|
||
# """
|
||
# Override delete to show success message.
|
||
# """
|
||
# self.object = self.get_object()
|
||
# name = self.object.name
|
||
#
|
||
# # Check if department can be safely deleted
|
||
# if self.object.employees.exists():
|
||
# messages.error(
|
||
# request,
|
||
# f"Cannot delete {name} because it has employees assigned to it."
|
||
# )
|
||
# return redirect('hr:department_detail', pk=self.object.pk)
|
||
#
|
||
# if self.object.sub_departments.exists():
|
||
# messages.error(
|
||
# request,
|
||
# f"Cannot delete {name} because it has sub-departments assigned to it."
|
||
# )
|
||
# return redirect('hr:department_detail', pk=self.object.pk)
|
||
#
|
||
# success_url = self.get_success_url()
|
||
# self.object.delete()
|
||
#
|
||
# messages.success(request, f"Department {name} deleted successfully.")
|
||
#
|
||
# return redirect(success_url)
|
||
#
|
||
# def get_success_url(self):
|
||
# """
|
||
# Redirect to department list view.
|
||
# """
|
||
# return reverse_lazy('hr:department_list')
|
||
#
|
||
#
|
||
# class ScheduleListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
|
||
# """
|
||
# List view for schedules.
|
||
# """
|
||
# model = Schedule
|
||
# template_name = 'hr/schedule_list.html'
|
||
# context_object_name = 'schedules'
|
||
# paginate_by = 20
|
||
# permission_required = 'hr.view_schedule'
|
||
#
|
||
# def get_queryset(self):
|
||
# """
|
||
# Filter schedules by tenant and apply search filters.
|
||
# """
|
||
# queryset = Schedule.objects.filter(
|
||
# employee__tenant=self.request.user.tenant
|
||
# )
|
||
#
|
||
# # Apply search filters
|
||
# search = self.request.GET.get('search', '')
|
||
# if search:
|
||
# queryset = queryset.filter(
|
||
# Q(name__icontains=search) |
|
||
# Q(description__icontains=search) |
|
||
# Q(employee__first_name__icontains=search) |
|
||
# Q(employee__last_name__icontains=search) |
|
||
# Q(employee__employee_number__icontains=search)
|
||
# )
|
||
#
|
||
# employee_id = self.request.GET.get('employee', '')
|
||
# if employee_id:
|
||
# queryset = queryset.filter(employee_id=employee_id)
|
||
#
|
||
# schedule_type = self.request.GET.get('schedule_type', '')
|
||
# if schedule_type:
|
||
# queryset = queryset.filter(schedule_type=schedule_type)
|
||
#
|
||
# is_active = self.request.GET.get('is_active', '')
|
||
# if is_active == 'True':
|
||
# queryset = queryset.filter(is_active=True)
|
||
# elif is_active == 'False':
|
||
# queryset = queryset.filter(is_active=False)
|
||
#
|
||
# is_current = self.request.GET.get('is_current', '')
|
||
# if is_current == 'True':
|
||
# queryset = queryset.filter(is_current=True)
|
||
#
|
||
# return queryset.select_related('employee', 'approved_by')
|
||
#
|
||
# def get_context_data(self, **kwargs):
|
||
# """
|
||
# Add filter choices to context.
|
||
# """
|
||
# context = super().get_context_data(**kwargs)
|
||
# tenant = self.request.user.tenant
|
||
#
|
||
# context['search'] = self.request.GET.get('search', '')
|
||
# context['employees'] = Employee.objects.filter(
|
||
# tenant=tenant,
|
||
# is_active=True
|
||
# ).order_by('last_name', 'first_name')
|
||
# context['schedule_types'] = Schedule.SCHEDULE_TYPES
|
||
# context['selected_employee'] = self.request.GET.get('employee', '')
|
||
# context['selected_type'] = self.request.GET.get('schedule_type', '')
|
||
# context['selected_active'] = self.request.GET.get('is_active', '')
|
||
# context['selected_current'] = self.request.GET.get('is_current', '')
|
||
#
|
||
# return context
|
||
#
|
||
#
|
||
# class ScheduleDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
|
||
# """
|
||
# Detail view for schedules.
|
||
# """
|
||
# model = Schedule
|
||
# template_name = 'hr/schedule_detail.html'
|
||
# context_object_name = 'schedule'
|
||
# permission_required = 'hr.view_schedule'
|
||
#
|
||
# def get_queryset(self):
|
||
# """
|
||
# Filter schedule by tenant.
|
||
# """
|
||
# return Schedule.objects.filter(
|
||
# employee__tenant=self.request.user.tenant
|
||
# ).select_related(
|
||
# 'employee', 'approved_by', 'created_by'
|
||
# )
|
||
#
|
||
# def get_context_data(self, **kwargs):
|
||
# """
|
||
# Add related objects to context.
|
||
# """
|
||
# context = super().get_context_data(**kwargs)
|
||
# schedule = self.object
|
||
#
|
||
# # Assignments for this schedule
|
||
# context['assignments'] = ScheduleAssignment.objects.filter(
|
||
# schedule=schedule
|
||
# ).order_by('assignment_date', 'start_time')
|
||
#
|
||
# # Group assignments by week
|
||
# assignments_by_week = {}
|
||
# for assignment in context['assignments']:
|
||
# # Get week start date (Monday)
|
||
# week_start = assignment.assignment_date - timezone.timedelta(
|
||
# days=assignment.assignment_date.weekday()
|
||
# )
|
||
# week_key = week_start.strftime('%Y-%m-%d')
|
||
#
|
||
# if week_key not in assignments_by_week:
|
||
# assignments_by_week[week_key] = []
|
||
#
|
||
# assignments_by_week[week_key].append(assignment)
|
||
#
|
||
# context['assignments_by_week'] = assignments_by_week
|
||
#
|
||
# return context
|
||
#
|
||
#
|
||
# class ScheduleCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
|
||
# """
|
||
# Create view for schedules.
|
||
# """
|
||
# model = Schedule
|
||
# form_class = ScheduleForm
|
||
# template_name = 'hr/schedule_form.html'
|
||
# permission_required = 'hr.add_schedule'
|
||
#
|
||
# def get_form_kwargs(self):
|
||
# """
|
||
# Pass tenant and user to form.
|
||
# """
|
||
# kwargs = super().get_form_kwargs()
|
||
# kwargs['tenant'] = self.request.user.tenant
|
||
# kwargs['user'] = self.request.user
|
||
#
|
||
# # Pre-select employee if provided in GET parameters
|
||
# employee_id = self.request.GET.get('employee', None)
|
||
# if employee_id:
|
||
# kwargs['initial'] = kwargs.get('initial', {})
|
||
# kwargs['initial']['employee'] = employee_id
|
||
#
|
||
# return kwargs
|
||
#
|
||
# def form_valid(self, form):
|
||
# """
|
||
# Set tenant and user before saving.
|
||
# """
|
||
# form.instance.tenant = self.request.user.tenant
|
||
# form.instance.created_by = self.request.user
|
||
#
|
||
# # Set approval information
|
||
# form.instance.approved_by = self.request.user
|
||
# form.instance.approval_date = timezone.now()
|
||
#
|
||
# response = super().form_valid(form)
|
||
#
|
||
# messages.success(
|
||
# self.request,
|
||
# f"Schedule '{form.instance.name}' for {form.instance.employee.get_full_name()} created successfully."
|
||
# )
|
||
#
|
||
# return response
|
||
#
|
||
# def get_success_url(self):
|
||
# """
|
||
# Redirect to schedule detail view.
|
||
# """
|
||
# return reverse('hr:schedule_detail', kwargs={'pk': self.object.pk})
|
||
#
|
||
#
|
||
# class ScheduleUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
|
||
# """
|
||
# Update view for schedules.
|
||
# """
|
||
# model = Schedule
|
||
# form_class = ScheduleForm
|
||
# template_name = 'hr/schedule_form.html'
|
||
# permission_required = 'hr.change_schedule'
|
||
#
|
||
# def get_queryset(self):
|
||
# """
|
||
# Filter schedule by tenant.
|
||
# """
|
||
# return Schedule.objects.filter(
|
||
# employee__tenant=self.request.user.tenant
|
||
# )
|
||
#
|
||
# def get_form_kwargs(self):
|
||
# """
|
||
# Pass tenant and user to form.
|
||
# """
|
||
# kwargs = super().get_form_kwargs()
|
||
# kwargs['tenant'] = self.request.user.tenant
|
||
# kwargs['user'] = self.request.user
|
||
# return kwargs
|
||
#
|
||
# def form_valid(self, form):
|
||
# """
|
||
# Handle successful form validation.
|
||
# """
|
||
# # If is_current status changed, update approval information
|
||
# if 'is_current' in form.changed_data and form.instance.is_current:
|
||
# form.instance.approved_by = self.request.user
|
||
# form.instance.approval_date = timezone.now()
|
||
#
|
||
# response = super().form_valid(form)
|
||
#
|
||
# messages.success(
|
||
# self.request,
|
||
# f"Schedule '{form.instance.name}' updated successfully."
|
||
# )
|
||
#
|
||
# return response
|
||
#
|
||
# def get_success_url(self):
|
||
# """
|
||
# Redirect to schedule detail view.
|
||
# """
|
||
# return reverse('hr:schedule_detail', kwargs={'pk': self.object.pk})
|
||
#
|
||
#
|
||
# class ScheduleDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteView):
|
||
# """
|
||
# Delete view for schedules.
|
||
# """
|
||
# model = Schedule
|
||
# template_name = 'hr/schedule_confirm_delete.html'
|
||
# permission_required = 'hr.delete_schedule'
|
||
#
|
||
# def get_queryset(self):
|
||
# """
|
||
# Filter schedule by tenant.
|
||
# """
|
||
# return Schedule.objects.filter(
|
||
# employee__tenant=self.request.user.tenant
|
||
# )
|
||
#
|
||
# def delete(self, request, *args, **kwargs):
|
||
# """
|
||
# Override delete to show success message.
|
||
# """
|
||
# self.object = self.get_object()
|
||
# employee_name = self.object.employee.get_full_name()
|
||
# schedule_name = self.object.name
|
||
#
|
||
# # Check if schedule can be safely deleted
|
||
# if self.object.is_current:
|
||
# messages.error(
|
||
# request,
|
||
# f"Cannot delete schedule '{schedule_name}' because it is the current schedule for {employee_name}."
|
||
# )
|
||
# return redirect('hr:schedule_detail', pk=self.object.pk)
|
||
#
|
||
# success_url = self.get_success_url()
|
||
# self.object.delete()
|
||
#
|
||
# messages.success(
|
||
# request,
|
||
# f"Schedule '{schedule_name}' for {employee_name} deleted successfully."
|
||
# )
|
||
#
|
||
# return redirect(success_url)
|
||
#
|
||
# def get_success_url(self):
|
||
# """
|
||
# Redirect to schedule list view.
|
||
# """
|
||
# return reverse_lazy('hr:schedule_list')
|
||
#
|
||
#
|
||
# class TimeEntryListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
|
||
# """
|
||
# List view for time entries.
|
||
# """
|
||
# model = TimeEntry
|
||
# template_name = 'hr/time_entry_list.html'
|
||
# context_object_name = 'time_entries'
|
||
# paginate_by = 20
|
||
# permission_required = 'hr.view_timeentry'
|
||
#
|
||
# def get_queryset(self):
|
||
# """
|
||
# Filter time entries by tenant and apply search filters.
|
||
# """
|
||
# queryset = TimeEntry.objects.filter(tenant=self.request.user.tenant)
|
||
#
|
||
# # Apply search filters
|
||
# form = TimeEntrySearchForm(self.request.GET, tenant=self.request.user.tenant)
|
||
# if form.is_valid():
|
||
# data = form.cleaned_data
|
||
#
|
||
# # Search term
|
||
# if data.get('search'):
|
||
# search_term = data['search']
|
||
# queryset = queryset.filter(
|
||
# Q(employee__first_name__icontains=search_term) |
|
||
# Q(employee__last_name__icontains=search_term) |
|
||
# Q(employee__employee_number__icontains=search_term) |
|
||
# Q(description__icontains=search_term)
|
||
# )
|
||
#
|
||
# # Employee filter
|
||
# if data.get('employee'):
|
||
# queryset = queryset.filter(employee=data['employee'])
|
||
#
|
||
# # Department filter
|
||
# if data.get('department'):
|
||
# queryset = queryset.filter(department=data['department'])
|
||
#
|
||
# # Entry type filter
|
||
# if data.get('entry_type'):
|
||
# queryset = queryset.filter(entry_type=data['entry_type'])
|
||
#
|
||
# # Entry date range
|
||
# if data.get('entry_date_from'):
|
||
# queryset = queryset.filter(entry_date__gte=data['entry_date_from'])
|
||
#
|
||
# if data.get('entry_date_to'):
|
||
# queryset = queryset.filter(entry_date__lte=data['entry_date_to'])
|
||
#
|
||
# # Approval status
|
||
# if data.get('is_approved') == 'True':
|
||
# queryset = queryset.filter(is_approved=True)
|
||
# elif data.get('is_approved') == 'False':
|
||
# queryset = queryset.filter(is_approved=False)
|
||
#
|
||
# # Payment status
|
||
# if data.get('is_paid') == 'True':
|
||
# queryset = queryset.filter(is_paid=True)
|
||
# elif data.get('is_paid') == 'False':
|
||
# queryset = queryset.filter(is_paid=False)
|
||
#
|
||
# return queryset.select_related(
|
||
# 'employee', 'department', 'approved_by'
|
||
# ).order_by('-entry_date', '-updated_at')
|
||
#
|
||
# def get_context_data(self, **kwargs):
|
||
# """
|
||
# Add search form to context.
|
||
# """
|
||
# context = super().get_context_data(**kwargs)
|
||
# context['search_form'] = TimeEntrySearchForm(
|
||
# self.request.GET or None,
|
||
# tenant=self.request.user.tenant
|
||
# )
|
||
#
|
||
# # Calculate totals
|
||
# entries = self.object_list
|
||
# context['total_hours'] = sum(entry.total_hours for entry in entries)
|
||
# context['total_approved'] = sum(1 for entry in entries if entry.is_approved)
|
||
# context['total_pending'] = sum(1 for entry in entries if not entry.is_approved)
|
||
#
|
||
# return context
|
||
#
|
||
#
|
||
# class TimeEntryDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
|
||
# """
|
||
# Detail view for time entries.
|
||
# """
|
||
# model = TimeEntry
|
||
# template_name = 'hr/time_entry_detail.html'
|
||
# context_object_name = 'time_entry'
|
||
# permission_required = 'hr.view_timeentry'
|
||
#
|
||
# def get_queryset(self):
|
||
# """
|
||
# Filter time entry by tenant.
|
||
# """
|
||
# return TimeEntry.objects.filter(
|
||
# tenant=self.request.user.tenant
|
||
# ).select_related(
|
||
# 'employee', 'department', 'approved_by', 'created_by'
|
||
# )
|
||
#
|
||
#
|
||
# class TimeEntryCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
|
||
# """
|
||
# Create view for time entries.
|
||
# """
|
||
# model = TimeEntry
|
||
# form_class = TimeEntryForm
|
||
# template_name = 'hr/time_entry_form.html'
|
||
# permission_required = 'hr.add_timeentry'
|
||
#
|
||
# def get_form_kwargs(self):
|
||
# """
|
||
# Pass tenant and user to form.
|
||
# """
|
||
# kwargs = super().get_form_kwargs()
|
||
# kwargs['tenant'] = self.request.user.tenant
|
||
# kwargs['user'] = self.request.user
|
||
#
|
||
# # Pre-select employee and date if provided in GET parameters
|
||
# initial = {}
|
||
# employee_id = self.request.GET.get('employee', None)
|
||
# if employee_id:
|
||
# initial['employee'] = employee_id
|
||
#
|
||
# entry_date = self.request.GET.get('entry_date', None)
|
||
# if entry_date:
|
||
# try:
|
||
# initial['entry_date'] = timezone.datetime.strptime(
|
||
# entry_date, '%Y-%m-%d'
|
||
# ).date()
|
||
# except (ValueError, TypeError):
|
||
# pass
|
||
#
|
||
# if initial:
|
||
# kwargs['initial'] = initial
|
||
#
|
||
# return kwargs
|
||
#
|
||
# def form_valid(self, form):
|
||
# """
|
||
# Set tenant and user before saving.
|
||
# """
|
||
# form.instance.tenant = self.request.user.tenant
|
||
# form.instance.created_by = self.request.user
|
||
#
|
||
# response = super().form_valid(form)
|
||
#
|
||
# messages.success(
|
||
# self.request,
|
||
# f"Time entry for {form.instance.employee.get_full_name()} on {form.instance.entry_date} created successfully."
|
||
# )
|
||
#
|
||
# return response
|
||
#
|
||
# def get_success_url(self):
|
||
# """
|
||
# Redirect to time entry list view or employee detail based on origin.
|
||
# """
|
||
# if 'employee' in self.request.GET:
|
||
# return reverse('hr:employee_detail', kwargs={'pk': self.request.GET['employee']})
|
||
# return reverse('hr:time_entry_list')
|
||
#
|
||
#
|
||
# class TimeEntryUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
|
||
# """
|
||
# Update view for time entries.
|
||
# """
|
||
# model = TimeEntry
|
||
# form_class = TimeEntryForm
|
||
# template_name = 'hr/time_entry_form.html'
|
||
# permission_required = 'hr.change_timeentry'
|
||
#
|
||
# def get_queryset(self):
|
||
# """
|
||
# Filter time entry by tenant.
|
||
# """
|
||
# return TimeEntry.objects.filter(tenant=self.request.user.tenant)
|
||
#
|
||
# def get_form_kwargs(self):
|
||
# """
|
||
# Pass tenant and user to form.
|
||
# """
|
||
# kwargs = super().get_form_kwargs()
|
||
# kwargs['tenant'] = self.request.user.tenant
|
||
# kwargs['user'] = self.request.user
|
||
# return kwargs
|
||
#
|
||
# def form_valid(self, form):
|
||
# """
|
||
# Handle successful form validation.
|
||
# """
|
||
# response = super().form_valid(form)
|
||
#
|
||
# messages.success(
|
||
# self.request,
|
||
# f"Time entry for {form.instance.employee.get_full_name()} on {form.instance.entry_date} updated successfully."
|
||
# )
|
||
#
|
||
# return response
|
||
#
|
||
# def get_success_url(self):
|
||
# """
|
||
# Redirect to time entry detail view.
|
||
# """
|
||
# return reverse('hr:time_entry_detail', kwargs={'pk': self.object.pk})
|
||
#
|
||
#
|
||
# @login_required
|
||
# def approve_time_entry(request, pk):
|
||
# """
|
||
# Approve a time entry.
|
||
# """
|
||
# time_entry = get_object_or_404(
|
||
# TimeEntry,
|
||
# pk=pk,
|
||
# tenant=request.user.tenant
|
||
# )
|
||
#
|
||
# if request.method == 'POST':
|
||
# if not request.user.has_perm('hr.approve_timeentry'):
|
||
# messages.error(request, "You don't have permission to approve time entries.")
|
||
# return redirect('hr:time_entry_detail', pk=time_entry.pk)
|
||
#
|
||
# time_entry.is_approved = True
|
||
# time_entry.approved_by = request.user
|
||
# time_entry.approval_date = timezone.now()
|
||
# time_entry.save()
|
||
#
|
||
# messages.success(
|
||
# request,
|
||
# f"Time entry for {time_entry.employee.get_full_name()} on {time_entry.entry_date} approved."
|
||
# )
|
||
#
|
||
# redirect_url = request.POST.get('next', reverse('hr:time_entry_detail', kwargs={'pk': time_entry.pk}))
|
||
# return redirect(redirect_url)
|
||
#
|
||
# return redirect('hr:time_entry_detail', pk=time_entry.pk)
|
||
#
|
||
#
|
||
@login_required
|
||
def mark_time_entry_paid(request, pk):
|
||
"""
|
||
Mark a time entry as paid.
|
||
"""
|
||
time_entry = get_object_or_404(
|
||
TimeEntry,
|
||
pk=pk,
|
||
tenant=request.user.tenant
|
||
)
|
||
|
||
if request.method == 'POST':
|
||
if not request.user.has_perm('hr.change_timeentry'):
|
||
messages.error(request, "You don't have permission to mark time entries as paid.")
|
||
return redirect('hr:time_entry_detail', pk=time_entry.pk)
|
||
|
||
time_entry.is_paid = True
|
||
time_entry.payment_date = timezone.now().date()
|
||
time_entry.save()
|
||
|
||
messages.success(
|
||
request,
|
||
f"Time entry for {time_entry.employee.get_full_name()} on {time_entry.entry_date} marked as paid."
|
||
)
|
||
|
||
redirect_url = request.POST.get('next', reverse('hr:time_entry_detail', kwargs={'pk': time_entry.pk}))
|
||
return redirect(redirect_url)
|
||
|
||
return redirect('hr:time_entry_detail', pk=time_entry.pk)
|
||
|
||
|
||
@login_required
|
||
def bulk_approve_time_entries(request):
|
||
"""
|
||
Bulk approve time entries.
|
||
"""
|
||
if not request.user.has_perm('hr.approve_timeentry'):
|
||
messages.error(request, "You don't have permission to approve time entries.")
|
||
return redirect('hr:time_entry_list')
|
||
|
||
if request.method == 'POST':
|
||
entry_ids = request.POST.getlist('entry_ids')
|
||
|
||
if not entry_ids:
|
||
messages.warning(request, "No time entries selected for approval.")
|
||
return redirect('hr:time_entry_list')
|
||
|
||
# Get entries that belong to this tenant
|
||
entries = TimeEntry.objects.filter(
|
||
tenant=request.user.tenant,
|
||
pk__in=entry_ids,
|
||
is_approved=False
|
||
)
|
||
|
||
# Update all entries
|
||
updated_count = entries.update(
|
||
is_approved=True,
|
||
approved_by=request.user,
|
||
approval_date=timezone.now()
|
||
)
|
||
|
||
messages.success(request, f"{updated_count} time entries approved successfully.")
|
||
|
||
redirect_url = request.POST.get('next', reverse('hr:time_entry_list'))
|
||
return redirect(redirect_url)
|
||
|
||
return redirect('hr:time_entry_list')
|
||
|
||
|
||
@login_required
|
||
def bulk_mark_time_entries_paid(request):
|
||
"""
|
||
Bulk mark time entries as paid.
|
||
"""
|
||
if not request.user.has_perm('hr.change_timeentry'):
|
||
messages.error(request, "You don't have permission to mark time entries as paid.")
|
||
return redirect('hr:time_entry_list')
|
||
|
||
if request.method == 'POST':
|
||
entry_ids = request.POST.getlist('entry_ids')
|
||
|
||
if not entry_ids:
|
||
messages.warning(request, "No time entries selected for payment.")
|
||
return redirect('hr:time_entry_list')
|
||
|
||
# Get entries that belong to this tenant
|
||
entries = TimeEntry.objects.filter(
|
||
tenant=request.user.tenant,
|
||
pk__in=entry_ids,
|
||
is_approved=True,
|
||
is_paid=False
|
||
)
|
||
|
||
# Update all entries
|
||
updated_count = entries.update(
|
||
is_paid=True,
|
||
payment_date=timezone.now().date()
|
||
)
|
||
|
||
messages.success(request, f"{updated_count} time entries marked as paid successfully.")
|
||
|
||
redirect_url = request.POST.get('next', reverse('hr:time_entry_list'))
|
||
return redirect(redirect_url)
|
||
|
||
return redirect('hr:time_entry_list')
|
||
#
|
||
#
|
||
# class PerformanceReviewListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
|
||
# """
|
||
# List view for performance reviews.
|
||
# """
|
||
# model = PerformanceReview
|
||
# template_name = 'hr/performance_review_list.html'
|
||
# context_object_name = 'reviews'
|
||
# paginate_by = 20
|
||
# permission_required = 'hr.view_performancereview'
|
||
#
|
||
# def get_queryset(self):
|
||
# """
|
||
# Filter reviews by tenant and apply search filters.
|
||
# """
|
||
# queryset = PerformanceReview.objects.filter(tenant=self.request.user.tenant)
|
||
#
|
||
# # Apply search filters
|
||
# search = self.request.GET.get('search', '')
|
||
# if search:
|
||
# queryset = queryset.filter(
|
||
# Q(employee__first_name__icontains=search) |
|
||
# Q(employee__last_name__icontains=search) |
|
||
# Q(employee__employee_number__icontains=search) |
|
||
# Q(strengths__icontains=search) |
|
||
# Q(areas_for_improvement__icontains=search)
|
||
# )
|
||
#
|
||
# employee_id = self.request.GET.get('employee', '')
|
||
# if employee_id:
|
||
# queryset = queryset.filter(employee_id=employee_id)
|
||
#
|
||
# review_type = self.request.GET.get('review_type', '')
|
||
# if review_type:
|
||
# queryset = queryset.filter(review_type=review_type)
|
||
#
|
||
# status = self.request.GET.get('status', '')
|
||
# if status:
|
||
# queryset = queryset.filter(status=status)
|
||
#
|
||
# # Date range filters
|
||
# scheduled_from = self.request.GET.get('scheduled_from', '')
|
||
# if scheduled_from:
|
||
# try:
|
||
# scheduled_from = timezone.datetime.strptime(scheduled_from, '%Y-%m-%d').date()
|
||
# queryset = queryset.filter(scheduled_date__gte=scheduled_from)
|
||
# except (ValueError, TypeError):
|
||
# pass
|
||
#
|
||
# scheduled_to = self.request.GET.get('scheduled_to', '')
|
||
# if scheduled_to:
|
||
# try:
|
||
# scheduled_to = timezone.datetime.strptime(scheduled_to, '%Y-%m-%d').date()
|
||
# queryset = queryset.filter(scheduled_date__lte=scheduled_to)
|
||
# except (ValueError, TypeError):
|
||
# pass
|
||
#
|
||
# # Rating filter
|
||
# rating = self.request.GET.get('rating', '')
|
||
# if rating and rating.isdigit():
|
||
# queryset = queryset.filter(performance_rating=int(rating))
|
||
#
|
||
# return queryset.select_related('employee', 'reviewer')
|
||
#
|
||
# def get_context_data(self, **kwargs):
|
||
# """
|
||
# Add filter choices to context.
|
||
# """
|
||
# context = super().get_context_data(**kwargs)
|
||
# tenant = self.request.user.tenant
|
||
#
|
||
# context['search'] = self.request.GET.get('search', '')
|
||
# context['employees'] = Employee.objects.filter(
|
||
# tenant=tenant
|
||
# ).order_by('last_name', 'first_name')
|
||
# context['review_types'] = PerformanceReview.REVIEW_TYPES
|
||
# context['review_statuses'] = PerformanceReview.REVIEW_STATUS
|
||
# context['selected_employee'] = self.request.GET.get('employee', '')
|
||
# context['selected_type'] = self.request.GET.get('review_type', '')
|
||
# context['selected_status'] = self.request.GET.get('status', '')
|
||
# context['scheduled_from'] = self.request.GET.get('scheduled_from', '')
|
||
# context['scheduled_to'] = self.request.GET.get('scheduled_to', '')
|
||
# context['selected_rating'] = self.request.GET.get('rating', '')
|
||
#
|
||
# return context
|
||
#
|
||
#
|
||
# class PerformanceReviewDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
|
||
# """
|
||
# Detail view for performance reviews.
|
||
# """
|
||
# model = PerformanceReview
|
||
# template_name = 'hr/performance_review_detail.html'
|
||
# context_object_name = 'review'
|
||
# permission_required = 'hr.view_performancereview'
|
||
#
|
||
# def get_queryset(self):
|
||
# """
|
||
# Filter review by tenant.
|
||
# """
|
||
# return PerformanceReview.objects.filter(
|
||
# tenant=self.request.user.tenant
|
||
# ).select_related(
|
||
# 'employee', 'reviewer', 'created_by'
|
||
# )
|
||
#
|
||
#
|
||
# class PerformanceReviewCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
|
||
# """
|
||
# Create view for performance reviews.
|
||
# """
|
||
# model = PerformanceReview
|
||
# form_class = PerformanceReviewForm
|
||
# template_name = 'hr/performance_review_form.html'
|
||
# permission_required = 'hr.add_performancereview'
|
||
#
|
||
# def get_form_kwargs(self):
|
||
# """
|
||
# Pass tenant and user to form.
|
||
# """
|
||
# kwargs = super().get_form_kwargs()
|
||
# kwargs['tenant'] = self.request.user.tenant
|
||
# kwargs['user'] = self.request.user
|
||
#
|
||
# # Pre-select employee if provided in GET parameters
|
||
# employee_id = self.request.GET.get('employee', None)
|
||
# if employee_id:
|
||
# kwargs['initial'] = kwargs.get('initial', {})
|
||
# kwargs['initial']['employee'] = employee_id
|
||
# kwargs['initial']['reviewer'] = self.request.user.id
|
||
#
|
||
# return kwargs
|
||
#
|
||
# def form_valid(self, form):
|
||
# """
|
||
# Set tenant and user before saving.
|
||
# """
|
||
# form.instance.tenant = self.request.user.tenant
|
||
# form.instance.created_by = self.request.user
|
||
#
|
||
# response = super().form_valid(form)
|
||
#
|
||
# messages.success(
|
||
# self.request,
|
||
# f"Performance review for {form.instance.employee.get_full_name()} created successfully."
|
||
# )
|
||
#
|
||
# return response
|
||
#
|
||
# def get_success_url(self):
|
||
# """
|
||
# Redirect to review detail view.
|
||
# """
|
||
# return reverse('hr:performance_review_detail', kwargs={'pk': self.object.pk})
|
||
#
|
||
#
|
||
# class PerformanceReviewUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
|
||
# """
|
||
# Update view for performance reviews.
|
||
# """
|
||
# model = PerformanceReview
|
||
# form_class = PerformanceReviewForm
|
||
# template_name = 'hr/performance_review_form.html'
|
||
# permission_required = 'hr.change_performancereview'
|
||
#
|
||
# def get_queryset(self):
|
||
# """
|
||
# Filter review by tenant.
|
||
# """
|
||
# return PerformanceReview.objects.filter(tenant=self.request.user.tenant)
|
||
#
|
||
# def get_form_kwargs(self):
|
||
# """
|
||
# Pass tenant and user to form.
|
||
# """
|
||
# kwargs = super().get_form_kwargs()
|
||
# kwargs['tenant'] = self.request.user.tenant
|
||
# kwargs['user'] = self.request.user
|
||
# return kwargs
|
||
#
|
||
# def form_valid(self, form):
|
||
# """
|
||
# Handle successful form validation.
|
||
# """
|
||
# response = super().form_valid(form)
|
||
#
|
||
# messages.success(
|
||
# self.request,
|
||
# f"Performance review for {form.instance.employee.get_full_name()} updated successfully."
|
||
# )
|
||
#
|
||
# return response
|
||
#
|
||
# def get_success_url(self):
|
||
# """
|
||
# Redirect to review detail view.
|
||
# """
|
||
# return reverse('hr:performance_review_detail', kwargs={'pk': self.object.pk})
|
||
#
|
||
#
|
||
@login_required
|
||
def acknowledge_review(request, pk):
|
||
"""
|
||
Acknowledge a performance review.
|
||
"""
|
||
review = get_object_or_404(
|
||
PerformanceReview,
|
||
pk=pk,
|
||
tenant=request.user.tenant
|
||
)
|
||
|
||
# Check if the user is the employee being reviewed
|
||
if hasattr(request.user, 'employee_profile') and request.user.employee_profile == review.employee:
|
||
if request.method == 'POST':
|
||
review.is_acknowledged = True
|
||
review.acknowledgment_date = timezone.now()
|
||
review.save()
|
||
|
||
messages.success(request, "You have acknowledged this performance review.")
|
||
|
||
redirect_url = request.POST.get('next', reverse('hr:performance_review_detail', kwargs={'pk': review.pk}))
|
||
return redirect(redirect_url)
|
||
else:
|
||
messages.error(request, "Only the employee being reviewed can acknowledge this review.")
|
||
|
||
return redirect('hr:performance_review_detail', pk=review.pk)
|
||
|
||
|
||
@login_required
|
||
def complete_review(request, pk):
|
||
"""
|
||
Mark a performance review as completed.
|
||
"""
|
||
review = get_object_or_404(
|
||
PerformanceReview,
|
||
pk=pk,
|
||
tenant=request.user.tenant
|
||
)
|
||
|
||
if request.method == 'POST':
|
||
if not request.user.has_perm('hr.change_performancereview'):
|
||
messages.error(request, "You don't have permission to complete reviews.")
|
||
return redirect('hr:performance_review_detail', pk=review.pk)
|
||
|
||
# Check if reviewer is the one completing the review
|
||
if review.reviewer != request.user:
|
||
messages.warning(request, "Only the assigned reviewer should complete this review.")
|
||
|
||
# Update review status
|
||
review.status = 'COMPLETED'
|
||
review.completion_date = timezone.now().date()
|
||
review.save()
|
||
|
||
# Update employee's last and next review dates
|
||
employee = review.employee
|
||
employee.last_review_date = review.completion_date
|
||
employee.next_review_date = review.next_review_date
|
||
employee.save()
|
||
|
||
messages.success(
|
||
request,
|
||
f"Performance review for {review.employee.get_full_name()} marked as completed."
|
||
)
|
||
|
||
redirect_url = request.POST.get('next', reverse('hr:performance_review_detail', kwargs={'pk': review.pk}))
|
||
return redirect(redirect_url)
|
||
|
||
return redirect('hr:performance_review_detail', pk=review.pk)
|
||
|
||
|
||
# class TrainingRecordListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
|
||
# """
|
||
# List view for training records.
|
||
# """
|
||
# model = TrainingRecord
|
||
# template_name = 'hr/training_record_list.html'
|
||
# context_object_name = 'training_records'
|
||
# paginate_by = 20
|
||
# permission_required = 'hr.view_trainingrecord'
|
||
#
|
||
# def get_queryset(self):
|
||
# """
|
||
# Filter training records by tenant and apply search filters.
|
||
# """
|
||
# queryset = TrainingRecord.objects.filter(tenant=self.request.user.tenant)
|
||
#
|
||
# # Apply search filters
|
||
# search = self.request.GET.get('search', '')
|
||
# if search:
|
||
# queryset = queryset.filter(
|
||
# Q(employee__first_name__icontains=search) |
|
||
# Q(employee__last_name__icontains=search) |
|
||
# Q(employee__employee_number__icontains=search) |
|
||
# Q(training_title__icontains=search) |
|
||
# Q(training_description__icontains=search) |
|
||
# Q(provider__icontains=search) |
|
||
# Q(trainer__icontains=search)
|
||
# )
|
||
#
|
||
# employee_id = self.request.GET.get('employee', '')
|
||
# if employee_id:
|
||
# queryset = queryset.filter(employee_id=employee_id)
|
||
#
|
||
# training_type = self.request.GET.get('training_type', '')
|
||
# if training_type:
|
||
# queryset = queryset.filter(training_type=training_type)
|
||
#
|
||
# status = self.request.GET.get('status', '')
|
||
# if status:
|
||
# queryset = queryset.filter(status=status)
|
||
#
|
||
# is_mandatory = self.request.GET.get('is_mandatory', '')
|
||
# if is_mandatory == 'True':
|
||
# queryset = queryset.filter(is_mandatory=True)
|
||
# elif is_mandatory == 'False':
|
||
# queryset = queryset.filter(is_mandatory=False)
|
||
#
|
||
# # Date range filters
|
||
# start_from = self.request.GET.get('start_from', '')
|
||
# if start_from:
|
||
# try:
|
||
# start_from = timezone.datetime.strptime(start_from, '%Y-%m-%d').date()
|
||
# queryset = queryset.filter(start_date__gte=start_from)
|
||
# except (ValueError, TypeError):
|
||
# pass
|
||
#
|
||
# start_to = self.request.GET.get('start_to', '')
|
||
# if start_to:
|
||
# try:
|
||
# start_to = timezone.datetime.strptime(start_to, '%Y-%m-%d').date()
|
||
# queryset = queryset.filter(start_date__lte=start_to)
|
||
# except (ValueError, TypeError):
|
||
# pass
|
||
#
|
||
# # Expiry status filter
|
||
# expiry_status = self.request.GET.get('expiry_status', '')
|
||
# today = timezone.now().date()
|
||
# if expiry_status == 'valid':
|
||
# queryset = queryset.filter(
|
||
# status='COMPLETED',
|
||
# expiry_date__gt=today
|
||
# )
|
||
# elif expiry_status == 'expiring':
|
||
# queryset = queryset.filter(
|
||
# status='COMPLETED',
|
||
# expiry_date__gt=today,
|
||
# expiry_date__lte=today + timezone.timedelta(days=30)
|
||
# )
|
||
# elif expiry_status == 'expired':
|
||
# queryset = queryset.filter(
|
||
# Q(status='EXPIRED') |
|
||
# Q(status='COMPLETED', expiry_date__lt=today)
|
||
# )
|
||
#
|
||
# return queryset.select_related('employee')
|
||
#
|
||
# def get_context_data(self, **kwargs):
|
||
# """
|
||
# Add filter choices to context.
|
||
# """
|
||
# context = super().get_context_data(**kwargs)
|
||
# tenant = self.request.user.tenant
|
||
#
|
||
# context['search'] = self.request.GET.get('search', '')
|
||
# context['employees'] = Employee.objects.filter(
|
||
# tenant=tenant
|
||
# ).order_by('last_name', 'first_name')
|
||
# context['training_statuses'] = TrainingRecord.TRAINING_STATUS
|
||
# context['selected_employee'] = self.request.GET.get('employee', '')
|
||
# context['selected_type'] = self.request.GET.get('training_type', '')
|
||
# context['selected_status'] = self.request.GET.get('status', '')
|
||
# context['selected_mandatory'] = self.request.GET.get('is_mandatory', '')
|
||
# context['start_from'] = self.request.GET.get('start_from', '')
|
||
# context['start_to'] = self.request.GET.get('start_to', '')
|
||
# context['selected_expiry'] = self.request.GET.get('expiry_status', '')
|
||
#
|
||
# # Collect unique training types from existing records
|
||
# context['training_types'] = TrainingRecord.objects.filter(
|
||
# tenant=tenant
|
||
# ).values_list('training_type', flat=True).distinct().order_by('training_type')
|
||
#
|
||
# return context
|
||
#
|
||
#
|
||
# class TrainingRecordDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
|
||
# """
|
||
# Detail view for training records.
|
||
# """
|
||
# model = TrainingRecord
|
||
# template_name = 'hr/training_record_detail.html'
|
||
# context_object_name = 'training_record'
|
||
# permission_required = 'hr.view_trainingrecord'
|
||
#
|
||
# def get_queryset(self):
|
||
# """
|
||
# Filter training record by tenant.
|
||
# """
|
||
# return TrainingRecord.objects.filter(
|
||
# tenant=self.request.user.tenant
|
||
# ).select_related(
|
||
# 'employee', 'created_by'
|
||
# )
|
||
#
|
||
#
|
||
# class TrainingRecordCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
|
||
# """
|
||
# Create view for training records.
|
||
# """
|
||
# model = TrainingRecord
|
||
# form_class = TrainingRecordForm
|
||
# template_name = 'hr/training_record_form.html'
|
||
# permission_required = 'hr.add_trainingrecord'
|
||
#
|
||
# def get_form_kwargs(self):
|
||
# """
|
||
# Pass tenant and user to form.
|
||
# """
|
||
# kwargs = super().get_form_kwargs()
|
||
# kwargs['tenant'] = self.request.user.tenant
|
||
# kwargs['user'] = self.request.user
|
||
#
|
||
# # Pre-select employee if provided in GET parameters
|
||
# employee_id = self.request.GET.get('employee', None)
|
||
# if employee_id:
|
||
# kwargs['initial'] = kwargs.get('initial', {})
|
||
# kwargs['initial']['employee'] = employee_id
|
||
#
|
||
# return kwargs
|
||
#
|
||
# def form_valid(self, form):
|
||
# """
|
||
# Set tenant and user before saving.
|
||
# """
|
||
# form.instance.tenant = self.request.user.tenant
|
||
# form.instance.created_by = self.request.user
|
||
#
|
||
# response = super().form_valid(form)
|
||
#
|
||
# messages.success(
|
||
# self.request,
|
||
# f"Training record '{form.instance.training_title}' for {form.instance.employee.get_full_name()} created successfully."
|
||
# )
|
||
#
|
||
# return response
|
||
#
|
||
# def get_success_url(self):
|
||
# """
|
||
# Redirect to training record detail view.
|
||
# """
|
||
# return reverse('hr:training_record_detail', kwargs={'pk': self.object.pk})
|
||
#
|
||
#
|
||
# class TrainingRecordUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
|
||
# """
|
||
# Update view for training records.
|
||
# """
|
||
# model = TrainingRecord
|
||
# form_class = TrainingRecordForm
|
||
# template_name = 'hr/training_record_form.html'
|
||
# permission_required = 'hr.change_trainingrecord'
|
||
#
|
||
# def get_queryset(self):
|
||
# """
|
||
# Filter training record by tenant.
|
||
# """
|
||
# return TrainingRecord.objects.filter(tenant=self.request.user.tenant)
|
||
#
|
||
# def get_form_kwargs(self):
|
||
# """
|
||
# Pass tenant and user to form.
|
||
# """
|
||
# kwargs = super().get_form_kwargs()
|
||
# kwargs['tenant'] = self.request.user.tenant
|
||
# kwargs['user'] = self.request.user
|
||
# return kwargs
|
||
#
|
||
# def form_valid(self, form):
|
||
# """
|
||
# Handle successful form validation.
|
||
# """
|
||
# response = super().form_valid(form)
|
||
#
|
||
# messages.success(
|
||
# self.request,
|
||
# f"Training record '{form.instance.training_title}' updated successfully."
|
||
# )
|
||
#
|
||
# return response
|
||
#
|
||
# def get_success_url(self):
|
||
# """
|
||
# Redirect to training record detail view.
|
||
# """
|
||
# return reverse('hr:training_record_detail', kwargs={'pk': self.object.pk})
|
||
#
|
||
#
|
||
# @login_required
|
||
# def hr_stats(request):
|
||
# """
|
||
# Return HR statistics for dashboard updates.
|
||
# """
|
||
# context = {
|
||
# 'total_employees': Employee.objects.filter(tenant=request.user.tenant).count(),
|
||
# 'active_employees': Employee.objects.filter(
|
||
# tenant=request.user.tenant,
|
||
# employment_status='ACTIVE'
|
||
# ).count(),
|
||
# 'total_departments': Department.objects.filter(tenant=request.user.tenant).count(),
|
||
# 'pending_reviews': PerformanceReview.objects.filter(
|
||
# tenant=request.user.tenant,
|
||
# status='PENDING'
|
||
# ).count(),
|
||
# 'employees_clocked_in': TimeEntry.objects.filter(
|
||
# tenant=request.user.tenant,
|
||
# clock_in_time__date=timezone.now().date(),
|
||
# clock_out_time__isnull=True
|
||
# ).count(),
|
||
# }
|
||
#
|
||
# return render(request, 'hr/partials/hr_stats.html', context)
|
||
#
|
||
#
|
||
# @login_required
|
||
# def clock_in(request):
|
||
# """
|
||
# Clock in the current user.
|
||
# """
|
||
# # Check if user has an employee profile
|
||
# try:
|
||
# employee = request.user.employee_profile
|
||
# except (AttributeError, Employee.DoesNotExist):
|
||
# messages.error(request, "You don't have an employee profile. Please contact HR.")
|
||
# return redirect('hr:dashboard')
|
||
#
|
||
# # Check if already clocked in
|
||
# existing_entry = TimeEntry.objects.filter(
|
||
# tenant=request.user.tenant,
|
||
# employee=employee,
|
||
# entry_date=timezone.now().date(),
|
||
# clock_out_time__isnull=True
|
||
# ).first()
|
||
#
|
||
# if existing_entry:
|
||
# messages.warning(request, "You are already clocked in. Please clock out first.")
|
||
# return redirect('hr:dashboard')
|
||
#
|
||
# # Create new time entry
|
||
# time_entry = TimeEntry(
|
||
# tenant=request.user.tenant,
|
||
# employee=employee,
|
||
# entry_date=timezone.now().date(),
|
||
# entry_type='REGULAR',
|
||
# clock_in_time=timezone.now(),
|
||
# department=employee.department,
|
||
# created_by=request.user
|
||
# )
|
||
# time_entry.save()
|
||
#
|
||
# messages.success(request, f"Clocked in at {time_entry.clock_in_time.strftime('%H:%M:%S')}.")
|
||
# return redirect('hr:dashboard')
|
||
#
|
||
#
|
||
# @login_required
|
||
# def clock_out(request):
|
||
# """
|
||
# Clock out the current user.
|
||
# """
|
||
# # Check if user has an employee profile
|
||
# try:
|
||
# employee = request.user.employee_profile
|
||
# except (AttributeError, Employee.DoesNotExist):
|
||
# messages.error(request, "You don't have an employee profile. Please contact HR.")
|
||
# return redirect('hr:dashboard')
|
||
#
|
||
# # Find active time entry
|
||
# time_entry = TimeEntry.objects.filter(
|
||
# tenant=request.user.tenant,
|
||
# employee=employee,
|
||
# entry_date=timezone.now().date(),
|
||
# clock_out_time__isnull=True
|
||
# ).order_by('-clock_in_time').first()
|
||
#
|
||
# if not time_entry:
|
||
# messages.warning(request, "You are not clocked in. Please clock in first.")
|
||
# return redirect('hr:dashboard')
|
||
#
|
||
# # Update time entry
|
||
# time_entry.clock_out_time = timezone.now()
|
||
# time_entry.save() # This will calculate total_hours in the save method
|
||
#
|
||
# hours = time_entry.total_hours
|
||
# messages.success(
|
||
# request,
|
||
# f"Clocked out at {time_entry.clock_out_time.strftime('%H:%M:%S')}. "
|
||
# f"Total hours: {hours:.2f}"
|
||
# )
|
||
# return redirect('hr:dashboard')
|
||
#
|
||
#
|
||
@login_required
|
||
def employee_schedule(request, employee_id):
|
||
"""
|
||
View an employee's schedule.
|
||
"""
|
||
employee = get_object_or_404(
|
||
Employee,
|
||
pk=employee_id,
|
||
tenant=request.user.tenant
|
||
)
|
||
|
||
# Get current schedule
|
||
current_schedule = Schedule.objects.filter(
|
||
employee=employee,
|
||
is_current=True,
|
||
is_active=True
|
||
).first()
|
||
|
||
# Get upcoming assignments
|
||
today = timezone.now().date()
|
||
upcoming_assignments = ScheduleAssignment.objects.filter(
|
||
schedule__employee=employee,
|
||
assignment_date__gte=today
|
||
).order_by('assignment_date', 'start_time')[:14] # Next 2 weeks
|
||
|
||
context = {
|
||
'employee': employee,
|
||
'current_schedule': current_schedule,
|
||
'upcoming_assignments': upcoming_assignments,
|
||
}
|
||
|
||
return render(request, 'hr/employee_schedule.html', context)
|
||
|