From a66f75fe323e2ae5c60e10a512fc8f3605d8d325 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Fri, 13 Feb 2026 11:42:39 +0000 Subject: [PATCH] Ver 14.06 Show outstanding per proj --- core/__pycache__/views.cpython-311.pyc | Bin 57677 -> 59670 bytes core/templates/core/index.html | 44 ++++++++++++--- core/templates/core/payroll_dashboard.html | 33 +++++++++--- core/views.py | 59 +++++++++++++++++++-- 4 files changed, 120 insertions(+), 16 deletions(-) diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 2aa9d2481c2ee85c907b1f79ec21d3a3741044ec..22896605103cf837a04234dae564ecf21703adcb 100644 GIT binary patch delta 8734 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 fU9y(jspmg;;Kf?kRGsyPx;g|md3|Q>O diff --git a/core/templates/core/index.html b/core/templates/core/index.html index a9b9a95..5f1d45c 100644 --- a/core/templates/core/index.html +++ b/core/templates/core/index.html @@ -30,8 +30,8 @@
{% if is_admin_user %} -
-
+
+

Outstanding Payments

@@ -45,8 +45,8 @@
-
-
+
+

Paid This Month

@@ -59,8 +59,8 @@
-
-
+
+

Active Loans

@@ -74,6 +74,36 @@ View loans →
+ +
+
+
+
+

Outstanding by Project

+
+ +
+
+
+ {% if outstanding_project_costs %} +
    + {% for p in outstanding_project_costs %} +
  • + {{ p.name }} + R {{ p.cost|intcomma }} +
  • + {% endfor %} +
+ {% else %} +

No outstanding costs.

+ {% endif %} +
+
+ +
+
{% else %}
@@ -395,4 +425,4 @@ document.addEventListener('DOMContentLoaded', function() { }); {% endif %} -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/core/templates/core/payroll_dashboard.html b/core/templates/core/payroll_dashboard.html index d5191d4..961a44c 100644 --- a/core/templates/core/payroll_dashboard.html +++ b/core/templates/core/payroll_dashboard.html @@ -20,8 +20,8 @@
- -
+ +
Outstanding Payments
@@ -32,7 +32,7 @@
-
+
Recent Payments (2 Months)
@@ -42,10 +42,31 @@
- -
+ +
+
+
Outstanding by Project
+
+ {% if outstanding_project_costs %} +
    + {% for p in outstanding_project_costs %} +
  • + {{ p.name }} + R {{ p.cost|intcomma }} +
  • + {% endfor %} +
+ {% else %} +

No outstanding project costs.

+ {% endif %} +
+
+
+ + +
-
Project Costs (Active)
+
Project Costs (History)
{% if project_costs %}
    diff --git a/core/views.py b/core/views.py index 91158f9..e6d4f07 100644 --- a/core/views.py +++ b/core/views.py @@ -104,6 +104,32 @@ def home(request): all_projects = Project.objects.all().order_by('name') if user_is_admin else [] all_teams = Team.objects.all().prefetch_related('workers').order_by('name') if user_is_admin else [] + # Outstanding Project Costs (Admin only) - Added for Dashboard visibility + outstanding_project_costs = [] + if user_is_admin: + for project in all_projects: + outstanding_cost = 0 + + # Unpaid WorkLogs + unpaid_logs = project.logs.filter(paid_in__isnull=True).prefetch_related('workers') + for log in unpaid_logs: + for worker in log.workers.all(): + outstanding_cost += worker.day_rate + + # Unpaid Adjustments linked to this project + project_adjustments = PayrollAdjustment.objects.filter( + work_log__project=project, + payroll_record__isnull=True + ) + for adj in project_adjustments: + if adj.type in ['BONUS', 'OVERTIME', 'LOAN']: + outstanding_cost += adj.amount + elif adj.type in ['DEDUCTION', 'LOAN_REPAYMENT']: + outstanding_cost -= adj.amount + + if outstanding_cost > 0: + outstanding_project_costs.append({'name': project.name, 'cost': outstanding_cost}) + context = { "is_admin_user": user_is_admin, "workers_count": workers_count, @@ -116,6 +142,7 @@ def home(request): "recent_payments_total": recent_payments_total, "active_loans_count": active_loans_count, "active_loans_total": active_loans_total, + "outstanding_project_costs": outstanding_project_costs, # This week "week_worker_days": week_worker_days, "week_projects": week_projects, @@ -668,7 +695,7 @@ def toggle_resource_status(request, model_type, pk): return JsonResponse({ 'success': True, 'is_active': obj.is_active, - 'message': f"{obj.name} is now {'Active' if obj.is_active else 'Inactive'}." + 'message': f"{obj.name} is now {'Active' if obj.is_active else 'Inactive'}." }) return redirect('home') @@ -750,9 +777,11 @@ def payroll_dashboard(request): # Analytics: Project Costs (Active Projects) project_costs = [] + outstanding_project_costs = [] active_projects = Project.objects.filter(is_active=True) for project in active_projects: + # 1. Total Historical Cost cost = 0 logs = project.logs.all() for log in logs: @@ -760,7 +789,30 @@ def payroll_dashboard(request): cost += worker.day_rate if cost > 0: project_costs.append({'name': project.name, 'cost': cost}) - + + # 2. Outstanding Cost (Unpaid) + outstanding_cost = 0 + + # Unpaid WorkLogs + unpaid_logs = project.logs.filter(paid_in__isnull=True).prefetch_related('workers') + for log in unpaid_logs: + for worker in log.workers.all(): + outstanding_cost += worker.day_rate + + # Unpaid Adjustments linked to this project + project_adjustments = PayrollAdjustment.objects.filter( + work_log__project=project, + payroll_record__isnull=True + ) + for adj in project_adjustments: + if adj.type in ['BONUS', 'OVERTIME', 'LOAN']: + outstanding_cost += adj.amount + elif adj.type in ['DEDUCTION', 'LOAN_REPAYMENT']: + outstanding_cost -= adj.amount + + if outstanding_cost > 0: + outstanding_project_costs.append({'name': project.name, 'cost': outstanding_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 @@ -790,7 +842,7 @@ def payroll_dashboard(request): chart_labels = [] # e.g. ["Sep 2025", "Oct 2025", ...] chart_totals = [] # total payroll paid per month - # For per-project chart: {project_name: [month0_cost, month1_cost, ...]} + # For per-project chart: {project_name: [month0_cost, month1_cost, ...]}} all_project_names = list(Project.objects.values_list('name', flat=True).order_by('name')) project_monthly = {name: [] for name in all_project_names} @@ -843,6 +895,7 @@ def payroll_dashboard(request): 'paid_records': paid_records, 'outstanding_total': outstanding_total, 'project_costs': project_costs, + 'outstanding_project_costs': outstanding_project_costs, 'recent_payments_total': recent_payments_total, 'active_tab': status_filter, 'all_workers': all_workers,