Ver 14.06 Show outstanding per proj
This commit is contained in:
parent
23f7726fb9
commit
a66f75fe32
Binary file not shown.
@ -30,8 +30,8 @@
|
|||||||
<div class="row g-4 mb-4">
|
<div class="row g-4 mb-4">
|
||||||
{% if is_admin_user %}
|
{% if is_admin_user %}
|
||||||
<!-- Admin: Outstanding Payments -->
|
<!-- Admin: Outstanding Payments -->
|
||||||
<div class="col-md-4">
|
<div class="col-xl-3 col-md-6">
|
||||||
<div class="card stat-card p-4 border-start border-4 border-warning">
|
<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 class="d-flex justify-content-between align-items-center">
|
||||||
<div>
|
<div>
|
||||||
<p class="text-uppercase small fw-bold mb-1 opacity-75">Outstanding Payments</p>
|
<p class="text-uppercase small fw-bold mb-1 opacity-75">Outstanding Payments</p>
|
||||||
@ -45,8 +45,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Admin: Paid This Month -->
|
<!-- Admin: Paid This Month -->
|
||||||
<div class="col-md-4">
|
<div class="col-xl-3 col-md-6">
|
||||||
<div class="card stat-card p-4 border-start border-4 border-success">
|
<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 class="d-flex justify-content-between align-items-center">
|
||||||
<div>
|
<div>
|
||||||
<p class="text-uppercase small fw-bold mb-1 opacity-75">Paid This Month</p>
|
<p class="text-uppercase small fw-bold mb-1 opacity-75">Paid This Month</p>
|
||||||
@ -59,8 +59,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Admin: Active Loans -->
|
<!-- Admin: Active Loans -->
|
||||||
<div class="col-md-4">
|
<div class="col-xl-3 col-md-6">
|
||||||
<div class="card stat-card p-4 border-start border-4 border-danger">
|
<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 class="d-flex justify-content-between align-items-center">
|
||||||
<div>
|
<div>
|
||||||
<p class="text-uppercase small fw-bold mb-1 opacity-75">Active Loans</p>
|
<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 →</a>
|
<a href="{% url 'payroll_dashboard' %}?status=loans" class="small text-muted mt-1 d-block stretched-link">View loans →</a>
|
||||||
</div>
|
</div>
|
||||||
</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 →</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<!-- Non-admin: Active Projects -->
|
<!-- Non-admin: Active Projects -->
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
|
|||||||
@ -20,8 +20,8 @@
|
|||||||
|
|
||||||
<!-- Analytics Section -->
|
<!-- Analytics Section -->
|
||||||
<div class="row mb-5">
|
<div class="row mb-5">
|
||||||
<!-- Outstanding Payments -->
|
<!-- Outstanding Payments (Global) -->
|
||||||
<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-warning bg-opacity-10">
|
<div class="card border-0 shadow-sm h-100 bg-warning bg-opacity-10">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h6 class="text-uppercase text-muted fw-bold small">Outstanding Payments</h6>
|
<h6 class="text-uppercase text-muted fw-bold small">Outstanding Payments</h6>
|
||||||
@ -32,7 +32,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Recent Payments -->
|
<!-- 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 border-0 shadow-sm h-100 bg-success bg-opacity-10">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h6 class="text-uppercase text-muted fw-bold small">Recent Payments (2 Months)</h6>
|
<h6 class="text-uppercase text-muted fw-bold small">Recent Payments (2 Months)</h6>
|
||||||
@ -42,10 +42,31 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Project Costs -->
|
<!-- Outstanding Project Costs -->
|
||||||
<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-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 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;">
|
<div class="card-body overflow-auto" style="max-height: 150px;">
|
||||||
{% if project_costs %}
|
{% if project_costs %}
|
||||||
<ul class="list-group list-group-flush">
|
<ul class="list-group list-group-flush">
|
||||||
|
|||||||
@ -104,6 +104,32 @@ def home(request):
|
|||||||
all_projects = Project.objects.all().order_by('name') if user_is_admin else []
|
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 []
|
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 = {
|
context = {
|
||||||
"is_admin_user": user_is_admin,
|
"is_admin_user": user_is_admin,
|
||||||
"workers_count": workers_count,
|
"workers_count": workers_count,
|
||||||
@ -116,6 +142,7 @@ def home(request):
|
|||||||
"recent_payments_total": recent_payments_total,
|
"recent_payments_total": recent_payments_total,
|
||||||
"active_loans_count": active_loans_count,
|
"active_loans_count": active_loans_count,
|
||||||
"active_loans_total": active_loans_total,
|
"active_loans_total": active_loans_total,
|
||||||
|
"outstanding_project_costs": outstanding_project_costs,
|
||||||
# This week
|
# This week
|
||||||
"week_worker_days": week_worker_days,
|
"week_worker_days": week_worker_days,
|
||||||
"week_projects": week_projects,
|
"week_projects": week_projects,
|
||||||
@ -750,9 +777,11 @@ def payroll_dashboard(request):
|
|||||||
|
|
||||||
# Analytics: Project Costs (Active Projects)
|
# Analytics: Project Costs (Active Projects)
|
||||||
project_costs = []
|
project_costs = []
|
||||||
|
outstanding_project_costs = []
|
||||||
active_projects = Project.objects.filter(is_active=True)
|
active_projects = Project.objects.filter(is_active=True)
|
||||||
|
|
||||||
for project in active_projects:
|
for project in active_projects:
|
||||||
|
# 1. Total Historical Cost
|
||||||
cost = 0
|
cost = 0
|
||||||
logs = project.logs.all()
|
logs = project.logs.all()
|
||||||
for log in logs:
|
for log in logs:
|
||||||
@ -761,6 +790,29 @@ def payroll_dashboard(request):
|
|||||||
if cost > 0:
|
if cost > 0:
|
||||||
project_costs.append({'name': project.name, 'cost': cost})
|
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
|
# Analytics: Previous 2 months payments
|
||||||
two_months_ago = timezone.now().date() - timedelta(days=60)
|
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
|
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_labels = [] # e.g. ["Sep 2025", "Oct 2025", ...]
|
||||||
chart_totals = [] # total payroll paid per month
|
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'))
|
all_project_names = list(Project.objects.values_list('name', flat=True).order_by('name'))
|
||||||
project_monthly = {name: [] for name in all_project_names}
|
project_monthly = {name: [] for name in all_project_names}
|
||||||
|
|
||||||
@ -843,6 +895,7 @@ def payroll_dashboard(request):
|
|||||||
'paid_records': paid_records,
|
'paid_records': paid_records,
|
||||||
'outstanding_total': outstanding_total,
|
'outstanding_total': outstanding_total,
|
||||||
'project_costs': project_costs,
|
'project_costs': project_costs,
|
||||||
|
'outstanding_project_costs': outstanding_project_costs,
|
||||||
'recent_payments_total': recent_payments_total,
|
'recent_payments_total': recent_payments_total,
|
||||||
'active_tab': status_filter,
|
'active_tab': status_filter,
|
||||||
'all_workers': all_workers,
|
'all_workers': all_workers,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user