diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc index 095285d..b5eaf8d 100644 Binary files a/core/__pycache__/urls.cpython-311.pyc and b/core/__pycache__/urls.cpython-311.pyc differ diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 37a4af8..d69ce16 100644 Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ diff --git a/core/templates/core/index.html b/core/templates/core/index.html index 27932e7..fc862c8 100644 --- a/core/templates/core/index.html +++ b/core/templates/core/index.html @@ -4,64 +4,97 @@ {% block title %}Dashboard | FoxFitt{% endblock %} {% block content %} -
-
-

Welcome back, {{ user.first_name|default:user.username }}!

- - Log Work - + +
+
+

Dashboard

+

Welcome back, {{ user.first_name|default:user.username }}!

+ + Log Daily Work + +
- -
- -
-
+
+ {% if is_admin %} + +
+ +
+
+
+
+
+
+ Outstanding Payments
+
${{ outstanding_payments|floatformat:2 }}
+
+
+ +
+
+
+
+
+ + +
+
- Active Workers
-
{{ total_workers|default:"0" }}
+ Paid This Month
+
${{ paid_this_month|floatformat:2 }}
- +
- -
-
-
-
-
-
- Active Projects
-
{{ total_projects|default:"0" }}
-
-
- -
-
-
-
-
- - -
-
+ +
+
- Today's Logs
-
{{ today_attendance|default:"0" }}
+ Active Loans ({{ active_loans_count }})
+
${{ active_loans_balance|floatformat:2 }}
- + +
+
+
+
+
+ + +
+
+
+
+
+
+ Outstanding by Project
+
+ {% if outstanding_by_project %} +
    + {% for proj, amount in outstanding_by_project.items %} +
  • {{ proj }}: ${{ amount|floatformat:2 }}
  • + {% endfor %} +
+ {% else %} + None + {% endif %} +
+
+
+
@@ -69,18 +102,223 @@
- -
-
-
-
-
Recent Activity
+ +
+ +
+
+
+
This Week Summary
-
-

No recent activity to show yet. Start by logging today's attendance!

+
+
{{ this_week_logs }}
+
Work Logs Created This Week
+
+
+
+ + +
+ +
+ +
+
+
+
Recent Activity
+
+
+
+ {% for log in recent_activity %} +
+
+
+
{{ log.project.name }}
+ {{ log.date }} · {{ log.workers.count }} workers +
+ {{ log.supervisor.username }} +
+
+ {% empty %} +
+ No recent activity. +
+ {% endfor %} +
+
+
+
+ + +
+
+
+
Manage Resources
+
+
+ +
+ +
+
    + {% for item in workers %} +
  • + {{ item.name }} +
    + +
    +
  • + {% empty %} +
  • No workers found.
  • + {% endfor %} +
+
+ +
+
    + {% for item in projects %} +
  • + {{ item.name }} +
    + +
    +
  • + {% empty %} +
  • No projects found.
  • + {% endfor %} +
+
+ +
+
    + {% for item in teams %} +
  • + {{ item.name }} +
    + +
    +
  • + {% empty %} +
  • No teams found.
  • + {% endfor %} +
+
+
+
+
+
+
+ {% else %} + +
+
+
+
+
This Week Summary
+
+
+
{{ this_week_logs }}
+
Work Logs Created This Week
+
+
+
+
+
+
+
Recent Activity
+
+
+
+ {% for log in recent_activity %} +
+
+
+
{{ log.project.name }}
+ {{ log.date }} · {{ log.workers.count }} workers +
+
+
+ {% empty %} +
+ No recent activity. +
+ {% endfor %} +
+
+
+
+
+ {% endif %}
-{% endblock %} \ No newline at end of file + + + + + +{% endblock %} diff --git a/core/templates/core/work_history.html b/core/templates/core/work_history.html new file mode 100644 index 0000000..81a5c6f --- /dev/null +++ b/core/templates/core/work_history.html @@ -0,0 +1,62 @@ +{% extends 'base.html' %} +{% load static %} + +{% block title %}Work History | FoxFitt{% endblock %} + +{% block content %} +
+
+

Work History

+ + Back + +
+ +
+
+
+ + + + + + + + + + + + + + {% for log in logs %} + + + + + + + + + + {% empty %} + + + + {% endfor %} + +
DateProjectTeamWorkersSupervisorOvertimeNotes
{{ log.date }}{{ log.project.name }}{{ log.team.name|default:"-" }} + {{ log.workers.count }} + {{ log.supervisor.username|default:"-" }} + {% if log.overtime_amount > 0 %} + {{ log.get_overtime_amount_display }} + {% else %} + - + {% endif %} + + {{ log.notes|truncatechars:30 }} +
No work history found.
+
+
+
+
+{% endblock %} diff --git a/core/urls.py b/core/urls.py index 3d5b301..911b624 100644 --- a/core/urls.py +++ b/core/urls.py @@ -4,4 +4,6 @@ from . import views urlpatterns = [ path('', views.index, name='home'), path('attendance/log/', views.attendance_log, name='attendance_log'), -] \ No newline at end of file + path('history/', views.work_history, name='work_history'), + path('toggle///', views.toggle_active, name='toggle_active'), +] diff --git a/core/views.py b/core/views.py index 22eea72..9469e11 100644 --- a/core/views.py +++ b/core/views.py @@ -1,23 +1,95 @@ -from django.shortcuts import render, redirect +from django.shortcuts import render, redirect, get_object_or_404 from django.utils import timezone -from .models import Worker, Project, WorkLog, Team +from django.db.models import Sum +from decimal import Decimal +from .models import Worker, Project, WorkLog, Team, PayrollRecord, Loan, PayrollAdjustment from .forms import AttendanceLogForm from django.contrib import messages from django.contrib.auth.decorators import login_required +from django.http import JsonResponse, HttpResponseForbidden + +def is_admin(user): + return user.is_staff or user.is_superuser + +def is_supervisor(user): + return user.supervised_teams.exists() or user.assigned_projects.exists() or user.groups.filter(name='Work Logger').exists() + +def is_staff_or_supervisor(user): + return is_admin(user) or is_supervisor(user) # Home view for the dashboard @login_required def index(request): - total_workers = Worker.objects.filter(active=True).count() - total_projects = Project.objects.filter(active=True).count() - today_attendance = WorkLog.objects.filter(date=timezone.now().date()).count() + user = request.user - context = { - 'total_workers': total_workers, - 'total_projects': total_projects, - 'today_attendance': today_attendance, - } - return render(request, 'core/index.html', context) + if is_admin(user): + # Calculate total value of unpaid work and break it down by project + unpaid_worklogs = WorkLog.objects.filter(payroll_records__isnull=True).prefetch_related('workers', 'project') + outstanding_payments = Decimal('0.00') + outstanding_by_project = {} + + for wl in unpaid_worklogs: + project_name = wl.project.name + if project_name not in outstanding_by_project: + outstanding_by_project[project_name] = Decimal('0.00') + for worker in wl.workers.all(): + cost = worker.daily_rate + outstanding_payments += cost + outstanding_by_project[project_name] += cost + + # Include unpaid payroll adjustments in the outstanding calculations + unpaid_adjustments = PayrollAdjustment.objects.filter(payroll_record__isnull=True) + for adj in unpaid_adjustments: + outstanding_payments += adj.amount + project_name = adj.project.name if adj.project else 'General' + if project_name not in outstanding_by_project: + outstanding_by_project[project_name] = Decimal('0.00') + outstanding_by_project[project_name] += adj.amount + + # Sum the total amount paid out over the last 60 days + sixty_days_ago = timezone.now().date() - timezone.timedelta(days=60) + paid_this_month = PayrollRecord.objects.filter(date__gte=sixty_days_ago).aggregate(total=Sum('amount_paid'))['total'] or Decimal('0.00') + + # Tally the count and total balance of active loans + active_loans_qs = Loan.objects.filter(active=True) + active_loans_count = active_loans_qs.count() + active_loans_balance = active_loans_qs.aggregate(total=Sum('remaining_balance'))['total'] or Decimal('0.00') + + start_of_week = timezone.now().date() - timezone.timedelta(days=timezone.now().date().weekday()) + this_week_logs = WorkLog.objects.filter(date__gte=start_of_week).count() + + recent_activity = WorkLog.objects.all().order_by('-date', '-id')[:5] + + # Get all workers, projects, and teams for the Manage Resources tab + workers = Worker.objects.all().order_by('name') + projects = Project.objects.all().order_by('name') + teams = Team.objects.all().order_by('name') + + context = { + 'is_admin': True, + 'outstanding_payments': outstanding_payments, + 'paid_this_month': paid_this_month, + 'active_loans_count': active_loans_count, + 'active_loans_balance': active_loans_balance, + 'outstanding_by_project': outstanding_by_project, + 'this_week_logs': this_week_logs, + 'recent_activity': recent_activity, + 'workers': workers, + 'projects': projects, + 'teams': teams, + } + return render(request, 'core/index.html', context) + else: + start_of_week = timezone.now().date() - timezone.timedelta(days=timezone.now().date().weekday()) + this_week_logs = WorkLog.objects.filter(date__gte=start_of_week, supervisor=user).count() + recent_activity = WorkLog.objects.filter(supervisor=user).order_by('-date', '-id')[:5] + + context = { + 'is_admin': False, + 'this_week_logs': this_week_logs, + 'recent_activity': recent_activity, + } + return render(request, 'core/index.html', context) # View for logging attendance @login_required @@ -29,6 +101,42 @@ def attendance_log(request): messages.success(request, 'Attendance logged successfully!') return redirect('home') else: - form = AttendanceLogForm(initial={'date': timezone.now().date()}) + form = AttendanceLogForm(initial={'date': timezone.now().date(), 'supervisor': request.user}) return render(request, 'core/attendance_log.html', {'form': form}) + +# Work history view +@login_required +def work_history(request): + if is_admin(request.user): + logs = WorkLog.objects.all().order_by('-date', '-id') + else: + logs = WorkLog.objects.filter(supervisor=request.user).order_by('-date', '-id') + return render(request, 'core/work_history.html', {'logs': logs}) + +# API view to toggle resource active status +@login_required +def toggle_active(request, model_name, item_id): + if request.method != 'POST': + return HttpResponseForbidden("Only POST requests are allowed.") + + if not is_admin(request.user): + return HttpResponseForbidden("Not authorized.") + + model_map = { + 'worker': Worker, + 'project': Project, + 'team': Team + } + + if model_name not in model_map: + return JsonResponse({'error': 'Invalid model'}, status=400) + + model = model_map[model_name] + try: + item = model.objects.get(id=item_id) + item.active = not item.active + item.save() + return JsonResponse({'status': 'success', 'active': item.active}) + except model.DoesNotExist: + return JsonResponse({'error': 'Item not found'}, status=404) \ No newline at end of file