import os import platform import json from django.shortcuts import render, redirect, get_object_or_404 from django.utils import timezone from django.contrib.auth.decorators import login_required from django.db.models import Sum, Q from django.core.mail import send_mail from django.conf import settings from django.contrib import messages from .models import Worker, Project, Team, WorkLog, PayrollRecord from .forms import WorkLogForm from datetime import timedelta def home(request): """Render the landing screen with dashboard stats.""" workers_count = Worker.objects.count() projects_count = Project.objects.count() teams_count = Team.objects.count() recent_logs = WorkLog.objects.order_by('-date')[:5] # Analytics # 1. Outstanding Payments outstanding_total = 0 active_workers = Worker.objects.filter(is_active=True) for worker in active_workers: # Find unpaid logs for this worker unpaid_logs_count = worker.work_logs.exclude(paid_in__worker=worker).count() outstanding_total += unpaid_logs_count * worker.day_rate # 2. Project Costs (Active Projects) # Calculate sum of day_rates for all workers in all logs for each project project_costs = [] active_projects = Project.objects.filter(is_active=True) # Simple iteration for calculation (safer than complex annotations given properties) for project in active_projects: cost = 0 logs = project.logs.all() for log in logs: # We need to sum the day_rate of all workers in this log # Optimization: prefetch workers if slow, but for now just iterate for worker in log.workers.all(): cost += worker.day_rate if cost > 0: project_costs.append({'name': project.name, 'cost': cost}) # 3. Previous 2 months payments two_months_ago = timezone.now().date() - timedelta(days=60) recent_payments_total = PayrollRecord.objects.filter(date__gte=two_months_ago).aggregate(total=Sum('amount'))['total'] or 0 context = { "workers_count": workers_count, "projects_count": projects_count, "teams_count": teams_count, "recent_logs": recent_logs, "current_time": timezone.now(), "outstanding_total": outstanding_total, "project_costs": project_costs, "recent_payments_total": recent_payments_total, } return render(request, "core/index.html", context) def log_attendance(request): if request.method == 'POST': form = WorkLogForm(request.POST, user=request.user) if form.is_valid(): work_log = form.save(commit=False) if request.user.is_authenticated: work_log.supervisor = request.user work_log.save() form.save_m2m() return redirect('home') else: form = WorkLogForm(user=request.user if request.user.is_authenticated else None) # Build team workers map for frontend JS teams_qs = Team.objects.filter(is_active=True) if request.user.is_authenticated and not request.user.is_superuser: teams_qs = teams_qs.filter(supervisor=request.user) team_workers_map = {} for team in teams_qs: # Get active workers for the team active_workers = team.workers.filter(is_active=True).values_list('id', flat=True) team_workers_map[team.id] = list(active_workers) context = { 'form': form, 'team_workers_json': json.dumps(team_workers_map) } return render(request, 'core/log_attendance.html', context) def work_log_list(request): logs = WorkLog.objects.all().order_by('-date') return render(request, 'core/work_log_list.html', {'logs': logs}) def manage_resources(request): """View to manage active status of resources.""" workers = Worker.objects.all().order_by('name') projects = Project.objects.all().order_by('name') teams = Team.objects.all().order_by('name') context = { 'workers': workers, 'projects': projects, 'teams': teams, } return render(request, 'core/manage_resources.html', context) def toggle_resource_status(request, model_type, pk): """Toggle the is_active status of a resource.""" if request.method == 'POST': model_map = { 'worker': Worker, 'project': Project, 'team': Team, } model_class = model_map.get(model_type) if model_class: obj = get_object_or_404(model_class, pk=pk) obj.is_active = not obj.is_active obj.save() return redirect('manage_resources') def payroll_dashboard(request): """Dashboard for payroll management with filtering.""" status_filter = request.GET.get('status', 'pending') # pending, paid, all # Common Analytics outstanding_total = 0 active_workers = Worker.objects.filter(is_active=True).order_by('name') workers_data = [] # For pending payments for worker in active_workers: unpaid_logs = worker.work_logs.exclude(paid_in__worker=worker) count = unpaid_logs.count() amount = count * worker.day_rate if count > 0: outstanding_total += amount if status_filter in ['pending', 'all']: workers_data.append({ 'worker': worker, 'unpaid_count': count, 'unpaid_amount': amount, 'logs': unpaid_logs }) # Paid History paid_records = [] if status_filter in ['paid', 'all']: paid_records = PayrollRecord.objects.select_related('worker').order_by('-date', '-id') # Analytics: Project Costs (Active Projects) project_costs = [] active_projects = Project.objects.filter(is_active=True) for project in active_projects: cost = 0 logs = project.logs.all() for log in logs: for worker in log.workers.all(): cost += worker.day_rate if cost > 0: project_costs.append({'name': project.name, 'cost': cost}) # Analytics: Previous 2 months payments two_months_ago = timezone.now().date() - timedelta(days=60) recent_payments_total = PayrollRecord.objects.filter(date__gte=two_months_ago).aggregate(total=Sum('amount'))['total'] or 0 context = { 'workers_data': workers_data, 'paid_records': paid_records, 'outstanding_total': outstanding_total, 'project_costs': project_costs, 'recent_payments_total': recent_payments_total, 'active_tab': status_filter, } return render(request, 'core/payroll_dashboard.html', context) def process_payment(request, worker_id): """Process payment for a worker, mark logs as paid, and email receipt.""" worker = get_object_or_404(Worker, pk=worker_id) if request.method == 'POST': # Find unpaid logs unpaid_logs = worker.work_logs.exclude(paid_in__worker=worker) count = unpaid_logs.count() if count > 0: amount = count * worker.day_rate # Create Payroll Record payroll_record = PayrollRecord.objects.create( worker=worker, amount=amount, date=timezone.now().date() ) # Link logs payroll_record.work_logs.set(unpaid_logs) payroll_record.save() # Email Notification subject = f"Payslip for {worker.name} - {payroll_record.date}" message = ( f"Payslip Generated\n\n" f"Record ID: #{payroll_record.id}\n" f"Worker: {worker.name}\n" f"ID Number: {worker.id_no}\n" f"Date: {payroll_record.date}\n" f"Amount Paid: R {payroll_record.amount}\n\n" f"This is an automated notification from Fox Fitt Payroll." ) recipient_list = ['foxfitt-ed9wc+expense@to.sparkreceipt.com'] try: send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, recipient_list) messages.success(request, f"Payment of R {payroll_record.amount} processed for {worker.name}. Email sent to accounting.") except Exception as e: messages.warning(request, f"Payment processed, but email delivery failed: {str(e)}") return redirect('payroll_dashboard') return redirect('payroll_dashboard') def payslip_detail(request, pk): """Show details of a payslip (Payment Record).""" record = get_object_or_404(PayrollRecord, pk=pk) # Get the logs included in this payment logs = record.work_logs.all().order_by('date') context = { 'record': record, 'logs': logs, } return render(request, 'core/payslip.html', context)