Add outstanding payments breakdown on dashboard
Split the single outstanding total into unpaid wages, additions, and deductions so the card shows where the number comes from. Rename the 'General' project bucket to 'No Project' so per-project totals now visibly sum to the overall total. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
d51d06d28d
commit
d33d5943f9
@ -24,6 +24,7 @@
|
|||||||
<!-- Admin View -->
|
<!-- Admin View -->
|
||||||
<div class="row g-4 mb-4 position-relative">
|
<div class="row g-4 mb-4 position-relative">
|
||||||
<!-- Outstanding Payments Card -->
|
<!-- Outstanding Payments Card -->
|
||||||
|
<!-- Shows the total owed to workers, with a breakdown of wages vs adjustments -->
|
||||||
<div class="col-xl-3 col-md-6">
|
<div class="col-xl-3 col-md-6">
|
||||||
<div class="card stat-card h-100 py-2">
|
<div class="card stat-card h-100 py-2">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
@ -32,8 +33,32 @@
|
|||||||
<div class="text-xs font-weight-bold text-uppercase mb-1" style="color: #ef4444;">
|
<div class="text-xs font-weight-bold text-uppercase mb-1" style="color: #ef4444;">
|
||||||
Outstanding Payments</div>
|
Outstanding Payments</div>
|
||||||
<div class="h5 mb-0 font-weight-bold text-gray-800">R {{ outstanding_payments|floatformat:2 }}</div>
|
<div class="h5 mb-0 font-weight-bold text-gray-800">R {{ outstanding_payments|floatformat:2 }}</div>
|
||||||
|
{# === BREAKDOWN — only shown when there are pending adjustments === #}
|
||||||
|
{% if pending_adjustments_add or pending_adjustments_sub %}
|
||||||
|
<div class="mt-2 pt-2 border-top" style="font-size: 0.75rem; color: #64748b;">
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<span>Unpaid wages</span>
|
||||||
|
<span>R {{ unpaid_wages|floatformat:2 }}</span>
|
||||||
|
</div>
|
||||||
|
{% if pending_adjustments_add %}
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<span>+ Additions</span>
|
||||||
|
<span class="text-success">R {{ pending_adjustments_add|floatformat:2 }}</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if pending_adjustments_sub %}
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<span>- Deductions</span>
|
||||||
|
<span class="text-danger">-R {{ pending_adjustments_sub|floatformat:2 }}</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="mt-1" style="font-size: 0.65rem; color: #94a3b8;">
|
||||||
|
<i class="fas fa-info-circle"></i> Loan repayments deducted at payment time
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto">
|
<div class="col-auto align-self-start">
|
||||||
<i class="fas fa-exclamation-circle fa-2x text-danger opacity-50"></i>
|
<i class="fas fa-exclamation-circle fa-2x text-danger opacity-50"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -79,7 +79,12 @@ def index(request):
|
|||||||
'project'
|
'project'
|
||||||
).prefetch_related('workers', 'payroll_records')
|
).prefetch_related('workers', 'payroll_records')
|
||||||
|
|
||||||
outstanding_payments = Decimal('0.00')
|
# === OUTSTANDING BREAKDOWN ===
|
||||||
|
# Track unpaid wages and adjustments separately so the dashboard
|
||||||
|
# can show a clear breakdown of what makes up the total.
|
||||||
|
unpaid_wages = Decimal('0.00') # Pure daily rates for unpaid workers
|
||||||
|
pending_adjustments_add = Decimal('0.00') # Unpaid additive adjustments (bonuses, overtime, etc.)
|
||||||
|
pending_adjustments_sub = Decimal('0.00') # Unpaid deductive adjustments (deductions, repayments)
|
||||||
outstanding_by_project = {}
|
outstanding_by_project = {}
|
||||||
|
|
||||||
for wl in all_worklogs:
|
for wl in all_worklogs:
|
||||||
@ -90,30 +95,33 @@ def index(request):
|
|||||||
for worker in wl.workers.all():
|
for worker in wl.workers.all():
|
||||||
if worker.id not in paid_worker_ids:
|
if worker.id not in paid_worker_ids:
|
||||||
cost = worker.daily_rate
|
cost = worker.daily_rate
|
||||||
outstanding_payments += cost
|
unpaid_wages += cost
|
||||||
if project_name not in outstanding_by_project:
|
if project_name not in outstanding_by_project:
|
||||||
outstanding_by_project[project_name] = Decimal('0.00')
|
outstanding_by_project[project_name] = Decimal('0.00')
|
||||||
outstanding_by_project[project_name] += cost
|
outstanding_by_project[project_name] += cost
|
||||||
|
|
||||||
# Also include unpaid payroll adjustments (bonuses, deductions, etc.)
|
# Also include unpaid payroll adjustments (bonuses, deductions, etc.)
|
||||||
# Additive types (Bonus, Overtime, New Loan) increase outstanding.
|
# Additive types (Bonus, Overtime, New Loan) increase outstanding.
|
||||||
# Deductive types (Deduction, Loan Repayment, Advance Payment) decrease it.
|
# Deductive types (Deduction, Loan Repayment, Advance Repayment) decrease it.
|
||||||
unpaid_adjustments = PayrollAdjustment.objects.filter(
|
unpaid_adjustments = PayrollAdjustment.objects.filter(
|
||||||
payroll_record__isnull=True
|
payroll_record__isnull=True
|
||||||
).select_related('project')
|
).select_related('project')
|
||||||
|
|
||||||
for adj in unpaid_adjustments:
|
for adj in unpaid_adjustments:
|
||||||
project_name = adj.project.name if adj.project else 'General'
|
project_name = adj.project.name if adj.project else 'No Project'
|
||||||
if project_name not in outstanding_by_project:
|
if project_name not in outstanding_by_project:
|
||||||
outstanding_by_project[project_name] = Decimal('0.00')
|
outstanding_by_project[project_name] = Decimal('0.00')
|
||||||
|
|
||||||
if adj.type in ADDITIVE_TYPES:
|
if adj.type in ADDITIVE_TYPES:
|
||||||
outstanding_payments += adj.amount
|
pending_adjustments_add += adj.amount
|
||||||
outstanding_by_project[project_name] += adj.amount
|
outstanding_by_project[project_name] += adj.amount
|
||||||
elif adj.type in DEDUCTIVE_TYPES:
|
elif adj.type in DEDUCTIVE_TYPES:
|
||||||
outstanding_payments -= adj.amount
|
pending_adjustments_sub += adj.amount
|
||||||
outstanding_by_project[project_name] -= adj.amount
|
outstanding_by_project[project_name] -= adj.amount
|
||||||
|
|
||||||
|
# Net total = wages + additions - deductions (same result as before, just tracked separately)
|
||||||
|
outstanding_payments = unpaid_wages + pending_adjustments_add - pending_adjustments_sub
|
||||||
|
|
||||||
# Sum total paid out in the last 60 days
|
# Sum total paid out in the last 60 days
|
||||||
sixty_days_ago = timezone.now().date() - timezone.timedelta(days=60)
|
sixty_days_ago = timezone.now().date() - timezone.timedelta(days=60)
|
||||||
paid_this_month = PayrollRecord.objects.filter(
|
paid_this_month = PayrollRecord.objects.filter(
|
||||||
@ -148,6 +156,9 @@ def index(request):
|
|||||||
context = {
|
context = {
|
||||||
'is_admin': True,
|
'is_admin': True,
|
||||||
'outstanding_payments': outstanding_payments,
|
'outstanding_payments': outstanding_payments,
|
||||||
|
'unpaid_wages': unpaid_wages,
|
||||||
|
'pending_adjustments_add': pending_adjustments_add,
|
||||||
|
'pending_adjustments_sub': pending_adjustments_sub,
|
||||||
'paid_this_month': paid_this_month,
|
'paid_this_month': paid_this_month,
|
||||||
'active_loans_count': active_loans_count,
|
'active_loans_count': active_loans_count,
|
||||||
'active_loans_balance': active_loans_balance,
|
'active_loans_balance': active_loans_balance,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user