trace for the my way of writing the dashboard logic

This commit is contained in:
Faheedkhan 2025-08-17 20:07:23 +03:00
parent 0dcc7d56a3
commit d0ac8eba9a
4 changed files with 272 additions and 104 deletions

View File

@ -602,7 +602,7 @@ def update_car_status_on_reservation_create(sender, instance, created, **kwargs)
"""
Signal handler to update the status of a car upon the creation of a car reservation.
This function is triggered when a new instance of a CarReservation is created and saved
to the database. It modifies the status of the associated car to reflect the RESERVED status.
to the database. It modifies the status of the associated car to reflect the D status.
:param sender: The model class that sends the signal (CarReservation).
:param instance: The specific instance of the CarReservation that triggered the signal.

View File

@ -495,14 +495,22 @@ class TestView(TemplateView):
from django.db.models import Sum, F, Count
from django.db.models.functions import ExtractMonth
from django.utils import timezone
from datetime import timedelta
def DealerDashboard(request):
dealer = request.dealer
# This part is correct as is, it filters cars by dealer and sold status
cars_sold = models.Car.objects.filter(dealer=dealer, status='sold')
today_local = timezone.localdate()
start_date_str = request.GET.get('start_date')
end_date_str = request.GET.get('end_date')
# This part needs to be made timezone-aware and robust
if not start_date_str:
start_date = today_local - timedelta(days=30)
else:
@ -513,41 +521,143 @@ def DealerDashboard(request):
else:
end_date = timezone.datetime.strptime(end_date_str, '%Y-%m-%d').date()
# Existing inventory calculations
active_cars = models.Car.objects.filter(status__in=['available', 'reserved', 'damaged'])
total_cars_in_inventory = active_cars.count()
new_cars_qs= active_cars.filter(stock_type='new')
used_cars_qs=active_cars.filter(stock_type='old')
total_new_cars_in_inventory = new_cars_qs.count()
total_used_cars_in_inventory = used_cars_qs.count()
total_inventory_value = sum([car.cost_price for car in active_cars])
new_car_value=sum([car.cost_price for car in new_cars_qs])
used_car_value=sum([car.cost_price for car in used_cars_qs])
# Define the aging threshold (e.g., 90 days)
aging_threshold_days = 60
# Efficiently query the database for aging cars
aging_inventory = active_cars.filter(created_at__lte=today_local - timedelta(days=aging_threshold_days))
# You can get the count for display on the dashboard
aging_inventory_count = aging_inventory.count()
# The database query will automatically be handled in a timezone-aware manner
cars_sold_filtered = cars_sold.filter(
sold_date__date__gte=start_date,
sold_date__date__lte=end_date
)
print(cars_sold_filtered)
# # Calculate summary data for the filtered results
total_cars_sold=cars_sold_filtered.count()
print(total_cars_sold)
total_revenue_from_cars = sum([ car.final_price for car in cars_sold_filtered])
total_cost_of_cars_sold=sum([car.cost_price for car in cars_sold_filtered])
net_profit_from_cars=total_revenue_from_cars-total_cost_of_cars_sold
total_vat_collected_from_cars=sum([car.vat_amount for car in cars_sold_filtered])
total_revenue_from_services=sum([car.get_additional_services()['total'] for car in cars_sold_filtered])
total_vat_collected_from_services=sum([car.get_additional_services()['services_vat'] for car in cars_sold_filtered])
total_vat_collected = total_vat_collected_from_cars+total_vat_collected_from_services
total_revenue_generated=total_revenue_from_cars+total_revenue_from_services
# Calculate summary data for the filtered results
total_cars_sold = cars_sold_filtered.count()
new_cars_sold=cars_sold_filtered.filter(stock_type='new')
used_cars_sold=cars_sold_filtered.filter(stock_type='used')
total_new_cars_sold=new_cars_sold.count()
total_used_cars_sold=used_cars_sold.count()
# Calculations for all cars
total_cost_of_cars_sold = sum([car.cost_price for car in cars_sold_filtered])
total_revenue_from_cars = sum([car.final_price for car in cars_sold_filtered])
net_profit_from_cars = total_revenue_from_cars - total_cost_of_cars_sold
total_vat_collected_from_cars = sum([car.vat_amount for car in cars_sold_filtered])
# Calculations specifically for used cars
total_cost_of_used_cars_sold = sum([car.cost_price for car in used_cars_sold])
total_revenue_from_used_cars = sum([car.final_price for car in used_cars_sold])
net_profit_from_used_cars = total_revenue_from_used_cars - total_cost_of_used_cars_sold
total_vat_collected_from_used_cars = sum([car.vat_amount for car in used_cars_sold])
# Calculations specifically for new cars
total_cost_of_new_cars_sold = sum([car.cost_price for car in new_cars_sold])
total_revenue_from_new_cars = sum([car.final_price for car in new_cars_sold])
net_profit_from_new_cars = total_revenue_from_new_cars - total_cost_of_new_cars_sold
total_vat_collected_from_new_cars = sum([car.vat_amount for car in new_cars_sold])
total_revenue_from_services = sum([car.get_additional_services()['total'] for car in cars_sold_filtered])
total_vat_collected_from_services = sum([car.get_additional_services()['services_vat'] for car in cars_sold_filtered])
total_vat_collected = total_vat_collected_from_cars + total_vat_collected_from_services
total_revenue_generated = total_revenue_from_cars + total_revenue_from_services
total_discount = sum([car.discount for car in cars_sold_filtered])
# Filter for expenses
expenses = ItemModel.objects.filter(entity__admin__dealer=dealer, item_role='expense')
total_expenses = sum([expense.default_amount for expense in expenses])
gross_profit = net_profit_from_cars - total_expenses
# ----------------------------------------------------
# NEW LOGIC FOR CHARTS - Aggregating data by month
# ----------------------------------------------------
# Group cars sold by month and aggregate totals
monthly_sales_data = cars_sold_filtered.annotate(
month=ExtractMonth('sold_date')
).values('month').annotate(
total_cars=Count('pk'),
total_revenue=Sum(F('marked_price') - F('discount_amount')), # Corrected for marked price and discount_amount
total_profit=Sum(F('marked_price') - F('discount_amount') - F('cost_price')) # Corrected profit calculation
).order_by('month')
# Initialize lists for chart data
monthly_cars_sold = [0] * 12
monthly_revenue = [0] * 12
monthly_net_profit = [0] * 12
# Populate the lists from the queryset results
for data in monthly_sales_data:
month_index = data['month'] - 1 # Months are 1-12, so we need to adjust to 0-11
monthly_cars_sold[month_index] = data['total_cars']
monthly_revenue[month_index] = data['total_revenue']
monthly_net_profit[month_index] = data['total_profit']
# the monthly revenue is of this form: [0, 0, 0, 0, 0, 0, 0, Decimal('21000'), 0, 0, 0, 0] so javascript cannot recognise Decimale('21000') as no.
# NEW: Convert the lists to JSON strings
monthly_cars_sold_json = json.dumps(monthly_cars_sold)
monthly_revenue_json = json.dumps([float(x) for x in monthly_revenue]) # Convert Decimal to float
monthly_net_profit_json = json.dumps([float(x) for x in monthly_net_profit])
# Update the context dictionary with the new monthly data lists
context = {
'start_date': start_date,
'end_date': end_date,
'cars_sold': cars_sold_filtered,
'total_cars_sold':total_cars_sold,
'total_cost_of_cars_sold':total_cost_of_cars_sold,
'total_revenue_from_cars':total_revenue_from_cars,
'net_profit_from_cars':net_profit_from_cars,
'total_cars_sold': total_cars_sold,
'total_cost_of_cars_sold': total_cost_of_cars_sold,
'total_revenue_from_cars': total_revenue_from_cars,
'net_profit_from_cars': net_profit_from_cars,
'total_vat_collected_from_cars': total_vat_collected_from_cars,
'total_discount_on_cars':total_discount,
'total_revenue_from_services':total_revenue_from_services,
'total_discount_on_cars': total_discount,
'total_revenue_from_services': total_revenue_from_services,
'total_vat_collected_from_services': total_vat_collected_from_services,
'total_revenue_generated': total_revenue_generated,
'total_vat_collected':total_vat_collected,
'total_vat_collected': total_vat_collected,
'total_expenses': total_expenses,
"gross_profit": gross_profit,
'total_cars_in_inventory': total_cars_in_inventory,
'total_used_cars_in_inventory': total_used_cars_in_inventory,
'total_new_cars_in_inventory': total_new_cars_in_inventory,
'total_inventory_value': total_inventory_value,
'new_car_value':new_car_value,
'used_car_value':used_car_value,
'aging_inventory_count':aging_inventory_count,
'total_new_cars_sold':total_new_cars_sold,
'total_used_cars_sold':total_used_cars_sold,
'total_cost_of_used_cars_sold': total_cost_of_used_cars_sold,
'total_revenue_from_used_cars':total_revenue_from_used_cars,
'net_profit_from_used_cars':net_profit_from_used_cars,
'total_vat_collected_from_used_cars':total_vat_collected_from_used_cars,
'total_cost_of_new_cars_sold': total_cost_of_new_cars_sold,
'total_revenue_from_new_cars':total_revenue_from_new_cars,
'net_profit_from_new_cars':net_profit_from_new_cars,
'total_vat_collected_from_new_cars':total_vat_collected_from_new_cars,
# New variables for charts
'monthly_cars_sold_json': monthly_cars_sold_json,
'monthly_revenue_json': monthly_revenue_json,
'monthly_net_profit_json': monthly_net_profit_json,
}
return render(request, 'dashboards/dealer_dashboard.html', context)
@ -580,76 +690,6 @@ class ManagerDashboard(LoginRequiredMixin, TemplateView):
# )
# return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
dealer = get_user_type(self.request)
entity = dealer.entity
qs = models.Car.objects.filter(dealer=dealer)
total_cars = qs.count()
stats = 0 # models.CarFinance.objects.filter(car__dealer=dealer).aggregate( #TODO:update_finance
# total_cost_price=Sum("cost_price"),
# total_selling_price=Sum("selling_price"),
# )
total_cost_price = stats["total_cost_price"] or 0
total_selling_price = stats["total_selling_price"] or 0
total_profit = total_selling_price - total_cost_price
new_leads = models.Lead.objects.filter(
dealer=dealer, status=models.Status.NEW
).count()
pending_leads = models.Lead.objects.filter(
dealer=dealer, status=models.Status.CONTACTED
).count()
canceled_leads = models.Lead.objects.filter(
dealer=dealer, status=models.Status.UNQUALIFIED
).count()
car_status_qs = qs.values("status").annotate(count=Count("status"))
car_status = {status: count for status, count in car_status_qs}
car_by_make_qs = qs.values("id_car_make__name").annotate(count=Count("id"))
car_by_make = list(car_by_make_qs)
context["dealer"] = dealer
context["total_activity"] = models.UserActivityLog.objects.filter(
user=dealer.user
).count()
context["total_cars"] = total_cars
context["total_reservations"] = models.CarReservation.objects.filter(
reserved_until__gte=timezone.now()
).count()
context["total_cost_price"] = total_cost_price
context["total_selling_price"] = total_selling_price
context["total_profit"] = total_profit
context["new_leads"] = new_leads
context["pending_leads"] = pending_leads
context["canceled_leads"] = canceled_leads
context["car"] = json.dumps(car_by_make)
context["available_cars"] =qs.filter(status='available').count()
context["sold_cars"] = qs.filter(status='sold').count()
context["reserved_cars"] = qs.filter(status='reserved').count()
context["hold_cars"] =qs.filter(status='hold').count()
context["damaged_cars"] = qs.filter(status='damaged').count()
context["transfer_cars"] = qs.filter(status='transfer').count()
context["present_inventory_count"]=total_cars-context["sold_cars"]-context["damaged_cars"]
cars_sold=qs.filter(status='sold')
# cars_sold.aggregate(total_inventory_value=sum())
context["customers"] = entity.get_customers().count()
context["staff"] = models.Staff.objects.filter(dealer=dealer).count()
context["total_leads"] = models.Lead.objects.filter(dealer=dealer).count()
context["invoices"] = entity.get_invoices().count()
context["estimates"] = entity.get_estimates().count()
context["purchase_orders"] = entity.get_purchase_orders().count()
return context

View File

@ -152,8 +152,8 @@
<div class="card h-100 shadow-sm border-0">
<div class="card-body d-flex flex-column justify-content-between p-4">
<div>
<p class="text-uppercase text-muted fw-bold small mb-1">Total Expense</p>
<h4 class="fw-bolder text-danger mb-3">$1.25M</h4>
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Total Expense" %}</p>
<h4 class="fw-bolder text-danger mb-3">{{total_expenses|floatformat:2}}<span class="icon-saudi_riyal"></span></h4>
</div>
<span class="badge bg-danger-subtle text-danger fw-bold p-2 rounded-pill d-inline-flex align-self-start">
-2% from last month
@ -165,8 +165,8 @@
<div class="card h-100 shadow-sm border-0">
<div class="card-body d-flex flex-column justify-content-between p-4">
<div>
<p class="text-uppercase text-muted fw-bold small mb-1">Gross Profit</p>
<h4 class="fw-bolder text-info mb-3">$1.25M</h4>
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Gross Profit" %}</p>
<h4 class="fw-bolder text-info mb-3">{{gross_profit|floatformat:2}}<span class="icon-saudi_riyal"></span></h4>
</div>
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
+8% from last month
@ -179,7 +179,7 @@
<div class="card h-100 shadow-sm border-0">
<div class="card-body d-flex flex-column justify-content-between p-4">
<div>
<p class="text-uppercase text-muted fw-bold small mb-1">Total VAT Collected</p>
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Total VAT Collected" %}</p>
<h4 class="fw-bolder text-primary mb-3">{{total_vat_collected|floatformat:2}}<span class="icon-saudi_riyal"></span></h4>
</div>
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
@ -201,8 +201,135 @@
</div>
</div>
</div>
</div>
<div class="col-sm-6 col-md-4 col-lg-3">
<div class="card h-100 shadow-sm border-0">
<div class="card-body d-flex flex-column justify-content-between p-4">
<div>
<p class="text-uppercase text-muted fw-bold small mb-1">New Cars Sold</p>
<h4 class="fw-bolder text-success mb-3">{{ total_cars_sold }}</h4>
</div>
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
+5 units from last month
</span>
</div>
</div>
</div>
<div class="col-sm-6 col-md-4 col-lg-3">
<div class="card h-100 shadow-sm border-0">
<div class="card-body d-flex flex-column justify-content-between p-4">
<div>
<p class="text-uppercase text-muted fw-bold small mb-1">Used Cars Sold</p>
<h4 class="fw-bolder text-success mb-3">{{ total_cars_sold }}</h4>
</div>
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
+5 units from last month
</span>
</div>
</div>
</div>
<div class="col-sm-6 col-md-4 col-lg-3">
<div class="card h-100 shadow-sm border-0">
<div class="card-body d-flex flex-column justify-content-between p-4">
<div>
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans 'Inventory Value '%}</p>
<h4 class="fw-bolder text-success mb-3">{{ total_inventory_value|floatformat:2 }}</h4>
</div>
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
+8% from last month
</span>
</div>
</div>
</div>
<div class="col-sm-6 col-md-4 col-lg-3">
<div class="card h-100 shadow-sm border-0">
<div class="card-body d-flex flex-column justify-content-between p-4">
<div>
<p class="text-uppercase text-muted fw-bold small mb-1">Total Cars in Inventory</p>
<h4 class="fw-bolder text-success mb-3">{{ total_cars_in_inventory }}</h4>
</div>
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
+5 units from last month
</span>
</div>
</div>
</div>
<div class="col-sm-6 col-md-4 col-lg-3">
<div class="card h-100 shadow-sm border-0">
<div class="card-body d-flex flex-column justify-content-between p-4">
<div>
<p class="text-uppercase text-muted fw-bold small mb-1">Total Old Cars in Inventory</p>
<h4 class="fw-bolder text-success mb-3">{{ total_old_cars_in_inventory}}</h4>
</div>
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
+5 units from last month
</span>
</div>
</div>
</div>
<div class="col-sm-6 col-md-4 col-lg-3">
<div class="card h-100 shadow-sm border-0">
<div class="card-body d-flex flex-column justify-content-between p-4">
<div>
<p class="text-uppercase text-muted fw-bold small mb-1">Total New Cars in Inventory</p>
<h4 class="fw-bolder text-success mb-3">{{ total_new_cars_in_inventory }}</h4>
</div>
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
+5 units from last month
</span>
</div>
</div>
</div>
<div class="col-sm-6 col-md-4 col-lg-3">
<div class="card h-100 shadow-sm border-0">
<div class="card-body d-flex flex-column justify-content-between p-4">
<div>
<p class="text-uppercase text-muted fw-bold small mb-1">New Cars Value</p>
<h4 class="fw-bolder text-success mb-3">{{ new_car_value }}</h4>
</div>
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
+8% from last month
</span>
</div>
</div>
</div>
<div class="col-sm-6 col-md-4 col-lg-3">
<div class="card h-100 shadow-sm border-0">
<div class="card-body d-flex flex-column justify-content-between p-4">
<div>
<p class="text-uppercase text-muted fw-bold small mb-1">Old Cars Value</p>
<h4 class="fw-bolder text-success mb-3">{{ old_car_value }}</h4>
</div>
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
+8% from last month
</span>
</div>
</div>
</div>
<div class="col-sm-6 col-md-4 col-lg-3">
<div class="card h-100 shadow-sm border-0">
<div class="card-body d-flex flex-column justify-content-between p-4">
<div>
<p class="text-uppercase text-muted fw-bold small mb-1">Aging Inventory</p>
<h4 class="fw-bolder text-success mb-3">{{ aging_inventory_count}}</h4>
</div>
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
+5 units from last month
</span>
</div>
</div>
</div>
</div>
<!--charts-->
<div class="row g-4">
<div class="col-lg-8">
<div class="card h-100 shadow-sm border-0">
@ -225,6 +352,7 @@
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
@ -247,7 +375,7 @@
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
datasets: [{
label: 'Total Cars Sold',
data: [2, 3, 10, 4, 30, 12, 8, 9, 20, 12, 15, 35],
data: {{monthly_cars_sold_json|safe}},
backgroundColor: primaryColor,
borderColor: primaryColor,
borderWidth: 1
@ -282,7 +410,7 @@
datasets: [
{
label: 'Monthly Revenue',
data: [120000, 150000, 130000, 180000, 200000, 175000, 190000, 220000, 210000, 250000, 240000, 280000],
data: {{monthly_revenue_json|safe}},
borderColor: primaryColor,
backgroundColor: 'rgba(114, 73, 182, 0.1)', // Using primaryColor with transparency
tension: 0.4,
@ -293,7 +421,7 @@
},
{
label: 'Monthly Net Profit',
data: [25000, 35000, 28000, 40000, 45000, 38000, 42000, 50000, 48000, 55000, 52000, 60000],
data: {{monthly_net_profit_json|safe}},
borderColor: successColor,
backgroundColor: 'rgba(0, 208, 116, 0.1)', // Using successColor with transparency
tension: 0.4,
@ -324,7 +452,7 @@
label += ': ';
}
if (context.parsed.y !== null) {
label += new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(context.parsed.y);
label += new Intl.NumberFormat('en-US', { style: 'currency', currency: 'SAR' }).format(context.parsed.y);
}
return label;
}