diff --git a/inventory/signals.py b/inventory/signals.py index aa3dbf5e..b7a4dbf1 100644 --- a/inventory/signals.py +++ b/inventory/signals.py @@ -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. diff --git a/inventory/views.py b/inventory/views.py index 7fe42acd..b1bfb665 100644 --- a/inventory/views.py +++ b/inventory/views.py @@ -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: @@ -512,6 +520,25 @@ def DealerDashboard(request): end_date = today_local 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 @@ -519,35 +546,118 @@ def DealerDashboard(request): 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 - total_discount = sum([car.discount for car in cars_sold_filtered]) + # 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,77 +690,7 @@ 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 - + diff --git a/templates/dashboards/dealer_dashboard.html b/templates/dashboards/dealer_dashboard.html index 93e2a2c0..3a8caaf8 100644 --- a/templates/dashboards/dealer_dashboard.html +++ b/templates/dashboards/dealer_dashboard.html @@ -152,8 +152,8 @@
Total Expense
-{% trans "Total Expense" %}
+Gross Profit
-{% trans "Gross Profit" %}
+Total VAT Collected
+{% trans "Total VAT Collected" %}
New Cars Sold
+Used Cars Sold
+{% trans 'Inventory Value '%}
+Total Cars in Inventory
+Total Old Cars in Inventory
+Total New Cars in Inventory
+New Cars Value
+Old Cars Value
+Aging Inventory
+