From 306fb0e95d55ad61210b6d19afcde6cccd04abbb Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Sun, 22 Feb 2026 13:31:37 +0000 Subject: [PATCH] Ver 1.04 --- core/__pycache__/urls.cpython-311.pyc | Bin 465 -> 673 bytes core/__pycache__/views.cpython-311.pyc | Bin 2380 -> 9012 bytes core/templates/core/index.html | 332 +++++++++++++++++++++---- core/templates/core/work_history.html | 62 +++++ core/urls.py | 4 +- core/views.py | 132 +++++++++- 6 files changed, 470 insertions(+), 60 deletions(-) create mode 100644 core/templates/core/work_history.html diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc index 095285d695daa291a7e4c05b06e497a8e700a186..b5eaf8dba3654d1cf20127126c2415f450c30599 100644 GIT binary patch delta 310 zcmcb}ypUCWIWI340}yyJ&&~{CWMFs<;=lk0l<|4fMD^4lCWchT6wXxUEH;>Y3fCI8 zWy}l=tAQ8-z-qXGYS>{SDLm+E(pjQ7Qh0+IH2Ee@^9icr$jB@%$uFwZzr|CYUz8mW zEGgosEbcbF3BuQ z_0!~>_*8?v2;`|E?#Wz?0bD{rCeUXL#ZxAyFvi;7VBu(R>5!TbdPP)yMcxG#+lwr= qS6FPp!WTFVH<(^Ab~_P&fhG1LOY9Yv*bkhn{7enpAXp>-Gz9>cKTg>I delta 105 zcmZ3;dXZUuIWI340}vefJu7o7kbVr}zyKSR@i}XvdMZ~sQxsbYXE1{%*Tj21%zm2e nlWQ0?Ca+`kv1hnfaL-xIwUp4=4-(i~SWe diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 37a4af8442c6935c50f549e28d51ebe5a9ffb58b..d69ce1677b7076701af9a203902eca241073358e 100644 GIT binary patch literal 9012 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 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