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/urls.py b/inventory/urls.py index 6124a486..f0a92d2d 100644 --- a/inventory/urls.py +++ b/inventory/urls.py @@ -42,14 +42,16 @@ urlpatterns = [ ), - #dashboards - path("dashboards/dealer/", views.DealerDashboard,name="dealer_dashboard"), - path( "dashboards/manager/", views.ManagerDashboard.as_view(),name="manager_dashboard"), - path("dashboards/sales/", views.SalesDashboard.as_view(), name="sales_dashboard"), - path("dashboards/accountant/", views.AccountantDashboard.as_view(), name="accountant_dashboard"), - path("dashboards/inventory/", views.InventoryDashboard.as_view(), name="inventory_dashboard"), - + #dashboards for manager, dealer, inventory and accounatant + path("dashboards//general/", views.general_dashboard,name="general_dashboard"), + #dashboard for sales + path("dashboards//sales/", views.sales_dashboard, name="sales_dashboard"), + path( + "/cars/aging-inventory/list", + views.aging_inventory_list_view, + name="aging_inventory_list", + ), path("cars/inventory/table/", views.CarListViewTable.as_view(), name="car_table"), path("export/format/", TableExport.export, name="export"), # Dealer URLs diff --git a/inventory/views.py b/inventory/views.py index 7fe42acd..996b0ad9 100644 --- a/inventory/views.py +++ b/inventory/views.py @@ -52,6 +52,7 @@ from django.forms import CharField, HiddenInput, ValidationError from django.shortcuts import HttpResponse from django.db.models import Sum, F, Count +from django.db.models.functions import ExtractMonth from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.contrib.auth.models import User, Group from django.db.models import Value @@ -396,482 +397,426 @@ class TestView(TemplateView): template_name = "inventory/cars_list_api.html" -# class DealerDashboard(LoginRequiredMixin, TemplateView): -# """ -# ManagerDashboard class is a view handling the dashboard for a manager. - -# Provides functionality to manage and view various statistics and data specific -# to the dealer associated with the authenticated manager. It uses a specific -# template and ensures authentication before granting access. The class -# aggregates data about cars, leads, financial statistics, and other related -# business information for display in the manager's dashboard. - -# :ivar template_name: Path to the template used for rendering the manager's dashboard. -# :type template_name: str -# """ - -# template_name = "dashboards/dealer_dashbaord.html" - -# # def dispatch(self, request, *args, **kwargs): -# # if not request.user.is_authenticated: -# # return redirect("welcome") -# # if not getattr(request.user, "dealer", False): -# # return HttpResponseForbidden( -# # "You are not authorized to view this dashboard." -# # ) -# # 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 = models.CarFinance.objects.filter(car__dealer=dealer).aggregate( -# # 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 - - - -def DealerDashboard(request): - dealer = request.dealer - cars_sold = models.Car.objects.filter(dealer=dealer, status='sold') - +@login_required +def general_dashboard(request,dealer_slug): + """ + Renders the dealer dashboard with key performance indicators and chart data. + """ + dealer = get_object_or_404(models.Dealer,slug=dealer_slug) + vat = models.VatRate.objects.filter(dealer=dealer,is_active=True).first() + VAT_RATE=vat.rate today_local = timezone.localdate() + + # ---------------------------------------------------- + # 1. Date Filtering + # ---------------------------------------------------- start_date_str = request.GET.get('start_date') end_date_str = request.GET.get('end_date') - if not start_date_str: - start_date = today_local - timedelta(days=30) - else: + if start_date_str and end_date_str: start_date = timezone.datetime.strptime(start_date_str, '%Y-%m-%d').date() - - if not end_date_str: - end_date = today_local - else: end_date = timezone.datetime.strptime(end_date_str, '%Y-%m-%d').date() + else: + start_date = today_local - timedelta(days=30) + end_date = today_local + # ---------------------------------------------------- + # 2. Inventory KPIs + # ---------------------------------------------------- + active_cars = models.Car.objects.filter(dealer=dealer).exclude(status='sold') - # The database query will automatically be handled in a timezone-aware manner - cars_sold_filtered = cars_sold.filter( + total_cars_in_inventory = active_cars.count() + total_inventory_value = active_cars.aggregate(total=Sum('cost_price'))['total'] or 0 + new_cars_qs = active_cars.filter(stock_type='new') + total_new_cars_in_inventory = new_cars_qs.count() + new_car_value = new_cars_qs.aggregate(total=Sum('cost_price'))['total'] or 0 + used_cars_qs = active_cars.filter(stock_type='used') + total_used_cars_in_inventory = used_cars_qs.count() + used_car_value = used_cars_qs.aggregate(total=Sum('cost_price'))['total'] or 0 + + aging_threshold_days = 60 + aging_inventory_count = active_cars.filter(receiving_date__date__lte=today_local - timedelta(days=aging_threshold_days)).count() + + # ---------------------------------------------------- + # 3. Sales KPIs (filtered by date) + # ---------------------------------------------------- + cars_sold_filtered = models.Car.objects.filter( + dealer=dealer, + status='sold', 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]) + + # General sales KPIs + total_cars_sold = cars_sold_filtered.count() + total_cost_of_cars_sold = cars_sold_filtered.aggregate(total=Sum('cost_price'))['total'] or 0 + total_revenue_from_cars = cars_sold_filtered.aggregate( + total=Sum(F('selling_price') - F('discount_amount')) + )['total'] or 0 + + # Correct calculation for VAT based on the selling price + total_vat_collected_from_cars = cars_sold_filtered.aggregate( + total=Sum(F('selling_price') * VAT_RATE) + )['total'] or 0 + + net_profit_from_cars = total_revenue_from_cars - total_cost_of_cars_sold + total_discount = cars_sold_filtered.aggregate(total=Sum('discount_amount'))['total'] or 0 + + # Sales breakdown by type + new_cars_sold = cars_sold_filtered.filter(stock_type='new') + total_new_cars_sold = new_cars_sold.count() + total_cost_of_new_cars_sold = new_cars_sold.aggregate(total=Sum('cost_price'))['total'] or 0 + total_revenue_from_new_cars = new_cars_sold.aggregate( + total=Sum(F('selling_price') - F('discount_amount')) + )['total'] or 0 + net_profit_from_new_cars = total_revenue_from_new_cars - total_cost_of_new_cars_sold + total_vat_collected_from_new_cars = new_cars_sold.aggregate( + total=Sum(F('selling_price') * VAT_RATE) + )['total'] or 0 + + used_cars_sold = cars_sold_filtered.filter(stock_type='used') + total_used_cars_sold = used_cars_sold.count() + total_cost_of_used_cars_sold = used_cars_sold.aggregate(total=Sum('cost_price'))['total'] or 0 + total_revenue_from_used_cars = used_cars_sold.aggregate( + total=Sum(F('selling_price') - F('discount_amount')) + )['total'] or 0 + net_profit_from_used_cars = total_revenue_from_used_cars - total_cost_of_used_cars_sold + total_vat_collected_from_used_cars = used_cars_sold.aggregate( + total=Sum(F('selling_price') * VAT_RATE) + )['total'] or 0 + + # Service & Overall KPIs + 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 + + expenses = models.ItemModel.objects.filter(entity__admin__dealer=dealer, item_role='expense') + total_expenses = expenses.aggregate(total=Sum('default_amount'))['total'] or 0 + gross_profit = net_profit_from_cars - total_expenses + + # ---------------------------------------------------- + # 4. Chart Data Aggregation + # ---------------------------------------------------- + monthly_sales_data = cars_sold_filtered.annotate( + month=ExtractMonth('sold_date') + ).values('month').annotate( + total_cars=Count('pk'), + total_revenue=Sum(F('selling_price') - F('discount_amount')), + total_profit=Sum(F('selling_price') - F('discount_amount') - F('cost_price')) + ).order_by('month') + + monthly_cars_sold = [0] * 12 + monthly_revenue = [0] * 12 + monthly_net_profit = [0] * 12 + + for data in monthly_sales_data: + month_index = data['month'] - 1 + monthly_cars_sold[month_index] = data['total_cars'] + monthly_revenue[month_index] = float(data['total_revenue']) if data['total_revenue'] else 0 + monthly_net_profit[month_index] = float(data['total_profit']) if data['total_profit'] else 0 + + monthly_cars_sold_json = json.dumps(monthly_cars_sold) + monthly_revenue_json = json.dumps(monthly_revenue) + monthly_net_profit_json = json.dumps(monthly_net_profit) + + # ---------------------------------------------------- + # Sales by MAKE + # ---------------------------------------------------- + sales_by_make_data = cars_sold_filtered.values('id_car_make__name').annotate( + car_count=Count('id_car_make__name') + ).order_by('-car_count') + + sales_by_make_labels = [data['id_car_make__name'] for data in sales_by_make_data] + sales_by_make_counts = [data['car_count'] for data in sales_by_make_data] + + + + # ---------------------------------------------------- + # DATA FOR CAR SALES BY MODELS (for the new interactive chart) + # ---------------------------------------------------- + + + # Get the selected make from the URL query parameter + selected_make_sales= request.GET.get('make_sold', None) + + + # Get a list of all unique makes for the dropdown + all_makes_sold = list(cars_sold_filtered.values_list('id_car_make__name', flat=True).distinct().order_by('id_car_make__name')) + + if selected_make_sales: + # If a make is selected, filter the queryset + sales_data_by_model = cars_sold_filtered.filter( + id_car_make__name=selected_make_sales + ).values('id_car_model__name').annotate( + count=Count('id_car_model__name') + ).order_by('-count') + else: + # If no make is selected, pass an empty list or some default data + sales_data_by_model = [] + + + + + # 1. Inventory by Make (Pie Chart) + + inventory_by_make_data = active_cars.values('id_car_make__name').annotate( + car_count=Count('id_car_make__name') + ).order_by('-car_count') + + inventory_by_make_labels = [data['id_car_make__name'] for data in inventory_by_make_data] + inventory_by_make_counts = [data['car_count'] for data in inventory_by_make_data] + + # 2. Inventory by Model (Bar Chart) + selected_make_inventory = request.GET.get('make_inventory', None) + + # Get all unique makes in inventory for the dropdown + all_makes_inventory = list(active_cars.values_list('id_car_make__name', flat=True).distinct().order_by('id_car_make__name')) + + if selected_make_inventory: + inventory_data_by_model = active_cars.filter( + id_car_make__name=selected_make_inventory + ).values('id_car_model__name').annotate( + count=Count('id_car_model__name') + ).order_by('-count') + else: + # Default data + inventory_data_by_model = [] + + context = { + 'start_date': start_date, + 'end_date': end_date, + 'today': today_local, + + # Inventory KPIs + 'total_cars_in_inventory': total_cars_in_inventory, + 'total_inventory_value': total_inventory_value, + 'total_new_cars_in_inventory': total_new_cars_in_inventory, + 'total_used_cars_in_inventory': total_used_cars_in_inventory, + 'new_car_value': new_car_value, + 'used_car_value': used_car_value, + 'aging_inventory_count': aging_inventory_count, + + # Sales KPIs + '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, + + # Sales by Type + 'total_new_cars_sold': total_new_cars_sold, + 'total_used_cars_sold': total_used_cars_sold, + '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, + '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, + + # Services and Overall KPIs + '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_expenses': total_expenses, + 'gross_profit': gross_profit, + + # Chart Data + + 'monthly_cars_sold_json': monthly_cars_sold_json, + 'monthly_revenue_json': monthly_revenue_json, + 'monthly_net_profit_json': monthly_net_profit_json, + + + # Sales Chart Data + 'sales_by_make_labels_json': json.dumps(sales_by_make_labels), + 'sales_by_make_counts_json': json.dumps(sales_by_make_counts), + 'all_makes_sold': all_makes_sold, + 'selected_make_sales': selected_make_sales, + 'sales_data_by_model_json': json.dumps(list(sales_data_by_model)), + + # New Inventory Chart Data + 'inventory_by_make_labels_json': json.dumps(inventory_by_make_labels), + 'inventory_by_make_counts_json': json.dumps(inventory_by_make_counts), + 'all_makes_inventory': all_makes_inventory, + 'selected_make_inventory': selected_make_inventory, + 'inventory_data_by_model_json': json.dumps(list(inventory_data_by_model)), + + + } + + + return render(request, 'dashboards/general_dashboard.html', context) + + +@login_required +def sales_dashboard(request,dealer_slug): + dealer = get_object_or_404(models.Dealer,slug=dealer_slug) + today_local = timezone.localdate() + + # ---------------------------------------------------- + # 1. Date Filtering + # ---------------------------------------------------- + start_date_str = request.GET.get('start_date') + end_date_str = request.GET.get('end_date') + + if start_date_str and end_date_str: + start_date = timezone.datetime.strptime(start_date_str, '%Y-%m-%d').date() + end_date = timezone.datetime.strptime(end_date_str, '%Y-%m-%d').date() + else: + start_date = today_local - timedelta(days=30) + end_date = today_local + + # Filter leads by date range and dealer + leads_filtered = models.Lead.objects.filter( + dealer=dealer, + created__date__gte=start_date, + created__date__lte=end_date + ) + + + # ---------------------------------------------------- + # 2. Lead Sources Chart Logic + # ---------------------------------------------------- + # Group leads by source and count them + # This generates a list of dictionaries like [{'source': 'Showroom', 'count': 45}, ...] + lead_sources_data = leads_filtered.values('source').annotate( + count=Count('source') + ).order_by('-count') + + # Separate the labels and counts for the chart + lead_sources_labels = [item['source'] for item in lead_sources_data] + lead_sources_counts = [item['count'] for item in lead_sources_data] + + # ---------------------------------------------------- + # 2. Lead Funnel Chart Logic + # ---------------------------------------------------- + opportunity_filtered = models.Opportunity.objects.filter( + dealer=dealer, + created__date__gte=start_date, + created__date__lte=end_date + ) + opportunity_stage_data = opportunity_filtered.values('stage').annotate( + count=Count('stage') + ).order_by('-count') + # Separate the labels and counts for the chart + opportunity_stage_labels = [item['stage'] for item in opportunity_stage_data ] + opportunity_stage_counts = [item['count'] for item in opportunity_stage_data ] + + + # 2. Inventory KPIs + # ---------------------------------------------------- + active_cars = models.Car.objects.filter(dealer=dealer).exclude(status='sold') + + total_cars_in_inventory = active_cars.count() + new_cars_qs = active_cars.filter(stock_type='new') + total_new_cars_in_inventory = new_cars_qs.count() + used_cars_qs = active_cars.filter(stock_type='used') + total_used_cars_in_inventory = used_cars_qs.count() + aging_threshold_days = 60 + aging_inventory_count = active_cars.filter(receiving_date__date__lte=today_local - timedelta(days=aging_threshold_days)).count() + 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_vat_collected_from_cars': total_vat_collected_from_cars, - '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, - + 'lead_sources_labels_json': json.dumps(lead_sources_labels), + 'lead_sources_counts_json': json.dumps(lead_sources_counts), + 'opportunity_stage_labels_json': json.dumps(opportunity_stage_labels), + 'opportunity_stage_counts_json': json.dumps(opportunity_stage_counts), + + # Inventory KPIs + 'total_cars_in_inventory': total_cars_in_inventory, + 'total_new_cars_in_inventory': total_new_cars_in_inventory, + 'total_used_cars_in_inventory': total_used_cars_in_inventory, + 'aging_inventory_count': aging_inventory_count, } - return render(request, 'dashboards/dealer_dashboard.html', context) + return render(request, 'dashboards/sales_dashboard.html', context) + + +def aging_inventory_list_view(request, dealer_slug): + """ + Renders a paginated list of aging inventory for a specific dealer, with filters. + """ + dealer = get_object_or_404(models.Dealer, slug=dealer_slug) + today_local = timezone.localdate() + aging_threshold_days = 60 + + # Get filter parameters from the request + selected_make = request.GET.get('make') + selected_model = request.GET.get('model') + selected_series = request.GET.get('series') # Changed 'serie' to 'series' for consistency + selected_year = request.GET.get('year') + selected_stock_type = request.GET.get('stock_type') + + # Start with the base queryset for all aging cars. + aging_cars_queryset = models.Car.objects.filter( + dealer=dealer, + receiving_date__date__lt=today_local - timedelta(days=aging_threshold_days) + ).exclude(status='sold') + total_aging_inventory_value=aging_cars_queryset.aggregate(total=Sum('cost_price'))['total'] + # Apply filters to the queryset if they exist. Chaining is fine here. + if selected_make: + aging_cars_queryset = aging_cars_queryset.filter(id_car_make__name=selected_make) + if selected_model: + aging_cars_queryset = aging_cars_queryset.filter(id_car_model__name=selected_model) + if selected_series: + aging_cars_queryset = aging_cars_queryset.filter(id_car_series__name=selected_series) + if selected_year: + aging_cars_queryset = aging_cars_queryset.filter(id_car_year__year=selected_year) + if selected_stock_type: + aging_cars_queryset = aging_cars_queryset.filter(stock_type=selected_stock_type) + -class ManagerDashboard(LoginRequiredMixin, TemplateView): - """ - ManagerDashboard class is a view handling the dashboard for a manager. - - Provides functionality to manage and view various statistics and data specific - to the dealer associated with the authenticated manager. It uses a specific - template and ensures authentication before granting access. The class - aggregates data about cars, leads, financial statistics, and other related - business information for display in the manager's dashboard. - - :ivar template_name: Path to the template used for rendering the manager's dashboard. - :type template_name: str - """ - - template_name = "dashboards/manager_dashboard.html" - - # def dispatch(self, request, *args, **kwargs): - # if not request.user.is_authenticated: - # return redirect("welcome") - # if not getattr(request.user, "dealer", False): - # return HttpResponseForbidden( - # "You are not authorized to view this dashboard." - # ) - # 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 - - - - - -class SalesDashboard(LoginRequiredMixin, TemplateView): - """ - SalesDashboard class provides a view for the sales dashboard. - - This class is responsible for generating the context data required to - display various statistics and information on the sales dashboard. It - inherits from `LoginRequiredMixin` and `TemplateView`, ensuring only - authenticated users can access the view. The dashboard includes data - such as the total number of cars, reservations, sold percentages, - reserved percentages, and cars categorized by various statuses. The - purpose of this dashboard is to provide dealership staff an overview - of their inventory and related sales metrics. - - :ivar template_name: The name of the HTML template used for rendering - the sales dashboard. - :type template_name: str - """ - - template_name = "dashboards/sales_dashboard.html" - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - dealer = self.request.dealer - staff = getattr(self.request, "staff", None) - total_cars = models.Car.objects.filter(dealer=dealer).count() - total_reservations = models.CarReservation.objects.filter( - reserved_by=self.request.user, reserved_until__gte=timezone.now() - ).count() - - available_cars = models.Car.objects.filter( - dealer=dealer, status=models.CarStatusChoices.AVAILABLE - ).count() - sold_cars = models.Car.objects.filter( - dealer=dealer, status=models.CarStatusChoices.SOLD - ).count() - reserved_cars = models.Car.objects.filter( - dealer=dealer, status=models.CarStatusChoices.RESERVED - ).count() - hold_cars = models.Car.objects.filter( - dealer=dealer, status=models.CarStatusChoices.HOLD - ).count() - damaged_cars = models.Car.objects.filter( - dealer=dealer, status=models.CarStatusChoices.DAMAGED - ).count() - transfer_cars = models.Car.objects.filter( - dealer=dealer, status=models.CarStatusChoices.TRANSFER - ).count() - reserved_percentage = reserved_cars / total_cars * 100 - sold_percentage = sold_cars / total_cars * 100 - qs = ( - models.Car.objects.values("id_car_make__name") - .annotate(count=Count("id")) - .order_by("id_car_make__name") - ) - car_by_make = list(qs) - - context["dealer"] = dealer - context["staff"] = staff - context["total_cars"] = total_cars - context["total_reservations"] = total_reservations - context["reserved_percentage"] = reserved_percentage - context["sold_percentage"] = sold_percentage - context["available_cars"] = available_cars - context["sold_cars"] = sold_cars - context["reserved_cars"] = reserved_cars - context["hold_cars"] = hold_cars - context["damaged_cars"] = damaged_cars - context["transfer_cars"] = transfer_cars - context["car"] = json.dumps(car_by_make) - - return context - - -class InventoryDashboard(LoginRequiredMixin, TemplateView): - """ - SalesDashboard class provides a view for the sales dashboard. - - This class is responsible for generating the context data required to - display various statistics and information on the sales dashboard. It - inherits from `LoginRequiredMixin` and `TemplateView`, ensuring only - authenticated users can access the view. The dashboard includes data - such as the total number of cars, reservations, sold percentages, - reserved percentages, and cars categorized by various statuses. The - purpose of this dashboard is to provide dealership staff an overview - of their inventory and related sales metrics. - - :ivar template_name: The name of the HTML template used for rendering - the sales dashboard. - :type template_name: str - """ - - template_name = "dashboards/inventory_dashboard.html" - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - dealer = self.request.dealer - staff = getattr(self.request, "staff", None) - total_cars = models.Car.objects.filter(dealer=dealer).count() - total_reservations = models.CarReservation.objects.filter( - reserved_by=self.request.user, reserved_until__gte=timezone.now() - ).count() - - available_cars = models.Car.objects.filter( - dealer=dealer, status=models.CarStatusChoices.AVAILABLE - ).count() - sold_cars = models.Car.objects.filter( - dealer=dealer, status=models.CarStatusChoices.SOLD - ).count() - reserved_cars = models.Car.objects.filter( - dealer=dealer, status=models.CarStatusChoices.RESERVED - ).count() - hold_cars = models.Car.objects.filter( - dealer=dealer, status=models.CarStatusChoices.HOLD - ).count() - damaged_cars = models.Car.objects.filter( - dealer=dealer, status=models.CarStatusChoices.DAMAGED - ).count() - transfer_cars = models.Car.objects.filter( - dealer=dealer, status=models.CarStatusChoices.TRANSFER - ).count() - reserved_percentage = reserved_cars / total_cars * 100 - sold_percentage = sold_cars / total_cars * 100 - qs = ( - models.Car.objects.values("id_car_make__name") - .annotate(count=Count("id")) - .order_by("id_car_make__name") - ) - car_by_make = list(qs) - - context["dealer"] = dealer - context["staff"] = staff - context["total_cars"] = total_cars - context["total_reservations"] = total_reservations - context["reserved_percentage"] = reserved_percentage - context["sold_percentage"] = sold_percentage - context["available_cars"] = available_cars - context["sold_cars"] = sold_cars - context["reserved_cars"] = reserved_cars - context["hold_cars"] = hold_cars - context["damaged_cars"] = damaged_cars - context["transfer_cars"] = transfer_cars - context["car"] = json.dumps(car_by_make) - - return context - -class AccountantDashboard(LoginRequiredMixin, TemplateView): - """ - SalesDashboard class provides a view for the sales dashboard. - - This class is responsible for generating the context data required to - display various statistics and information on the sales dashboard. It - inherits from `LoginRequiredMixin` and `TemplateView`, ensuring only - authenticated users can access the view. The dashboard includes data - such as the total number of cars, reservations, sold percentages, - reserved percentages, and cars categorized by various statuses. The - purpose of this dashboard is to provide dealership staff an overview - of their inventory and related sales metrics. - - :ivar template_name: The name of the HTML template used for rendering - the sales dashboard. - :type template_name: str - """ - - template_name = "dashboards/accountant_dashboard.html" - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - dealer = self.request.dealer - staff = getattr(self.request, "staff", None) - total_cars = models.Car.objects.filter(dealer=dealer).count() - total_reservations = models.CarReservation.objects.filter( - reserved_by=self.request.user, reserved_until__gte=timezone.now() - ).count() - - available_cars = models.Car.objects.filter( - dealer=dealer, status=models.CarStatusChoices.AVAILABLE - ).count() - sold_cars = models.Car.objects.filter( - dealer=dealer, status=models.CarStatusChoices.SOLD - ).count() - reserved_cars = models.Car.objects.filter( - dealer=dealer, status=models.CarStatusChoices.RESERVED - ).count() - hold_cars = models.Car.objects.filter( - dealer=dealer, status=models.CarStatusChoices.HOLD - ).count() - damaged_cars = models.Car.objects.filter( - dealer=dealer, status=models.CarStatusChoices.DAMAGED - ).count() - transfer_cars = models.Car.objects.filter( - dealer=dealer, status=models.CarStatusChoices.TRANSFER - ).count() - reserved_percentage = reserved_cars / total_cars * 100 - sold_percentage = sold_cars / total_cars * 100 - qs = ( - models.Car.objects.values("id_car_make__name") - .annotate(count=Count("id")) - .order_by("id_car_make__name") - ) - car_by_make = list(qs) - - context["dealer"] = dealer - context["staff"] = staff - context["total_cars"] = total_cars - context["total_reservations"] = total_reservations - context["reserved_percentage"] = reserved_percentage - context["sold_percentage"] = sold_percentage - context["available_cars"] = available_cars - context["sold_cars"] = sold_cars - context["reserved_cars"] = reserved_cars - context["hold_cars"] = hold_cars - context["damaged_cars"] = damaged_cars - context["transfer_cars"] = transfer_cars - context["car"] = json.dumps(car_by_make) - - return context + # Get distinct values for filter dropdowns based on the initial, unfiltered aging cars queryset. + # This ensures all possible filter options are always available. + aging_base_queryset = models.Car.objects.filter( + dealer=dealer, + receiving_date__date__lt=today_local - timedelta(days=aging_threshold_days) + ).exclude(status='sold') + + all_makes = aging_base_queryset.values_list('id_car_make__name', flat=True).distinct().order_by('id_car_make__name') + all_models = aging_base_queryset.values_list('id_car_model__name', flat=True).distinct().order_by('id_car_model__name') + all_series = aging_base_queryset.values_list('id_car_serie__name', flat=True).distinct().order_by('id_car_serie__name') + all_stock_types = aging_base_queryset.values_list('stock_type', flat=True).distinct().order_by('stock_type') + all_years = aging_base_queryset.values_list('year', flat=True).distinct().order_by('-year') + + # + # Set up pagination + paginator = Paginator(aging_cars_queryset, 10) + page_number = request.GET.get('page') + page_obj = paginator.get_page(page_number) + + # Iterate only on the cars for the current page to add the age attribute. + for car in page_obj.object_list: + car.age_in_days = (today_local - car.receiving_date.date()).days + context = { + "is_paginated": page_obj.has_other_pages, + "cars": page_obj.object_list, + 'selected_make': selected_make, + 'selected_model': selected_model, + 'selected_series': selected_series, # Corrected variable name + 'selected_year': selected_year, + 'selected_stock_type': selected_stock_type, + 'all_makes': all_makes, + 'all_models': all_models, + 'all_series': all_series, + 'all_stock_types': all_stock_types, + 'all_years': all_years, + 'total_aging_inventory_value':total_aging_inventory_value + + } + + return render(request, 'dashboards/aging_inventory_list.html', context) def terms_and_privacy(request): return render(request, "terms_and_privacy.html") @@ -7094,14 +7039,14 @@ def send_lead_email(request, dealer_slug, slug, email_pk=None): لقد أنشأنا ملفاً شخصياً لك في نظامنا لتتبع تفضيلاتك والسيارات التي تهتم بها. سنتواصل معك قريباً للمتابعة والإجابة على أي أسئلة أخرى قد تكون لديك. -في هذه الأثناء، لا تتردد في الاتصال بنا مباشرة على {lead.dealer.phone_number} أو زيارتنا مرة أخرى في أي وقت يناسبك. +في هذه الأثناء، لا تتردد في الاتصال بنا مباشرة أو زيارتنا مرة أخرى في أي وقت يناسبك. نتطلع إلى مساعدتك في العثور على سيارتك القادمة! تحياتي، {lead.dealer.arabic_name} {lead.dealer.address} -{lead.dealer.phone_number} +{lead.dealer.phone_number} ----- Dear {lead.full_name}, @@ -7182,7 +7127,7 @@ class OpportunityCreateView( dealer = get_object_or_404(models.Dealer, slug=self.kwargs.get("dealer_slug")) form = super().get_form(form_class) form.fields["car"].queryset = models.Car.objects.filter( - dealer=dealer, status="available", finances__marked_price__gt=0 + dealer=dealer, status="available", marked_price__gt=0 ) if self.request.is_dealer: form.fields["lead"].queryset = models.Lead.objects.filter( @@ -7241,7 +7186,7 @@ class OpportunityUpdateView( dealer = get_object_or_404(models.Dealer, slug=self.kwargs.get("dealer_slug")) staff = getattr(self.request.user, "staff", None) form.fields["car"].queryset = models.Car.objects.filter( - dealer=dealer, status="available", finances__marked_price__gt=0 + dealer=dealer, status="available",marked_price__gt=0 ) form.fields["lead"].queryset = models.Lead.objects.filter( dealer=dealer, staff=staff @@ -11114,13 +11059,6 @@ def purchase_report_csv_export(request,dealer_slug): ]) return response -# @login_required -# def car_sale_report_view(request,dealer_slug): -# dealer = get_object_or_404(models.Dealer, slug=dealer_slug) -# cars_sold = models.Car.objects.filter(dealer=dealer,status='sold') -# current_time = timezone.now().strftime("%Y-%m-%d %H:%M:%S") -# context={'cars_sold':cars_sold,'current_time':current_time } -# return render(request,'ledger/reports/car_sale_report.html',context) @login_required @@ -11148,15 +11086,6 @@ def car_sale_report_view(request, dealer_slug): if selected_stock_type: cars_sold = cars_sold.filter(stock_type=selected_stock_type) - # Get distinct values for filter dropdowns - makes = models.Car.objects.filter(dealer=dealer, status='sold').values_list('id_car_make__name', flat=True).distinct() - models_qs = models.Car.objects.filter(dealer=dealer, status='sold').values_list('id_car_model__name', flat=True).distinct() - - series = models.Car.objects.filter(dealer=dealer, status='sold').values_list('id_car_serie__name', flat=True).distinct() - stock_types=models.Car.objects.filter(dealer=dealer, status='sold').values_list('stock_type', flat=True).distinct() - years = models.Car.objects.filter(dealer=dealer, status='sold').values_list('year', flat=True).distinct().order_by('-year') - - # # Calculate summary data for the filtered results total_cars_sold=cars_sold.count() @@ -11171,6 +11100,15 @@ def car_sale_report_view(request, dealer_slug): current_time = timezone.now().strftime("%Y-%m-%d %H:%M:%S") + # Get distinct values for filter dropdowns + base_sold_cars_queryset = models.Car.objects.filter(dealer=dealer, status='sold') + makes =base_sold_cars_queryset.values_list('id_car_make__name', flat=True).distinct() + models_qs =base_sold_cars_queryset.values_list('id_car_model__name', flat=True).distinct() + + series =base_sold_cars_queryset.values_list('id_car_serie__name', flat=True).distinct() + stock_types=base_sold_cars_queryset.values_list('stock_type', flat=True).distinct() + years = base_sold_cars_queryset.values_list('year', flat=True).distinct().order_by('-year') + context = { 'cars_sold': cars_sold, 'total_cars_sold':total_cars_sold, @@ -11198,7 +11136,6 @@ def car_sale_report_view(request, dealer_slug): return render(request, 'ledger/reports/car_sale_report.html', context) - @login_required def car_sale_report_csv_export(request, dealer_slug): response = HttpResponse(content_type='text/csv') diff --git a/static/images/customers/Gemini_Generated_Image_wf3w0uwf3w0uwf3w_1.png b/static/images/customers/Gemini_Generated_Image_wf3w0uwf3w0uwf3w_1.png new file mode 100644 index 00000000..934c1be1 Binary files /dev/null and b/static/images/customers/Gemini_Generated_Image_wf3w0uwf3w0uwf3w_1.png differ diff --git a/static/images/customers/istockphoto-1188035960-612x612.jpg b/static/images/customers/istockphoto-1188035960-612x612.jpg new file mode 100644 index 00000000..ede81f81 Binary files /dev/null and b/static/images/customers/istockphoto-1188035960-612x612.jpg differ diff --git a/templates/dashboards/accountant_dashboard.html b/templates/dashboards/accountant_dashboard.html deleted file mode 100644 index 0bf03d0f..00000000 --- a/templates/dashboards/accountant_dashboard.html +++ /dev/null @@ -1,218 +0,0 @@ -{% extends 'base.html' %} -{% block content %} - -
-
-

Accountant Dashboard

- -
- -
-
-
-
-
-

Total Revenue

-

$1.25M

-
- - +8% from last month - -
-
-
-
-
-
-
-

Net Profit

-

$1.25M

-
- - +8% from last month - -
-
-
-
-
-
-
-

Gross Profit

-

$1.25M

-
- - +8% from last month - -
-
-
-
-
-
-
-

Total Expense

-

$1.25M

-
- - +8% from last month - -
-
-
-
- -

Breakdown by Vehicle Type

-
-
-
-

New Cars

-

$1.25M

-

Total Revenue

-
-
-
-
-

Used Cars

-

$0.75M

-

Total Revenue

-
-
-
-
-

New Cars

-

$0.25M

-

Net Profit

-
-
-
-
-

Used Cars

-

$0.15M

-

Net Profit

-
-
-
- -
-
-
-
-
Monthly Revenue & Profit
-
-
- -
-
-
-
-
- - -{% endblock %} - -{% block customJS %} - -{% endblock %} \ No newline at end of file diff --git a/templates/dashboards/aging_inventory_list.html b/templates/dashboards/aging_inventory_list.html new file mode 100644 index 00000000..f2619a01 --- /dev/null +++ b/templates/dashboards/aging_inventory_list.html @@ -0,0 +1,124 @@ +{% extends 'base.html' %} +{% load i18n %} +{% load tenhal_tag %} +{% block content %} +
+
+

+ {% trans "Aging Inventory" %} + +

+

{% trans "Aging Inventory Total" %} :: {{total_aging_inventory_value}}

+

{% trans "Cars in inventory for more than 60 days." %}

+ +
+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+
+ + {% if is_paginated %} +
+ {% trans "Page" %} {{ page_obj.number }} {% trans "of" %} {{ page_obj.paginator.num_pages }} + {% trans "Total Aging Cars:" %} {{ page_obj.paginator.count }} +
+ {% endif %} + + {% if cars %} +
+ {% for car in cars %} +
+
+
+
+   {{ car.id_car_make.name }}  {{ car.id_car_model.name }}  {{ car.id_car_serie.name }}  {{ car.year}} +
+

+ {% trans "VIN:" %} {{ car.vin }} +

+

+ {% trans "Age:" %} + {{ car.age_in_days }} {% trans "days" %} +

+

+ {% trans "Acquisition Date:" %} {{ car.receiving_date|date:"F j, Y" }} +

+ +
+
+
+ {% endfor %} +
+ {% else %} + + {% endif %} +
+
+ {% if is_paginated %} + {% include 'partials/pagination.html' %} + {% endif %} +
+
+
+{% endblock content %} \ No newline at end of file diff --git a/templates/dashboards/chart.html b/templates/dashboards/chart.html new file mode 100644 index 00000000..b831587a --- /dev/null +++ b/templates/dashboards/chart.html @@ -0,0 +1,109 @@ +{% load i18n %} +{% if request.is_dealer or request.is_manager or request.is_accountant %} +

+ {% blocktrans with start_date=start_date|date:"F j, Y" end_date=end_date|date:"F j, Y" %} + Monthly Performance Trends ({{ start_date }} - {{ end_date }}) + {% endblocktrans %} +

+ +
+
+
+
+
{% trans "Monthly Revenue & Profit" %}
+
+
+ +
+
+
+
+
+
+
{% trans "Monthly Cars Sold" %}
+
+
+ +
+
+
+
+ +
+
+
+
+
{% trans "Sales by Make" %}
+
+
+ +
+
+
+ +
+
+
+
{% trans "Models Sold" %}
+
+
+ + +
+ + + +
+
+
+ +
+
+
+
+{% endif %} + +{% if request.is_dealer or request.is_manager or request.is_inventory %} +

{% trans "Inventory Trends" %}

+ +
+
+
+
+
{% trans "Inventory by Make" %}
+
+
+ +
+
+
+ +
+
+
+
{% trans "Models in Inventory" %}
+
+
+ + +
+ +
+
+
+ +
+
+
+
+{% endif %} \ No newline at end of file diff --git a/templates/dashboards/crm.html b/templates/dashboards/crm.html deleted file mode 100644 index d89bf990..00000000 --- a/templates/dashboards/crm.html +++ /dev/null @@ -1,508 +0,0 @@ -{% extends 'base.html' %} -{% load i18n static %} -{% block content %} -
-
-
-

CRM Dashboard

-
Check your business growth in one place
-
-
-
-
-
-
-
-
- -
-

Outgoing call

-
-

- 3 Leads Today -

-
-
- +24.5% -

Than Yesterday

-
-
-
-
-
-
-
-
-
-
-
-
- -
-

Outgoing meeting

-
-

- 12 This Week -

-
-
- +24.5% -

Than last week

-
-
-
-
-
-
-
-
Top 5 Lead Sources
-
    -
  • -
    - 1.None (65) -
    -
  • -
  • -
    - 2.Online Store(74) -
    -
  • -
  • -
    - 3. Advertisement(32) -
    -
  • -
  • -
    - 4. Seminar Partner(25) -
    -
  • -
  • -
    - 5. Partner(23) -
    -
  • -
-
-
-
-
-
-

Contacts Created

-

Payment received across all channels

-
-
-
-
-
-

New Contacts by Source

-

Payment received across all channels

-
-
-
-
- Organic -
-

80

-
-
-
-
-
- Paid Search -
-

65

-
-
-
-
-
- Direct -
-

40

-
-
-
-
-
- Social -
-

220

-
-
-
-
-
- Referrals -
-

120

-
-
-
-
-
- Others -
-

35

-
-
-
-
-
-
-
-
-

-
-
-
-
-
-
-
-

New Users & Leads

-

Payment received across all channels

-
-
-
-
- -

- New Users :42 -

- +24.5% -
-
-
-
-
-
-
- -

- New Leads :45 -

- +30.5% -
-
-
-
-
-
-
-
-
-
-

Ad Clicks

-

Check effectiveness of your ads

-
-
- -
-
-
-
-
-
-
-

- Deal Forecast- $90,303 -

-

Show what you offer here

-
-
- -
-
-
-
-

$21.0k

-

$3.4k

-

$15.1k

-

$4.6k

-
-
-
-
-
-
-
-
-

Deal Forecast by Owner

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Contact -
- Appointment -
-
-
- Qualified -
-
-
- Closed Won -
-
-
- Contact Sent -
-
- Carrie Anne - - 1000 - - $1256 - - $1200 - - $1200 -
- Milind Mikuja - - 558 - - $2531 - - $2200 - - $2200 -
- Stanley Drinkwater - - 1100 - - $100 - - $100 - - $100 -
- Josef Stravinsky - - 856 - - $326 - - $265 - - $265 -
- Roy Anderson - - 1200 - - $1452 - - $865 - - $865 -
- Oscar Wilde - - 1020 - - $950 - - $1000 - - $800 -
- - 4,744 - - $5,665 - - $4630 - - $4630 -
-
-
-
-
-
-
-
-
-
-

Lead Conversion

-

Stages of deals & conversion

-
-
-
-
-
-
-
-

Revenue Target

-

Country-wise target fulfilment

-
-
-
-
-
-
-
-
-
-

- Thank you for creating with Phoenix| -
- 2024 ©Themewagon -

-
-
-

v1.20.1

-
-
-
-
-{% endblock %} diff --git a/templates/dashboards/dealer_dashboard.html b/templates/dashboards/dealer_dashboard.html deleted file mode 100644 index 93e2a2c0..00000000 --- a/templates/dashboards/dealer_dashboard.html +++ /dev/null @@ -1,349 +0,0 @@ -{% extends 'base.html' %} -{% load i18n %} -{% load tenhal_tag %} -{% block title %} - {{ _("Dealership Dashboard") |capfirst }} -{% endblock title %} -{% block content%} -
-
-

Business Health Dashboard

-
-
-
- - -
-
- - -
-
- -
-
-
-
- -
-
-
-
-
-

Total Cost of Cars Sold

-

{{total_cost_of_cars_sold|floatformat:2}}

-
- - +8% from last month - -
-
-
-
-
-
-
-

Total Revenue from cars

-

{{total_revenue_from_cars|floatformat:2}}

-
- - +8% from last month - -
-
-
-
-
-
-
-

Net Profit From Cars

-

{{net_profit_from_cars|floatformat:2}}

-
- - +8% from last month - -
-
-
-
-
-
-
-

Total Discount on cars

-

{{total_discount_on_cars|floatformat:2}}

-
- - +8% from last month - -
-
-
-
-
-
-
-

Total VAT collected from cars

-

{{total_vat_collected_from_cars|floatformat:2}}

-
- - +8% from last month - -
-
-
-
-
-
-
-

Total Revenue from Services

-

{{total_revenue_from_services|floatformat:2}}

-
- - +8% from last month - -
-
-
- -
-
-
-
-

Total VAT collected from Services

-

{{total_vat_collected_from_services|floatformat:2}}

-
- - +8% from last month - -
-
-
-
-
-
-
-

{% trans "Total Revenue Generated" %}

-

{{total_revenue_generated|floatformat:2}}

-
- - +8% from last month - -
-
-
- -
-
-
-
-

{% trans "Total VAT Collected" %}

-

{{total_vat_collected|floatformat:2}}

-
- - +8% from last month - -
-
-
- -
-
-
-
-

Total Expense

-

$1.25M

-
- - -2% from last month - -
-
-
-
-
-
-
-

Gross Profit

-

$1.25M

-
- - +8% from last month - -
-
-
- -
-
-
-
-

Total VAT Collected

-

{{total_vat_collected|floatformat:2}}

-
- - +8% from last month - -
-
-
-
-
-
-
-

Total Cars Sold

-

{{ total_cars_sold }}

-
- - +5 units from last month - -
-
-
-
- -
-
-
-
-
Monthly Revenue & Profit
-
-
- -
-
-
-
-
-
-
Monthly Cars Sold
-
-
- -
-
-
-
-
- - -{% endblock content %} - - -{% block customJS %} - -{% endblock %} \ No newline at end of file diff --git a/templates/dashboards/financial_data_cards.html b/templates/dashboards/financial_data_cards.html new file mode 100644 index 00000000..3a27bda3 --- /dev/null +++ b/templates/dashboards/financial_data_cards.html @@ -0,0 +1,266 @@ +{% load i18n %} +{% if request.is_dealer or request.is_manager or request.is_accountant %} +

+ {% blocktrans with start_date=start_date|date:"F j, Y" end_date=end_date|date:"F j, Y" %} + Sales KPIs ({{ start_date }} - {{ end_date }}) + {% endblocktrans %} +

+
+
+
+
+

{% trans "Total Cars Sold" %}

+

{{ total_cars_sold }}

+
+
+
+
+
+
+

{% trans "Total Revenue from Cars" %}

+

{{ total_revenue_from_cars|floatformat:2 }}

+
+
+
+
+
+
+

{% trans "Net Profit from Cars" %}

+

{{ net_profit_from_cars|floatformat:2 }}

+
+
+
+
+
+
+

{% trans "Total Discount on Cars" %}

+

{{ total_discount_on_cars|floatformat:2 }}

+
+
+
+
+
+
+

{% trans "Total Cost of Cars Sold" %}

+

{{ total_cost_of_cars_sold|floatformat:2 }}

+
+
+
+
+
+
+

{% trans "Total VAT from Cars" %}

+

{{ total_vat_collected_from_cars|floatformat:2 }}

+
+
+
+
+ +

{% trans "Sales of New Cars" %}

+
+
+
+
+

{% trans "New Cars Sold" %}

+

{{ total_new_cars_sold }}

+
+
+
+
+
+
+

{% trans "New Cars Revenue" %}

+

{{ total_revenue_from_new_cars|floatformat:2 }}

+
+
+
+
+
+
+

{% trans "New Cars Net Profit" %}

+

{{ net_profit_from_new_cars|floatformat:2 }}

+
+
+
+
+
+
+

{% trans "New Cars VAT" %}

+

{{ total_vat_collected_from_new_cars|floatformat:2 }}

+
+
+
+
+
+
+

{% trans "New Cars Cost" %}

+

{{ total_cost_of_new_cars_sold|floatformat:2 }}

+
+
+
+
+ +

{% trans "Sales of Used Cars" %}

+
+
+
+
+

{% trans "Used Cars Sold" %}

+

{{ total_used_cars_sold }}

+
+
+
+
+
+
+

{% trans "Used Cars Revenue" %}

+

{{ total_revenue_from_used_cars|floatformat:2 }}

+
+
+
+
+
+
+

{% trans "Used Cars Net Profit" %}

+

{{ net_profit_from_used_cars|floatformat:2 }}

+
+
+
+
+
+
+

{% trans "Used Cars VAT" %}

+

{{ total_vat_collected_from_used_cars|floatformat:2 }}

+
+
+
+
+
+
+

{% trans "Used Cars Cost" %}

+

{{ total_cost_of_used_cars_sold|floatformat:2 }}

+
+
+
+
+{% endif %} + +{% if request.is_dealer or request.is_manager or request.is_inventory %} +

{% trans "Inventory KPIs" %}

+
+
+
+
+

{% trans "Total Cars in Inventory" %}

+

{{ total_cars_in_inventory }}

+
+
+
+
+
+
+

{% trans "Total Inventory Value" %}

+

{{ total_inventory_value|floatformat:2 }}

+
+
+
+
+
+
+

{% trans "New Cars in Inventory" %}

+

{{ total_new_cars_in_inventory }}

+
+
+
+
+
+
+

{% trans "Used Cars in Inventory" %}

+

{{ total_used_cars_in_inventory }}

+
+
+
+
+
+
+

{% trans "New Cars Inventory Value" %}

+

{{ new_car_value|floatformat:2 }}

+
+
+
+
+
+
+

{% trans "Used Cars Inventory Value" %}

+

{{ used_car_value|floatformat:2 }}

+
+
+
+ +
+{% endif %} + +{% if request.is_dealer or request.is_manager or request.is_accountant %} +

+ {% blocktrans with start_date=start_date|date:"F j, Y" end_date=end_date|date:"F j, Y" %} + Financial Health KPIs ({{ start_date }} - {{ end_date }}) + {% endblocktrans %} +

+
+
+
+
+

{% trans "Total Revenue from Services" %}

+

{{ total_revenue_from_services|floatformat:2 }}

+
+
+
+
+
+
+

{% trans "Total VAT from Services" %}

+

{{ total_vat_collected_from_services|floatformat:2 }}

+
+
+
+
+
+
+

{% trans "Total Revenue Generated" %}

+

{{ total_revenue_generated|floatformat:2 }}

+
+
+
+
+
+
+

{% trans "Total VAT Collected" %}

+

{{ total_vat_collected|floatformat:2 }}

+
+
+
+
+
+
+

{% trans "Total Expenses" %}

+

{{ total_expenses|floatformat:2 }}

+
+
+
+
+
+
+

{% trans "Gross Profit" %}

+

{{ gross_profit|floatformat:2 }}

+
+
+
+
+{% endif %} \ No newline at end of file diff --git a/templates/dashboards/general_dashboard.html b/templates/dashboards/general_dashboard.html new file mode 100644 index 00000000..7609b221 --- /dev/null +++ b/templates/dashboards/general_dashboard.html @@ -0,0 +1,429 @@ +{% extends 'base.html' %} +{% load i18n %} +{% load tenhal_tag %} + +{% block title %} + {% trans "Dealership Dashboard"|capfirst %} +{% endblock title %} + +{% block content %} +
+
+

+ {% if request.is_dealer %} + {% trans "Business Health Dashboard" %} + {% elif request.is_manger %} + {% trans "Manager Dashboard" %} + {% elif request.is_inventory %} + {% trans "Inventory Dashboard" %} + {% else %} + {% trans "Accountant Dashboard" %} + {% endif %} + +

+
+
+
+ + +
+
+ + +
+
+ +
+
+ +
+
+ +
+ {% include 'dashboards/financial_data_cards.html' %} +
+ +
+ {% include 'dashboards/chart.html' %} +
+
+ + +{% endblock content %} + +{% block customJS %} + +{% endblock %} \ No newline at end of file diff --git a/templates/dashboards/inventory_dashboard.html b/templates/dashboards/inventory_dashboard.html deleted file mode 100644 index d18771f7..00000000 --- a/templates/dashboards/inventory_dashboard.html +++ /dev/null @@ -1,300 +0,0 @@ -{% extends 'base.html' %} -{% block content %} - -
-
-

Inventory Dashboard

- -
- -
-
-
-
-
-

Total Cars in Inventory

-

{{ total_cars_in_inventory }}

-
- - +5 units from last month - -
-
-
-
-
-
-
-

New Cars in Inventory

-

{{ new_cars_in_inventory }}

-
- - +5 units from last month - -
-
-
-
-
-
-
-

Used Cars in Inventory

-

{{ old_cars_in_inventory }}

-
- - +5 units from last month - -
-
-
-
-
-
-
-

Inventory Value

-

$5.8M

-
- - -2% from last month - -
-
-
-
- -
-
-
-

New Car Value

-

$3.2M

-

Total new cars value

-
-
-
-
-

Used Car Value

-

$2.6M

-

Total used cars value

-
-
-
-
-

Average Time on Lot

-

10 days

-

Average for all vehicles

-
-
-
-
-

Aging Inventory

-

12 cars

- - -4 cars from last month - -
-
-
- - -
-
-
-
-
Inventory by Make
-
-
- -
-
-
- -
-
-
-
Car Models by Make in Inventory
-
-
-
- - -
-
-
- -
-
-
- -
- - -
- - -{% endblock content %} - -{% block customJS %} - -{% endblock %} \ No newline at end of file diff --git a/templates/dashboards/sales.html b/templates/dashboards/sales.html deleted file mode 100644 index 2a537a98..00000000 --- a/templates/dashboards/sales.html +++ /dev/null @@ -1,452 +0,0 @@ -{% load i18n static custom_filters django_ledger %} -{% block content %} -
-
-
-

- {{ staff }} -

-
-
-
-
-

{{ _("Inventory by Status") }}

-
-
-
-
- {{ _("Available") }} -
-

{{ available_cars }}

-
-
-
-
-
- {{ _("Sold") }} -
-

{{ sold_cars }}

-
-
-
-
-
- {{ _("Reserved") }} -
-

{{ reserved_cars }}

-
-
-
-
-
- {{ _("Transfer") }} -
-

{{ transfer_cars }}

-
-
-
-
-
- {{ _("Hold") }} -
-

{{ hold_cars }}

-
-
-
-
-
- {{ _("Damaged") }} -
-

{{ damaged_cars }}

-
-
-
-
-
-
-
-
-

-
-
-
-
-
-
-
-
-
-

{{ _("New Leads and Customers") }}

-

{{ _("Payment received across all channels") }}

-
-
-
-
- -

- {{ _("New Customers") }} : - 42 -

- - +24.5% - - -
-
-
-
-
-
-
- -

- {{ _("New Leads") }} :45 -

- - +30.5% - - -
-
-
-
-
-
-
-
-
-
-
-
-
-
-

{% trans 'inventory'|upper %}

-
-
-
-

{{ total_cars }}

- {{ _("As of") }}{% now "SHORT_DATETIME_FORMAT" %} -
-
-
-
-

{% trans 'inventory value'|upper %}

-
-
- -
-

-

-
-
-
-

- {{ total_selling_price|currency_format }} -

-
-
-
-
-

{% trans "Profits"|upper %}

-
-
- -
-

-

-
-
-
-

- {{ total_profit|currency_format }} -

-
-
-
-
-
{{ _("Canceled Invoices") }}
-
-
-
-

120.00

- 5.76% - {{ _("From last month") }} -
-
-
-
-
-
-
-
-
-
-

{{ _("Gross Profit") }}

-

Annual income according to the board

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Flight
-
$162,791,400
-
-
-
- -

15.50%

-
-
-
-
-
-
-
-
Flight (Package)
-
$135,659,500
-
-
-
- -

11.09%

-
-
-
-
-
-
-
-
Hotel
-
$271,319,000
-
-
-
- -

29.98%

-
-
-
-
-
-
-
-
Hotel (Package)
-
$162,791,400
-
-
-
- -

03.90%

-
-
-
-
-
-
-
-
-
-
-
-
- -{% endblock %} diff --git a/templates/dashboards/sales_dashboard.html b/templates/dashboards/sales_dashboard.html index 211fa9e7..2da598a8 100644 --- a/templates/dashboards/sales_dashboard.html +++ b/templates/dashboards/sales_dashboard.html @@ -1,58 +1,63 @@ {% extends 'base.html' %} +{% load i18n %} + {% block content %}
-
-

Sales & Leads Dashboard

- +
+

{% trans "Sales Dashboard" %}

+
+
+
+ + +
+
+ + +
+
+ +
+
+
- +
-
+

{% trans "Inventory KPIs" %}

+
-
-
-

Total Cars Sold

-

{{ sold_cars }}

-
- - +5 units from last month - +
+

{% trans "Total Cars in Inventory" %}

+

{{ total_cars_in_inventory }}

-
+ +
-
-
-

Used Cars Sold

-

{{ sold_used_cars }}

-
- - +5 units from last month - +
+

{% trans "New Cars in Inventory" %}

+

{{ total_new_cars_in_inventory }}

-
+
-
-
-

New Cars Sold

-

{{ sold_new_cars }}

-
- - +5 units from last month - +
+

{% trans "Used Cars in Inventory" %}

+

{{ total_used_cars_in_inventory }}

+
+
+
+ + @@ -62,60 +67,27 @@
-
Monthly Cars Sold
-
-
- -
-
-
-
-
-
-
Sales by Make
-
-
- -
-
-
-
- -
-
-
-
-
Top 5 Lead Sources
+
{% trans "Top Lead Sources" %}
-
-
-
-
Appointments by Weekday
-
-
- -
+ +
+
+
+
{% trans "Lead Conversion Funnel" %}
+
+
+
+
+
-
-
-
-
-
Lead Conversion Funnel
-
-
- -
-
-
-
@@ -124,233 +96,174 @@ {% block customJS%} {% endblock %} \ No newline at end of file diff --git a/templates/dealers/dealer_detail.html b/templates/dealers/dealer_detail.html index 8c30f461..704b2d97 100644 --- a/templates/dealers/dealer_detail.html +++ b/templates/dealers/dealer_detail.html @@ -125,7 +125,7 @@ {% endfor %} -
+ {% comment %}
{% if dealer.user.userplan.is_expired %} {{ _("Renew") }} {% endif %} @@ -135,6 +135,15 @@ {% if not dealer.user.userplan %} {{ _("Subscribe Now") }} {% endif %} +
{% endcomment %} +
+ {% if not dealer.user.userplan %} + {{ _("Subscribe Now") }} + {% elif dealer.user.userplan.is_expired %} + {{ _("Renew") }} + {% elif dealer.user.userplan.plan.name != "Enterprise" %} + {{ _("Upgrade Plan") }} + {% endif %}
diff --git a/templates/index.html b/templates/index.html index c12821e9..8ad09f25 100644 --- a/templates/index.html +++ b/templates/index.html @@ -3,16 +3,10 @@ {% block content %} {% if request.user.is_authenticated %}
{% endif %} + + {% endblock %} diff --git a/templates/inventory/car_list.html b/templates/inventory/car_list.html index 5fe4b8bf..45eaf25f 100644 --- a/templates/inventory/car_list.html +++ b/templates/inventory/car_list.html @@ -180,7 +180,7 @@
-
+
diff --git a/templates/ledger/bank_accounts/bank_account_detail.html b/templates/ledger/bank_accounts/bank_account_detail.html index 880ffac9..3cfe895d 100644 --- a/templates/ledger/bank_accounts/bank_account_detail.html +++ b/templates/ledger/bank_accounts/bank_account_detail.html @@ -3,75 +3,72 @@ {% block title %} {{ _("View Bank Account") }} {% endblock title %} + {% block content %} -