3008 lines
99 KiB
Python
3008 lines
99 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.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 .models import (
|
|
Employee, Department, Schedule, ScheduleAssignment,
|
|
TimeEntry, PerformanceReview, TrainingRecord
|
|
)
|
|
from .forms import (
|
|
EmployeeForm, DepartmentForm, ScheduleForm, ScheduleAssignmentForm,
|
|
TimeEntryForm, PerformanceReviewForm, TrainingRecordForm
|
|
)
|
|
|
|
|
|
# ============================================================================
|
|
# DASHBOARD AND OVERVIEW VIEWS
|
|
# ============================================================================
|
|
|
|
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(),
|
|
'total_departments': Department.objects.filter(tenant=self.request.user.tenant).count(),
|
|
'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
|
|
|
|
|
|
# ============================================================================
|
|
# EMPLOYEE VIEWS (FULL CRUD - Master Data)
|
|
# ============================================================================
|
|
|
|
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_number__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/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)
|
|
|
|
|
|
# ============================================================================
|
|
# DEPARTMENT VIEWS (FULL CRUD - Master Data)
|
|
# ============================================================================
|
|
|
|
class DepartmentListView(LoginRequiredMixin, ListView):
|
|
"""
|
|
List all departments with employee counts.
|
|
"""
|
|
model = Department
|
|
template_name = 'hr/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.annotate(
|
|
employee_count=Count('employees')
|
|
).order_by('name')
|
|
|
|
|
|
class DepartmentDetailView(LoginRequiredMixin, DetailView):
|
|
"""
|
|
Display detailed information about a specific department.
|
|
"""
|
|
model = Department
|
|
template_name = 'hr/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/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/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/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)
|
|
|
|
|
|
# ============================================================================
|
|
# SCHEDULE VIEWS (LIMITED CRUD - Operational Data)
|
|
# ============================================================================
|
|
|
|
class ScheduleListView(LoginRequiredMixin, ListView):
|
|
"""
|
|
List all schedules with filtering capabilities.
|
|
"""
|
|
model = Schedule
|
|
template_name = 'hr/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/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/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/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
|
|
|
|
|
|
# ============================================================================
|
|
# SCHEDULE ASSIGNMENT VIEWS (LIMITED CRUD - Operational Data)
|
|
# ============================================================================
|
|
|
|
class ScheduleAssignmentListView(LoginRequiredMixin, ListView):
|
|
"""
|
|
List all schedule assignments with filtering capabilities.
|
|
"""
|
|
model = ScheduleAssignment
|
|
template_name = 'hr/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/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/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
|
|
|
|
|
|
# ============================================================================
|
|
# TIME ENTRY VIEWS (RESTRICTED CRUD - Operational Data)
|
|
# ============================================================================
|
|
|
|
class TimeEntryListView(LoginRequiredMixin, ListView):
|
|
"""
|
|
List time entries with filtering capabilities.
|
|
"""
|
|
model = TimeEntry
|
|
template_name = 'hr/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_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_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_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
|
|
|
|
|
|
# ============================================================================
|
|
# PERFORMANCE REVIEW VIEWS (FULL CRUD - Operational Data)
|
|
# ============================================================================
|
|
|
|
class PerformanceReviewListView(LoginRequiredMixin, ListView):
|
|
"""
|
|
List performance reviews with filtering capabilities.
|
|
"""
|
|
model = PerformanceReview
|
|
template_name = 'hr/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/performance_review_detail.html'
|
|
context_object_name = 'review'
|
|
|
|
def get_queryset(self):
|
|
return PerformanceReview.objects.filter(employee__tenant=self.request.user.tenant)
|
|
|
|
|
|
class PerformanceReviewCreateView(LoginRequiredMixin, CreateView):
|
|
"""
|
|
Create a new performance review.
|
|
"""
|
|
model = PerformanceReview
|
|
form_class = PerformanceReviewForm
|
|
template_name = 'hr/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/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/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)
|
|
|
|
|
|
# ============================================================================
|
|
# TRAINING RECORD VIEWS (FULL CRUD - Operational Data)
|
|
# ============================================================================
|
|
|
|
class TrainingRecordListView(LoginRequiredMixin, ListView):
|
|
"""
|
|
List training records with filtering capabilities.
|
|
"""
|
|
model = TrainingRecord
|
|
template_name = 'hr/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_record_detail.html'
|
|
context_object_name = 'training_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_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_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_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)
|
|
|
|
|
|
# ============================================================================
|
|
# HTMX VIEWS FOR REAL-TIME UPDATES
|
|
# ============================================================================
|
|
|
|
@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_number__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)
|
|
|
|
|
|
# ============================================================================
|
|
# ACTION VIEWS FOR WORKFLOW OPERATIONS
|
|
# ============================================================================
|
|
|
|
@login_required
|
|
def clock_in(request):
|
|
"""
|
|
Clock in an employee.
|
|
"""
|
|
if request.method == 'POST':
|
|
employee_id = request.POST.get('employee_id')
|
|
|
|
try:
|
|
employee = Employee.objects.get(
|
|
id=employee_id,
|
|
tenant=request.user.tenant
|
|
)
|
|
|
|
# Check if already clocked in today
|
|
today = timezone.now().date()
|
|
existing_entry = TimeEntry.objects.filter(
|
|
employee=employee,
|
|
work_date=today,
|
|
clock_out_time__isnull=True
|
|
).first()
|
|
|
|
if existing_entry:
|
|
return JsonResponse({
|
|
'success': False,
|
|
'message': 'Employee is already clocked in.'
|
|
})
|
|
|
|
# Create new time entry
|
|
time_entry = TimeEntry.objects.create(
|
|
employee=employee,
|
|
work_date=today,
|
|
clock_in_time=timezone.now(),
|
|
status='DRAFT'
|
|
)
|
|
|
|
return JsonResponse({
|
|
'success': True,
|
|
'message': f'{employee.get_full_name()} clocked in successfully.',
|
|
'time_entry_id': time_entry.id
|
|
})
|
|
|
|
except Employee.DoesNotExist:
|
|
return JsonResponse({
|
|
'success': False,
|
|
'message': 'Employee not found.'
|
|
})
|
|
|
|
return JsonResponse({'success': False, 'message': 'Invalid request.'})
|
|
|
|
|
|
@login_required
|
|
def clock_out(request):
|
|
"""
|
|
Clock out an employee.
|
|
"""
|
|
if request.method == 'POST':
|
|
employee_id = request.POST.get('employee_id')
|
|
|
|
try:
|
|
employee = Employee.objects.get(
|
|
id=employee_id,
|
|
tenant=request.user.tenant
|
|
)
|
|
|
|
# Find active time 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:
|
|
return JsonResponse({
|
|
'success': False,
|
|
'message': 'No active clock-in found for this employee.'
|
|
})
|
|
|
|
# Update time entry
|
|
time_entry.clock_out_time = timezone.now()
|
|
time_entry.save() # This will trigger the save method to calculate hours
|
|
|
|
return JsonResponse({
|
|
'success': True,
|
|
'message': f'{employee.get_full_name()} clocked out successfully.',
|
|
'time_entry_id': time_entry.id,
|
|
'hours_worked': float(time_entry.total_hours)
|
|
})
|
|
|
|
except Employee.DoesNotExist:
|
|
return JsonResponse({
|
|
'success': False,
|
|
'message': 'Employee not found.'
|
|
})
|
|
|
|
return JsonResponse({'success': False, 'message': 'Invalid request.'})
|
|
|
|
|
|
@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_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/schedule_publish.html', {
|
|
'schedule': schedule
|
|
})
|
|
|
|
|
|
# ============================================================================
|
|
# API ENDPOINTS
|
|
# ============================================================================
|
|
|
|
@login_required
|
|
def api_employee_list(request):
|
|
"""
|
|
API endpoint for employee list.
|
|
"""
|
|
employees = Employee.objects.filter(
|
|
tenant=request.user.tenant
|
|
).values('id', 'first_name', 'last_name', 'employee_number', '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)})
|
|
|
|
#
|
|
# 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)
|
|
#
|