Ver 14.06 Show outstanding per proj

This commit is contained in:
Flatlogic Bot 2026-02-13 11:42:39 +00:00
parent 23f7726fb9
commit a66f75fe32
4 changed files with 120 additions and 16 deletions

View File

@ -30,8 +30,8 @@
<div class="row g-4 mb-4">
{% if is_admin_user %}
<!-- Admin: Outstanding Payments -->
<div class="col-md-4">
<div class="card stat-card p-4 border-start border-4 border-warning">
<div class="col-xl-3 col-md-6">
<div class="card stat-card p-4 border-start border-4 border-warning h-100">
<div class="d-flex justify-content-between align-items-center">
<div>
<p class="text-uppercase small fw-bold mb-1 opacity-75">Outstanding Payments</p>
@ -45,8 +45,8 @@
</div>
</div>
<!-- Admin: Paid This Month -->
<div class="col-md-4">
<div class="card stat-card p-4 border-start border-4 border-success">
<div class="col-xl-3 col-md-6">
<div class="card stat-card p-4 border-start border-4 border-success h-100">
<div class="d-flex justify-content-between align-items-center">
<div>
<p class="text-uppercase small fw-bold mb-1 opacity-75">Paid This Month</p>
@ -59,8 +59,8 @@
</div>
</div>
<!-- Admin: Active Loans -->
<div class="col-md-4">
<div class="card stat-card p-4 border-start border-4 border-danger">
<div class="col-xl-3 col-md-6">
<div class="card stat-card p-4 border-start border-4 border-danger h-100">
<div class="d-flex justify-content-between align-items-center">
<div>
<p class="text-uppercase small fw-bold mb-1 opacity-75">Active Loans</p>
@ -74,6 +74,36 @@
<a href="{% url 'payroll_dashboard' %}?status=loans" class="small text-muted mt-1 d-block stretched-link">View loans &rarr;</a>
</div>
</div>
<!-- Admin: Outstanding Project Costs (New) -->
<div class="col-xl-3 col-md-6">
<div class="card stat-card border-start border-4 border-info h-100">
<div class="card-body p-4 pb-0">
<div class="d-flex justify-content-between align-items-center mb-2">
<p class="text-uppercase small fw-bold mb-0 opacity-75">Outstanding by Project</p>
<div class="p-2 bg-info bg-opacity-10 rounded-circle text-info">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 3v18h18"/><path d="M18.7 8l-5.1 5.2-2.8-2.7L7 14.3"/></svg>
</div>
</div>
<div class="overflow-auto" style="max-height: 100px;">
{% if outstanding_project_costs %}
<ul class="list-unstyled mb-0 small">
{% for p in outstanding_project_costs %}
<li class="d-flex justify-content-between py-1 border-bottom border-light">
<span class="text-truncate me-2" style="max-width: 120px;" title="{{ p.name }}">{{ p.name }}</span>
<span class="fw-bold">R {{ p.cost|intcomma }}</span>
</li>
{% endfor %}
</ul>
{% else %}
<p class="text-muted small">No outstanding costs.</p>
{% endif %}
</div>
</div>
<div class="card-footer bg-transparent border-0 pt-0 pb-3">
<a href="{% url 'payroll_dashboard' %}" class="small text-muted stretched-link">View payroll &rarr;</a>
</div>
</div>
</div>
{% else %}
<!-- Non-admin: Active Projects -->
<div class="col-md-4">
@ -395,4 +425,4 @@ document.addEventListener('DOMContentLoaded', function() {
});
</script>
{% endif %}
{% endblock %}
{% endblock %}

View File

@ -20,8 +20,8 @@
<!-- Analytics Section -->
<div class="row mb-5">
<!-- Outstanding Payments -->
<div class="col-md-4 mb-3">
<!-- Outstanding Payments (Global) -->
<div class="col-xl-3 col-md-6 mb-3">
<div class="card border-0 shadow-sm h-100 bg-warning bg-opacity-10">
<div class="card-body">
<h6 class="text-uppercase text-muted fw-bold small">Outstanding Payments</h6>
@ -32,7 +32,7 @@
</div>
<!-- Recent Payments -->
<div class="col-md-4 mb-3">
<div class="col-xl-3 col-md-6 mb-3">
<div class="card border-0 shadow-sm h-100 bg-success bg-opacity-10">
<div class="card-body">
<h6 class="text-uppercase text-muted fw-bold small">Recent Payments (2 Months)</h6>
@ -42,10 +42,31 @@
</div>
</div>
<!-- Project Costs -->
<div class="col-md-4 mb-3">
<!-- Outstanding Project Costs -->
<div class="col-xl-3 col-md-6 mb-3">
<div class="card border-0 shadow-sm h-100 bg-danger bg-opacity-10">
<div class="card-header bg-transparent border-0 fw-bold small text-uppercase text-muted pb-0">Outstanding by Project</div>
<div class="card-body overflow-auto pt-2" style="max-height: 150px;">
{% if outstanding_project_costs %}
<ul class="list-group list-group-flush bg-transparent">
{% for p in outstanding_project_costs %}
<li class="list-group-item d-flex justify-content-between align-items-center px-0 py-2 bg-transparent border-bottom border-danger border-opacity-25">
<span>{{ p.name }}</span>
<span class="fw-bold text-dark">R {{ p.cost|intcomma }}</span>
</li>
{% endfor %}
</ul>
{% else %}
<p class="text-muted small mb-0">No outstanding project costs.</p>
{% endif %}
</div>
</div>
</div>
<!-- Project Costs (Total History) -->
<div class="col-xl-3 col-md-6 mb-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-header bg-white fw-bold small text-uppercase text-muted">Project Costs (Active)</div>
<div class="card-header bg-white fw-bold small text-uppercase text-muted">Project Costs (History)</div>
<div class="card-body overflow-auto" style="max-height: 150px;">
{% if project_costs %}
<ul class="list-group list-group-flush">

View File

@ -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,