# Standard import cv2 import json import logging import numpy as np from rich import print from random import randint from decimal import Decimal from calendar import month_name from pyzbar.pyzbar import decode from urllib.parse import urlparse, urlunparse ##################################################################### from django.db.models.deletion import RestrictedError # Django from django.db.models import Q from django.conf import settings from django.db import transaction from django.db.models import Func from django.contrib import messages from django.http import JsonResponse from django.forms import HiddenInput, ValidationError from django.shortcuts import HttpResponse from django.db.models import Sum, F, Count from django.core.paginator import Paginator from django.contrib.auth.models import User from django.contrib.auth.models import Group from django.db.models import Count, F, Value from django.urls import reverse, reverse_lazy from django.utils import timezone, translation from django.db.models.functions import Coalesce from django.contrib.auth.models import Permission from django.utils.decorators import method_decorator from django.views.decorators.csrf import csrf_exempt from django.core.files.storage import default_storage from django.utils.translation import gettext_lazy as _ from django.contrib.auth.decorators import login_required from django.contrib.auth.mixins import LoginRequiredMixin from django.views.generic import DetailView, RedirectView from django.contrib.messages.views import SuccessMessageMixin from django.contrib.auth.mixins import PermissionRequiredMixin from django.contrib.auth.decorators import permission_required from django.shortcuts import render, get_object_or_404, redirect from django.views.generic import ( View, ListView, DetailView, CreateView, UpdateView, DeleteView, TemplateView, ) ##################################################################### # Django Ledger from django_ledger.io import roles from django_ledger.utils import accruable_net_summary from django_ledger.forms.account import AccountModelCreateForm, AccountModelUpdateForm from django_ledger.views.entity import EntityModelDetailBaseView,EntityModelDetailHandlerView from django_ledger.forms.item import ( ExpenseItemCreateForm, ExpenseItemUpdateForm, ) from django_ledger.forms.bank_account import ( BankAccountCreateForm, BankAccountUpdateForm, ) from django_ledger.forms.bill import ( ApprovedBillModelUpdateForm, InReviewBillModelUpdateForm, ) from django_ledger.forms.invoice import ( DraftInvoiceModelUpdateForm, ApprovedInvoiceModelUpdateForm, PaidInvoiceModelUpdateForm, ) from django_ledger.models import ( ItemTransactionModel, EntityModel, InvoiceModel, BankAccountModel, AccountModel, JournalEntryModel, TransactionModel, EstimateModel, CustomerModel, ItemModel, BillModel, VendorModel, ) from django_ledger.views.financial_statement import ( FiscalYearBalanceSheetView, BaseIncomeStatementRedirectView, FiscalYearIncomeStatementView, BaseCashFlowStatementRedirectView, FiscalYearCashFlowStatementView, ) from django_ledger.io.io_core import get_localdate from django_ledger.models import EntityModel from django_ledger.views.mixins import ( QuarterlyReportMixIn, MonthlyReportMixIn, DateReportMixIn, DjangoLedgerSecurityMixIn, EntityUnitMixIn, ) ##################################################################### # Other from plans.models import Plan from inventory.filters import AccountModelFilter from . import models, forms, tables from plans.quota import get_user_quota from django_tables2 import SingleTableView from django_tables2.export.views import ExportMixin from appointment.models import Appointment,AppointmentRequest,Service,StaffMember from .services import ( decodevin, get_make, get_model, ) from .utils import ( CarFinanceCalculator, get_car_finance_data, get_financial_values, get_item_transactions, reserve_car, send_email, get_user_type, set_bill_payment, set_invoice_payment, CarTransfer, ) ##################################################################### logger = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO) class Hash(Func): function = 'get_hash' def switch_language(request): language = request.GET.get("language", "en") referer = request.META.get("HTTP_REFERER", "/") parsed_url = urlparse(referer) path_parts = parsed_url.path.split("/") if path_parts[1] in dict(settings.LANGUAGES): path_parts.pop(1) new_path = "/".join(path_parts) new_url = urlunparse( ( parsed_url.scheme, parsed_url.netloc, new_path, parsed_url.params, parsed_url.query, parsed_url.fragment, ) ) if language in dict(settings.LANGUAGES): logger.debug(f"Switching language to: {language}") response = redirect(new_url) response.set_cookie(settings.LANGUAGE_COOKIE_NAME, language) translation.activate(language) request.session[settings.LANGUAGE_COOKIE_NAME] = language logger.debug( f"Language switched to: {language}, Session: {request.session[settings.LANGUAGE_COOKIE_NAME]}" ) return response else: logger.warning(f"Invalid language code: {language}") return redirect("/") def dealer_signup(request, *args, **kwargs): form1 = forms.WizardForm1() form2 = forms.WizardForm2() form3 = forms.WizardForm3() if request.method == "POST": if "Hx-Request" in request.headers: form1 = forms.WizardForm1(request.POST) return render(request,"account/signup-wizard.html",{"form1": form1, "form2": form2, "form3": form3}) data = json.loads(request.body) wf1 = data.get("wizardValidationForm1") wf2 = data.get("wizardValidationForm2") wf3 = data.get("wizardValidationForm3") email = wf1.get("email") password = wf1.get("password") password_confirm = wf1.get("confirm_password") name = wf2.get("name") arabic_name = wf2.get("arabic_name") phone = wf2.get("phone_number") crn = wf3.get("crn") vrn = wf3.get("vrn") address = wf3.get("address") if password != password_confirm: return JsonResponse({"error": "Passwords do not match."}, status=400) try: with transaction.atomic(): user = User.objects.create(username=email, email=email) user.set_password(password) user.save() StaffMember.objects.create(user=user) models.Dealer.objects.create( user=user, name=name, arabic_name=arabic_name, crn=crn, vrn=vrn, phone_number=phone, address=address, ) return JsonResponse( {"message": "User created successfully."}, status=200 ) except Exception as e: return JsonResponse({"error": str(e)}, status=400) return render(request,"account/signup-wizard.html",{"form1": form1, "form2": form2, "form3": form3}) # class OTPView(View, LoginRequiredMixin): # template_name = "account/otp_verification.html" # # def get(self, request, *args, **kwargs): # # device = default_device(request.user) # # device.generate_challenge() # return render(request, self.template_name) # # def post(self, request, *args, **kwargs): # otp_code = request.POST.get("otp_code") # # if self.verify_otp(otp_code, request.user): # messages.success(request, _("OTP verified successfully!")) # return redirect("home") # # messages.error(request, _("Invalid OTP. Please try again.")) # return render(request, self.template_name) # def verify_otp(self, otp_code, user): # device = default_device(user) # if device and device.verify_token(otp_code): # return True # return False class HomeView(LoginRequiredMixin,TemplateView): template_name = "index.html" def dispatch(self, request, *args, **kwargs): # Redirect unauthenticated users to the welcome page if not request.user.is_authenticated: return redirect("welcome") return super().dispatch(request, *args, **kwargs) # def get_context_data(self, **kwargs): # context = super().get_context_data(**kwargs) # dealer = get_user_type(self.request) # # try: # # Fetch car-related statistics # total_cars = models.Car.objects.filter(dealer=dealer).count() # total_reservations = models.CarReservation.objects.filter( # reserved_until__gte=timezone.now() # ).count() # cars_in_house = models.CarLocation.objects.filter( # owner=dealer, # ).count() # cars_outside = total_cars - cars_in_house # # # Fetch financial statistics # stats = models.CarFinance.objects.aggregate( # total_cost_price=Sum("cost_price"), # total_selling_price=Sum("selling_price"), # ) # total_cost_price = stats.get("total_cost_price", 0) or 0 # total_selling_price = stats.get("total_selling_price", 0) or 0 # total_profit = total_selling_price - total_cost_price # # # Prepare context data # context.update({ # "dealer": dealer, # "total_cars": total_cars, # "cars_in_house": cars_in_house, # "cars_outside": cars_outside, # "total_reservations": total_reservations, # "total_cost_price": total_cost_price, # "total_selling_price": total_selling_price, # "total_profit": total_profit, # }) # # except Exception as e: # # Log the error (you can use Django's logging framework) # print(f"Error fetching data: {e}") # # Provide default values in case of an error # context.update({ # "dealer": dealer, # "total_cars": 0, # "cars_in_house": 0, # "cars_outside": 0, # "total_reservations": 0, # "total_cost_price": 0, # "total_selling_price": 0, # "total_profit": 0, # }) # return context class TestView(TemplateView): template_name = "inventory/cars_list_api.html" class ManagerDashboard(LoginRequiredMixin, TemplateView): template_name = "dashboards/manager.html" def dispatch(self, request, *args, **kwargs): if not request.user.is_authenticated: return redirect("welcome") 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 total_cars = models.Car.objects.filter(dealer=dealer).count() total_reservations = models.CarReservation.objects.filter( reserved_until__gte=timezone.now() ).count() stats = models.CarFinance.objects.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.PENDING).count() canceled_leads = models.Lead.objects.filter(dealer=dealer, status=models.Status.CANCELED).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) total_activity = models.UserActivityLog.objects.filter(user=dealer.user).count() staff = models.Staff.objects.filter(dealer=dealer).count() total_leads = models.Lead.objects.filter(dealer=dealer).count() invoices = entity.get_invoices().count() customers = entity.get_customers().count() purchase_orders = entity.get_purchase_orders().count() estimates = entity.get_estimates().count() context["dealer"] = dealer context["total_cars"] = total_cars context["total_reservations"] = total_reservations 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['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) context['customers'] = customers context['staff'] = staff context['total_leads'] = total_leads context['invoices'] = invoices context['estimates'] = estimates context['purchase_orders'] = purchase_orders return context class SalesDashboard(LoginRequiredMixin, TemplateView): template_name = "dashboards/sales.html" def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) dealer = get_user_type(self.request) staff = getattr(self.request.user, "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() # new_leads = models.Lead.objects.filter(dealer=dealer, status=models.Status.NEW).count() pending_leads = models.Lead.objects.filter(dealer=dealer, dealer__staff__assigned=staff, status=models.Status.PENDING).count() # canceled_leads = models.Lead.objects.filter(dealer=dealer, status=models.Status.CANCELED).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["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['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) # context['customers'] = customers # context['staff'] = staff # context['total_leads'] = total_leads # context['invoices'] = invoices return context class WelcomeView(TemplateView): template_name = "welcome.html" def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) dealer = get_user_type(self.request) plan_list = Plan.objects.all() # pricing = PlanPricing.objects.filter(plan=plan). context["plan_list"] = plan_list return context class CarCreateView(LoginRequiredMixin,PermissionRequiredMixin, CreateView): model = models.Car form_class = forms.CarForm template_name = "inventory/car_form.html" permission_required = ['inventory.add_car'] # success_url = reverse_lazy('inventory_stats') def get_form(self, form_class=None): form = super().get_form(form_class) dealer = get_user_type(self.request) form.fields["vendor"].queryset = dealer.entity.get_vendors().filter(active=True) return form def get_success_url(self): """Determine the redirect URL based on user choice.""" if self.request.POST.get("add_another"): return reverse("car_add") return reverse("inventory_stats") def form_valid(self, form): dealer = get_user_type(self.request) form.instance.dealer = dealer form.save() messages.success(self.request, "Car saved successfully.") return super().form_valid(form) def car_history(request,pk): car = get_object_or_404(models.Car, pk=pk) activities = models.Activity.objects.filter(content_type__model="car", object_id=car.id) return render(request,'inventory/car_history.html',{"car":car,"activities":activities}) class AjaxHandlerView(LoginRequiredMixin, View): def get(self, request, *args, **kwargs): action = request.GET.get("action") handlers = { "decode_vin": self.decode_vin, "get_models": self.get_models, "get_series": self.get_series, "get_trims": self.get_trims, "get_specifications": self.get_specifications, "get_equipments": self.get_equipments, "get_options": self.get_options, } handler = handlers.get(action) if handler: return handler(request) else: return JsonResponse({"error": "Invalid action"}, status=400) def decode_vin(self, request): vin_no = request.GET.get("vin_no") car_existed = models.Car.objects.filter(vin=vin_no).exists() if car_existed: return JsonResponse({"error": _("VIN number exists")}, status=400) if not vin_no or len(vin_no.strip()) != 17: return JsonResponse( {"success": False, "error": "Invalid VIN number provided."}, status=400 ) vin_no = vin_no.strip() vin_data = {} decoding_method = "" # manufacturer_name = model_name = year_model = None if not (result := decodevin(vin_no)): return JsonResponse( {"success": False, "error": "VIN not found in all sources."}, status=404 ) manufacturer_name, model_name, year_model = result.values() car_make = get_make(manufacturer_name) car_model = get_model(model_name, car_make) logger.info( f"VIN decoded using {decoding_method}: Make={manufacturer_name}, Model={model_name}, Year={year_model}" ) if not car_make: return JsonResponse( {"success": False, "error": "Manufacturer not found in the database."}, status=404, ) vin_data["make_id"] = car_make.id_car_make vin_data["name"] = car_make.name vin_data["arabic_name"] = car_make.arabic_name if not car_model: vin_data["model_id"] = "" else: vin_data["model_id"] = car_model.id_car_model vin_data["year"] = year_model return JsonResponse({"success": True, "data": vin_data}) def get_models(self, request): make_id = request.GET.get("make_id") car_models = ( models.CarModel.objects.filter(id_car_make=make_id) .values("id_car_model", "name", "arabic_name") .order_by("name") ) return JsonResponse(list(car_models), safe=False) def get_series(self, request): model_id = request.GET.get("model_id") year = request.GET.get("year") model_id = int(model_id) year = int(year) query = Q(id_car_model=model_id) & ( Q(year_begin__lte=year, year_end__gte=year) | Q(year_end__isnull=True) | Q(year_begin__isnull=True) ) try: series = models.CarSerie.objects.filter(query).values( "id_car_serie", "name", "arabic_name", "generation_name" ) except Exception as e: return JsonResponse({"error": "Server error occurred"}, status=500) return JsonResponse(list(series), safe=False) def get_trims(self, request): serie_id = request.GET.get("serie_id") # model_id = request.GET.get('model_id') trims = models.CarTrim.objects.filter(id_car_serie=serie_id).values( "id_car_trim", "name", "arabic_name" ) return JsonResponse(list(trims), safe=False) def get_specifications(self, request): trim_id = request.GET.get("trim_id") car_spec_values = models.CarSpecificationValue.objects.filter( id_car_trim=trim_id ) lang = translation.get_language() specs_by_parent = {} for value in car_spec_values: specification = value.id_car_specification parent = specification.id_parent parent_id = parent.id_car_specification if parent else 0 if lang == "ar": parent_name = parent.arabic_name if parent else "Root" else: parent_name = parent.name if parent else "Root" if parent_id not in specs_by_parent: specs_by_parent[parent_id] = { "parent_name": parent_name, "specifications": [], } spec_data = { "specification_id": specification.id_car_specification, "s_name": specification.arabic_name if lang == "ar" else specification.name, "s_value": value.value, "s_unit": value.unit if value.unit else "", "trim_name": value.id_car_trim.name, } specs_by_parent[parent_id]["specifications"].append(spec_data) serialized_specs = [ {"parent_name": v["parent_name"], "specifications": v["specifications"]} for v in specs_by_parent.values() ] return JsonResponse(serialized_specs, safe=False) def get_equipments(self, request): trim_id = request.GET.get("trim_id") equipments = ( models.CarEquipment.objects.filter(id_car_trim=trim_id) .values("id_car_equipment", "name") .order_by("name") ) return JsonResponse(list(equipments), safe=False) def get_options(self, request): equipment_id = request.GET.get("equipment_id") car_option_values = models.CarOptionValue.objects.filter( id_car_equipment=equipment_id ) options_by_parent = {} for value in car_option_values: option = value.id_car_option parent = option.id_parent parent_id = parent.id_car_option if parent else 0 parent_name = parent.name if parent else "Root" if parent_id not in options_by_parent: options_by_parent[parent_id] = { "parent_name": parent_name, "options": [], } option_data = { "option_id": option.id_car_option, "option_name": option.name, "is_base": value.is_base, "equipment_name": value.id_car_equipment.name, } options_by_parent[parent_id]["options"].append(option_data) serialized_options = [ {"parent_name": v["parent_name"], "options": v["options"]} for v in options_by_parent.values() ] return JsonResponse(serialized_options, safe=False) @method_decorator(csrf_exempt, name="dispatch") class SearchCodeView(LoginRequiredMixin,View): template_name = "inventory/scan_vin.html" def get(self, request, *args, **kwargs): """Render the form page.""" return render(request, self.template_name) def post(self, request, *args, **kwargs): image_file = request.FILES.get("image") if image_file: print("image received!") image = cv2.imdecode( np.frombuffer(image_file.read(), np.uint8), cv2.IMREAD_COLOR ) decoded_objects = decode(image) if decoded_objects: print("image decoded!") print(decoded_objects[0]) code = decoded_objects[0].data.decode("utf-8") print("code received!") print(code) car = get_object_or_404(models.Car, vin=code) name = car.id_car_make.get_local_name print(name) return redirect("car_detail", pk=car.pk) else: print("back to else statement") return JsonResponse({"success": False, "error": "No code detected"}) else: return JsonResponse({"success": False, "error": "No image provided"}) class CarInventory(LoginRequiredMixin,PermissionRequiredMixin, ListView): model = models.Car home_label = _("inventory") template_name = "inventory/car_inventory.html" context_object_name = "cars" paginate_by = 10 ordering = ["receiving_date"] permission_required = ['inventory.view_car'] def get_queryset(self, *args, **kwargs): query = self.request.GET.get("q") make_id = self.kwargs["make_id"] model_id = self.kwargs["model_id"] trim_id = self.kwargs["trim_id"] dealer = get_user_type(self.request) cars = models.Car.objects.filter( dealer=dealer, id_car_make=make_id, id_car_model=model_id, id_car_trim=trim_id, ).order_by("receiving_date") return apply_search_filters(cars, query) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["query"] = self.request.GET.get("q", "") context["make_id"] = self.kwargs["make_id"] context["model_id"] = self.kwargs["model_id"] context["trim_id"] = self.kwargs["trim_id"] return context class CarColorCreate(LoginRequiredMixin,PermissionRequiredMixin, CreateView): model = models.CarColors form_class = forms.CarColorsForm template_name = "inventory/add_colors.html" permission_required = ['inventory.view_car'] def form_valid(self, form): car = get_object_or_404(models.Car, pk=self.kwargs["car_pk"]) form.instance.car = car return super().form_valid(form) def get_success_url(self): return reverse_lazy("car_detail", kwargs={"pk": self.kwargs["car_pk"]}) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["car"] = get_object_or_404(models.Car, pk=self.kwargs["car_pk"]) return context class CarListView(LoginRequiredMixin,PermissionRequiredMixin, ListView): model = models.Car template_name = "inventory/car_list_view.html" context_object_name = "cars" paginate_by = 20 permission_required = 'inventory.view_car' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) dealer = get_user_type(self.request) cars = models.Car.objects.filter(dealer=dealer).order_by("receiving_date") context["stats"] = { 'all': cars.count(), 'available':cars.filter(status='available').count(), 'reserved':cars.filter(status='reserved').count(), 'sold':cars.filter(status='sold').count(), 'transfer':cars.filter(status='transfer').count() } context['make'] = models.CarMake.objects.filter(car__in=cars).distinct() context['model'] = models.CarModel.objects.none() context['year'] = models.Car.objects.none() make = self.request.GET.get('make') model = self.request.GET.get('model') if make: make_ = models.CarMake.objects.get(id_car_make=int(make)) context['model'] = make_.carmodel_set.filter(car__in=cars).distinct() if make and model: make_ = models.CarMake.objects.get(id_car_make=int(make)) model_ = models.CarModel.objects.get(id_car_model=int(model)) context['year'] = models.Car.objects.filter(id_car_make=make_,id_car_model=model_).values_list('year').distinct() return context def get_queryset(self): dealer = get_user_type(self.request) qs = super().get_queryset() qs = qs.filter(dealer=dealer) status = self.request.GET.get('status') search = self.request.GET.get('search') make = self.request.GET.get('make',None) model = self.request.GET.get('model',None) year = self.request.GET.get('year',None) car_status = self.request.GET.get('car_status',None) if status: qs=qs.filter(status=status) if search: query = Q(vin__icontains=search)|Q(id_car_make__name__icontains=search)|Q(id_car_model__name__icontains=search)|Q(id_car_trim__name__icontains=search)|Q(vin=search) qs=qs.filter(query) if any([make, model, year, car_status]): query = Q() if make: query &= Q(id_car_make=int(make)) if model: query &= Q(id_car_model=model) if year: query &= Q(year=year) if car_status: query &= Q(status=car_status) qs = qs.filter(query) return qs @login_required def inventory_stats_view(request): dealer = get_user_type(request) # Base queryset for cars belonging to the dealer cars = models.Car.objects.filter(dealer=dealer) # Count for total, reserved, showroom, and unreserved cars total_cars = cars.count() reserved_cars = models.CarReservation.objects.count() # showroom_cars = cars.filter(location='showroom').count() # unreserved_cars = total_cars - reserved_cars # Annotate total cars by make, model, and trim cars = cars.select_related("id_car_make", "id_car_model", "id_car_trim").annotate( make_total=Count("id_car_make"), model_total=Count("id_car_model"), trim_total=Count("id_car_trim"), ) inventory = {} for car in cars: make = car.id_car_make if make.id_car_make not in inventory: inventory[make.id_car_make] = { "make_id": make.id_car_make, "make_name": make.get_local_name(), "total_cars": 0, "models": {}, } inventory[make.id_car_make]["total_cars"] += 1 model = car.id_car_model if model and model.id_car_model not in inventory[make.id_car_make]["models"]: inventory[make.id_car_make]["models"][model.id_car_model] = { "model_id": model.id_car_model, "model_name": model.get_local_name(), "total_cars": 0, "trims": {}, } try: inventory[make.id_car_make]["models"][model.id_car_model]["total_cars"] += 1 trim = car.id_car_trim if ( trim and trim.id_car_trim not in inventory[make.id_car_make]["models"][model.id_car_model][ "trims" ] ): inventory[make.id_car_make]["models"][model.id_car_model]["trims"][ trim.id_car_trim ] = { "trim_id": trim.id_car_trim, "trim_name": trim.name, "total_cars": 0, } inventory[make.id_car_make]["models"][model.id_car_model]["trims"][ trim.id_car_trim ]["total_cars"] += 1 except Exception as e: print(e) result = { "total_cars": total_cars, "reserved_cars": reserved_cars, "makes": [ { "make_id": make_data["make_id"], "make_name": make_data["make_name"], "total_cars": make_data["total_cars"], "models": [ { "model_id": model_data["model_id"], "model_name": model_data["model_name"], "total_cars": model_data["total_cars"], "trims": list(model_data["trims"].values()), } for model_data in make_data["models"].values() ], } for make_data in inventory.values() ], } return render(request, "inventory/inventory_stats.html", {"inventory": result}) class CarDetailView(LoginRequiredMixin,PermissionRequiredMixin, DetailView): model = models.Car template_name = "inventory/car_detail.html" context_object_name = "car" permission_required = ['inventory.view_car'] class CarFinanceCreateView(LoginRequiredMixin,PermissionRequiredMixin, CreateView): model = models.CarFinance form_class = forms.CarFinanceForm template_name = "inventory/car_finance_form.html" permission_required = ['inventory.add_carfinance'] def dispatch(self, request, *args, **kwargs): self.car = get_object_or_404(models.Car, pk=self.kwargs["car_pk"]) return super().dispatch(request, *args, **kwargs) def form_valid(self, form): form.instance.car = self.car messages.success(self.request, _("Car finance details saved successfully.")) return super().form_valid(form) def get_success_url(self): return reverse("car_detail", kwargs={"pk": self.car.pk}) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["car"] = self.car return context def get_form(self, form_class=None): form = super().get_form(form_class) dealer = get_user_type(self.request) form.fields[ "additional_finances" ].queryset = models.AdditionalServices.objects.filter(dealer=dealer) return form class CarFinanceUpdateView(LoginRequiredMixin,PermissionRequiredMixin, SuccessMessageMixin, UpdateView): model = models.CarFinance form_class = forms.CarFinanceForm template_name = "inventory/car_finance_form.html" success_message = _("Car finance details updated successfully.") permission_required = ['inventory.change_carfinance'] def get_success_url(self): return reverse("car_detail", kwargs={"pk": self.object.car.pk}) def get_form_kwargs(self): kwargs = super().get_form_kwargs() kwargs["instance"] = self.get_object() return kwargs def get_initial(self): initial = super().get_initial() instance = self.get_object() dealer = get_user_type(self.request) selected_items = instance.additional_services.filter(dealer=dealer) initial["additional_finances"] = selected_items return initial def get_form(self, form_class=None): form = super().get_form(form_class) dealer = get_user_type(self.request) form.fields["additional_finances"].queryset = models.AdditionalServices.objects.filter(dealer=dealer) return form class CarUpdateView(LoginRequiredMixin,PermissionRequiredMixin, SuccessMessageMixin, UpdateView): model = models.Car form_class = forms.CarUpdateForm template_name = "inventory/car_edit.html" success_message = _("Car updated successfully.") permission_required = ['inventory.change_car'] def get_success_url(self): return reverse("car_detail", kwargs={"pk": self.object.pk}) class CarDeleteView(LoginRequiredMixin,PermissionRequiredMixin, SuccessMessageMixin, DeleteView): model = models.Car template_name = "inventory/car_confirm_delete.html" success_url = reverse_lazy("inventory_stats") permission_required = ['inventory.delete_car'] def delete(self, request, *args, **kwargs): messages.success(request, _("Car deleted successfully.")) return super().delete(request, *args, **kwargs) class CarLocationCreateView(LoginRequiredMixin,PermissionRequiredMixin,CreateView): model = models.CarLocation form_class = forms.CarLocationForm template_name = "inventory/car_location_form.html" permission_required = ['inventory.add_carlocation'] def get_success_url(self): return reverse_lazy("car_detail", kwargs={"pk": self.object.car.pk}) def form_valid(self, form): form.instance.car = get_object_or_404(models.Car, pk=self.kwargs["car_pk"]) dealer = get_user_type(self.request) form.instance.owner = dealer form.save() messages.success(self.request, "Car saved successfully.") return super().form_valid(form) class CarLocationUpdateView(LoginRequiredMixin,UpdateView): model = models.CarLocation form_class = forms.CarLocationForm template_name = "inventory/car_location_form.html" def get_success_url(self): return reverse_lazy("car_detail", kwargs={"pk": self.object.car.pk}) class CarTransferCreateView(LoginRequiredMixin,CreateView): model = models.CarTransfer form_class = forms.CarTransferForm template_name = "inventory/car_transfer_form.html" def get_form(self, form_class=None): form = super().get_form(form_class) form.fields["to_dealer"].queryset = models.Dealer.objects.exclude( pk=get_user_type(self.request).pk ).all() form.fields["car"].queryset = models.Car.objects.filter(pk=self.kwargs["pk"]) return form def get_initial(self): initial = super().get_initial() initial["car"] = get_object_or_404(models.Car, pk=self.kwargs["pk"]) return initial def form_valid(self, form): form.instance.from_dealer = get_user_type(self.request) form.instance.car.status = "transfer" form.instance.car.save() return super().form_valid(form) def get_success_url(self): return reverse_lazy("car_detail", kwargs={"pk": self.object.car.pk}) class CarTransferDetailView(LoginRequiredMixin, SuccessMessageMixin, DetailView): model = models.CarTransfer template_name = "inventory/transfer_details.html" context_object_name = "transfer" def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["action"] = self.request.GET.get("action") return context @login_required def car_transfer_approve(request, car_pk, transfer_pk): car = get_object_or_404(models.Car, pk=car_pk) transfer = get_object_or_404(models.CarTransfer, pk=transfer_pk) action = request.GET.get("action") if action == "cancel": transfer.status = "cancel" transfer.active = False transfer.save() transfer.car.status = "available" transfer.car.save() messages.success(request, _("Car transfer canceled successfully.")) models.Notification.objects.create( user=transfer.from_dealer.user, message=f"Car transfer request from {transfer.to_dealer} is canceled.", ) return redirect("car_detail", pk=car.pk) transfer.status = "approved" transfer.save() url = request.build_absolute_uri( reverse( "transfer_preview", kwargs={"car_pk": car.pk, "transfer_pk": transfer.pk} ) ) models.Notification.objects.create( user=transfer.to_dealer.user, message=f"Car transfer request from {transfer.from_dealer} is waiting for your acceptance. Accept", ) messages.success(request, _("Car transfer approved successfully.")) return redirect("car_detail", pk=car.pk) @login_required def car_transfer_accept_reject(request, car_pk, transfer_pk): car = get_object_or_404(models.Car, pk=car_pk) transfer = get_object_or_404(models.CarTransfer, pk=transfer_pk) status = request.GET.get("status") if status == "rejected": transfer.status = "reject" transfer.active = False messages.success(request, _("Car transfer rejected successfully.")) models.Notification.objects.create( user=transfer.from_dealer.user, message=f"Car transfer request from {transfer.to_dealer} is rejected.", ) transfer.save() elif status == "accepted": transfer.status = "accept" transfer.save() transfer_process = CarTransfer(car, transfer) success = transfer_process.transfer_car() if success: messages.success(request, _("Car Transfer Completed successfully.")) models.Activity.objects.create(content_object=car,notes=f"Transfered from {transfer.from_dealer} to {transfer.to_dealer}",created_by=request.user) models.Notification.objects.create( user=transfer.from_dealer.user, message=f"Car transfer request from {transfer.to_dealer} is completed.", ) return redirect("inventory_stats") @login_required def CarTransferPreviewView(request, car_pk, transfer_pk): transfer = get_object_or_404(models.CarTransfer, pk=transfer_pk) if transfer.to_dealer != get_user_type(request): return redirect("car_detail", pk=car_pk) return render(request, "inventory/transfer_preview.html", {"transfer": transfer}) class CustomCardCreateView(LoginRequiredMixin, CreateView): model = models.CustomCard form_class = forms.CustomCardForm template_name = "inventory/add_custom_card.html" def form_valid(self, form): car = get_object_or_404(models.Car, pk=self.kwargs["car_pk"]) form.instance.car = car return super().form_valid(form) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["car"] = get_object_or_404(models.Car, pk=self.kwargs["car_pk"]) return context def get_success_url(self): messages.success(self.request, _("Custom Card added successfully.")) return reverse_lazy("car_detail", kwargs={"pk": self.kwargs["car_pk"]}) class CarRegistrationCreateView(LoginRequiredMixin, CreateView): model = models.CarRegistration form_class = forms.CarRegistrationForm template_name = 'inventory/car_registration_form.html' def form_valid(self, form): car = get_object_or_404(models.Car, pk=self.kwargs["car_pk"]) form.instance.car = car return super().form_valid(form) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["car"] = get_object_or_404(models.Car, pk=self.kwargs["car_pk"]) return context def get_success_url(self): messages.success(self.request, _("Registration added successfully.")) return reverse_lazy("car_detail", kwargs={"pk": self.kwargs["car_pk"]}) @login_required() def reserve_car_view(request, car_id): if request.method == "POST": car = get_object_or_404(models.Car, pk=car_id) if car.is_reserved(): messages.error(request, _("This car is already reserved.")) return redirect("car_detail", pk=car.pk) response = reserve_car(car, request) return response return JsonResponse( {"success": False, "message": "Invalid request method."}, status=400 ) @login_required def manage_reservation(request, reservation_id): reservation = get_object_or_404( models.CarReservation, pk=reservation_id, reserved_by=request.user ) if request.method == "POST": action = request.POST.get("action") if action == "renew": reservation.reserved_until = timezone.now() + timezone.timedelta(hours=24) reservation.save() messages.success(request, _("Reservation renewed successfully.")) return redirect("car_detail", pk=reservation.car.pk) elif action == "cancel": car = reservation.car reservation.delete() car.status = models.CarStatusChoices.AVAILABLE car.save() messages.success(request, _("Reservation canceled successfully.")) return redirect("car_detail", pk=reservation.car.pk) else: return JsonResponse( {"success": False, "message": _("Invalid action.")}, status=400 ) return JsonResponse( {"success": False, "message": _("Invalid request method.")}, status=400 ) class DealerDetailView(LoginRequiredMixin, DetailView): model = models.Dealer template_name = "dealers/dealer_detail.html" context_object_name = "dealer" def get_queryset(self): return models.Dealer.objects.annotate( staff_count=Coalesce(Count("staff"), Value(0)) # Get the number of staff members ) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) dealer = self.object car_makes = models.CarMake.objects.filter(car_dealers__dealer=dealer) # Fetch current staff count from the annotated queryset staff_count = dealer.staff_count cars_count = models.Car.objects.filter(dealer=dealer).count() # Get the quota value dynamically quota_dict = get_user_quota(dealer.user) allowed_users = quota_dict.get("Users", None) allowed_cars = quota_dict.get("Cars", None) context["car_makes"] = car_makes context["staff_count"] = staff_count context["cars_count"] = cars_count context["allowed_users"] = allowed_users context["allowed_cars"] = allowed_cars context["quota_display"] = f"{staff_count}/{allowed_users}" if allowed_users is not None else "N/A" return context class DealerUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView): model = models.Dealer form_class = forms.DealerForm template_name = "dealers/dealer_form.html" success_url = reverse_lazy("dealer_detail") success_message = _("Dealer updated successfully.") def get_success_url(self): return reverse("dealer_detail", kwargs={"pk": self.object.pk}) class CustomerListView(LoginRequiredMixin,PermissionRequiredMixin, ListView): model = CustomerModel home_label = _("customers") context_object_name = "customers" paginate_by = 10 template_name = "customers/customer_list.html" ordering = ["-created"] permission_required = ['django_ledger.view_customermodel'] def get_queryset(self): query = self.request.GET.get("q") dealer = get_user_type(self.request) customers = dealer.entity.get_customers().filter(additional_info__type="customer") return apply_search_filters(customers, query) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["query"] = self.request.GET.get("q", "") return context class CustomerDetailView(LoginRequiredMixin,PermissionRequiredMixin, DetailView): model = CustomerModel template_name = "customers/view_customer.html" context_object_name = "customer" permission_required = ['django_ledger.view_customermodel'] def get_context_data(self, **kwargs): dealer = get_user_type(self.request) entity = dealer.entity context = super().get_context_data(**kwargs) estimates = entity.get_estimates().filter(customer=self.object) invoices = entity.get_invoices().filter(customer=self.object) # txs = entity. transactions(customer=self.object) total = estimates.count() + invoices.count() context["estimates"] = estimates context["invoices"] = invoices context["total"] = total return context @login_required def add_note_to_customer(request, customer_id): customer = get_object_or_404(CustomerModel, uuid=customer_id) if request.method == "POST": form = forms.NoteForm(request.POST) if form.is_valid(): note = form.save(commit=False) note.content_object = customer note.created_by = request.user note.save() return redirect("customer_detail", pk=customer.pk) else: form = forms.NoteForm() return render(request, "customers/note_form.html", {"form": form, "customer": customer}) @login_required def add_activity_to_customer(request, pk): customer = get_object_or_404(CustomerModel, pk=pk) if request.method == "POST": form = forms.ActivityForm(request.POST) if form.is_valid(): activity = form.save(commit=False) activity.content_object = customer activity.created_by = request.user activity.save() return redirect("customer_detail", pk=pk) else: form = forms.ActivityForm() return render( request, "crm/add_activity.html", {"form": form, "customer": customer} ) @login_required @permission_required('django_ledger.add_customermodel',raise_exception=True) def CustomerCreateView(request): form = forms.CustomerForm() if request.method == "POST": form = forms.CustomerForm(request.POST) dealer = get_user_type(request) if form.is_valid(): if dealer.entity.get_customers().filter(email=form.cleaned_data["email"]).exists(): messages.error(request, _("Customer with this email already exists.")) else: # Create customer name customer_name = ( f"{form.cleaned_data['first_name']} " f"{form.cleaned_data['last_name']}" ) customer_dict = { x: request.POST[x] for x in request.POST if x != "csrfmiddlewaretoken"} # Create customer instance try: customer = dealer.entity.create_customer( commit=False, customer_model_kwargs={ "customer_name": customer_name, "address_1": form.cleaned_data["address"], "phone": form.cleaned_data["phone_number"], "email": form.cleaned_data["email"], } ) # customer.additional_info = {} customer.additional_info.update({"customer_info": customer_dict}) customer.additional_info.update({"type": "customer"}) customer.save() messages.success(request, _("Customer created successfully.")) return redirect("customer_list") except Exception as e: messages.error(request, _(f"An error occurred: {str(e)}")) else: # Form is invalid, show errors messages.error(request, _("Please correct the errors below.")) return render(request, "customers/customer_form.html", {"form": form}) @login_required @permission_required('django_ledger.change_customermodel',raise_exception=True) def CustomerUpdateView(request, pk): customer = get_object_or_404(CustomerModel, pk=pk) if request.method == "POST": # form = forms.CustomerForm(request.POST, instance=customer) customer_dict = { x: request.POST[x] for x in request.POST if x != "csrfmiddlewaretoken" } dealer = get_user_type(request) customer_name = ( customer_dict["first_name"] + " " + customer_dict["last_name"] ) instance = dealer.entity.get_customers().get(pk=pk) instance.customer_name = customer_name instance.address_1 = customer_dict["address"] instance.phone = customer_dict["phone_number"] instance.email = customer_dict["email"] customer_dict["pk"] = str(instance.pk) instance.additional_info.update({"customer_info": customer_dict}) try: user = User.objects.filter(pk=int(instance.additional_info['user_info']['id'])).first() if user: user.username = customer_dict["email"] user.email = customer_dict["email"] user.save() except Exception as e: raise Exception(e) instance.save() messages.success(request, _("Customer updated successfully.")) return redirect("customer_list") else: form = forms.CustomerForm( initial=customer.additional_info["customer_info"] if "customer_info" in customer.additional_info else {} ) return render(request, "customers/customer_form.html", {"form": form}) @login_required def delete_customer(request, pk): customer = get_object_or_404(models.CustomerModel, pk=pk) user = User.objects.get(email=customer.email) customer.active = False user.is_active = False customer.save() user.save() messages.success(request, _("Customer deleted successfully.")) return redirect("customer_list") class VendorListView(LoginRequiredMixin, ListView): model = VendorModel context_object_name = "vendors" paginate_by = 10 template_name = "vendors/vendors_list.html" ordering = ["-created"] def get_queryset(self): query = self.request.GET.get("q") dealer = get_user_type(self.request) vendors = dealer.entity.get_vendors().filter(active=True) return apply_search_filters(vendors, query) @login_required def vendorDetailView(request, pk): vendor = get_object_or_404(models.Vendor, pk=pk) return render(request, template_name="vendors/view_vendor.html", context={"vendor": vendor}) class VendorCreateView( LoginRequiredMixin, SuccessMessageMixin, CreateView, ): model = models.Vendor form_class = forms.VendorForm template_name = "vendors/vendor_form.html" success_url = reverse_lazy("vendor_list") success_message = _("Vendor created successfully.") def form_valid(self, form): dealer = get_user_type(self.request) form.instance.dealer = dealer form.instance.save() return super().form_valid(form) class VendorUpdateView( LoginRequiredMixin, SuccessMessageMixin, UpdateView, ): model = models.Vendor form_class = forms.VendorForm template_name = "vendors/vendor_form.html" success_url = reverse_lazy("vendor_list") success_message = _("Vendor updated successfully.") @login_required def delete_vendor(request, pk): vendor = get_object_or_404(models.Vendor, pk=pk) # vendor.active = False vendor.delete() messages.success(request, _("Vendor deleted successfully.")) return redirect("vendor_list") #group class GroupListView(LoginRequiredMixin, ListView): model = models.CustomGroup context_object_name = "groups" paginate_by = 10 template_name = "groups/group_list.html" def get_queryset(self): dealer = get_user_type(self.request) return dealer.groups.all() class GroupDetailView(LoginRequiredMixin, DetailView): model = models.CustomGroup template_name = "groups/group_detail.html" context_object_name = "group" class GroupCreateView( LoginRequiredMixin, SuccessMessageMixin, CreateView, ): model = models.CustomGroup form_class = forms.GroupForm template_name = "groups/group_form.html" success_url = reverse_lazy("group_list") success_message = _("Group created successfully.") def form_valid(self, form): dealer = get_user_type(self.request) instance = form.save(commit=False) group = Group.objects.create(name=f"{dealer.pk}_{instance.name}") instance.dealer = dealer instance.group = group instance.save() return super().form_valid(form) class GroupUpdateView( LoginRequiredMixin, SuccessMessageMixin, UpdateView, ): model = models.CustomGroup form_class = forms.GroupForm template_name = "groups/group_form.html" success_url = reverse_lazy("group_list") success_message = _("Group updated successfully.") def form_valid(self, form): dealer = get_user_type(self.request) instance = form.save(commit=False) instance.set_defualt_permissions() instance.group.name = f"{dealer.pk}_{instance.name}" instance.save() return super().form_valid(form) @login_required def GroupDeleteview(request, pk): group = get_object_or_404(models.CustomGroup, pk=pk) group.delete() messages.success(request, _("Group deleted successfully.")) return redirect("group_list") @login_required def GroupPermissionView(request, pk): group = get_object_or_404(models.CustomGroup, pk=pk) if request.method == "POST": form = forms.PermissionForm(request.POST) group.clear_permissions() permissions = request.POST.getlist("name") for i in permissions: group.add_permission(Permission.objects.get(id=int(i))) messages.success(request, _("Permission added successfully.")) return redirect("group_detail", pk=group.pk) form = forms.PermissionForm(initial={"name": group.permissions}) return render(request,"groups/group_permission_form.html",{"group": group, "form": form}) # Users @login_required def UserGroupView(request, pk): staff = get_object_or_404(models.Staff, pk=pk) if request.method == "POST": form = forms.UserGroupForm(request.POST) groups = request.POST.getlist("name") staff.clear_groups() for i in groups: cg = models.CustomGroup.objects.get(id=int(i)) staff.add_group(cg.group) messages.success(request, _("Group added successfully.")) return redirect("user_detail", pk=staff.pk) form = forms.UserGroupForm(initial={"name": staff.groups}) form.fields['name'].queryset = models.CustomGroup.objects.filter(dealer=staff.dealer) return render(request,"users/user_group_form.html",{"staff": staff, "form": form}) class UserListView(LoginRequiredMixin, ListView): model = models.Staff context_object_name = "users" paginate_by = 10 template_name = "users/user_list.html" def get_queryset(self): query = self.request.GET.get("q") dealer = get_user_type(self.request) staff = models.Staff.objects.filter(dealer=dealer).all() return apply_search_filters(staff, query) class UserDetailView(LoginRequiredMixin, DetailView): model = models.Staff template_name = "users/user_detail.html" context_object_name = "user_" class UserCreateView( LoginRequiredMixin, SuccessMessageMixin, CreateView, ): model = models.Staff form_class = forms.StaffForm template_name = "users/user_form.html" success_url = reverse_lazy("user_list") success_message = _("User created successfully.") def form_valid(self, form): dealer = get_user_type(self.request) quota_dict = get_user_quota(dealer.user) allowed_users = quota_dict.get("Users") if allowed_users is None: messages.error(self.request, _("The user quota for staff members is not defined. Please contact support.")) return self.form_invalid(form) current_staff_count = dealer.staff.count() if current_staff_count >= allowed_users: messages.error(self.request, _("You have reached the maximum number of staff users allowed for your plan.")) return self.form_invalid(form) email = form.cleaned_data["email"] password = "Tenhal@123" user = User.objects.create_user(username=form.cleaned_data["name"], email=email, password=password) user.is_staff = True user.save() staff_member = StaffMember.objects.create(user=user) for service in form.cleaned_data["service_offered"]: staff_member.services_offered.add(service) staff = form.save(commit=False) staff.staff_member = staff_member staff.dealer = dealer staff.add_as_manager() group = Group.objects.filter(customgroup__name__iexact=staff.staff_type).first() staff.save() if group: staff.add_group(group) return super().form_valid(form) class UserUpdateView( LoginRequiredMixin, SuccessMessageMixin, UpdateView, ): model = models.Staff form_class = forms.StaffForm template_name = "users/user_form.html" success_url = reverse_lazy("user_list") success_message = _("User updated successfully.") def get_form_kwargs(self): kwargs = super().get_form_kwargs() kwargs["instance"] = self.get_object() # Pass the Staff instance to the form return kwargs def get_form(self, form_class = None): form = super().get_form(form_class) form.fields['email'].disabled = True return form def get_initial(self): initial = super().get_initial() initial['email'] = self.object.staff_member.user.email initial['service_offered'] = self.object.staff_member.services_offered.all() return initial def form_valid(self, form): services = form.cleaned_data["service_offered"] if not services: self.object.staff_member.services_offered.clear() else: for service in services: self.object.staff_member.services_offered.add(service) staff = form.save(commit=False) staff.name = form.cleaned_data["name"] staff.arabic_name = form.cleaned_data["arabic_name"] staff.phone_number = form.cleaned_data["phone_number"] staff.staff_type = form.cleaned_data["staff_type"] staff.add_as_manager() staff.save() return super().form_valid(form) @login_required def UserDeleteview(request, pk): staff = get_object_or_404(models.Staff, pk=pk) staff.staff_member.delete() staff.delete() messages.success(request, _("User deleted successfully.")) return redirect("user_list") class OrganizationListView(LoginRequiredMixin, ListView): model = CustomerModel template_name = "organizations/organization_list.html" context_object_name = "organizations" paginate_by = 10 def get_queryset(self): query = self.request.GET.get("q") dealer = get_user_type(self.request) organization = dealer.entity.get_customers().filter(additional_info__type="organization", active=True) return apply_search_filters(organization, query) class OrganizationDetailView(LoginRequiredMixin,DetailView): model = CustomerModel template_name = "organizations/organization_detail.html" context_object_name = "organization" @login_required def OrganizationCreateView(request): if request.method == "POST": form = forms.OrganizationForm(request.POST) if CustomerModel.objects.filter(email=request.POST["email"]).exists(): messages.error(request, _("An organization with this email already exists.")) return redirect("organization_create") organization_dict = { x: request.POST[x] for x in request.POST if x != "csrfmiddlewaretoken" } dealer = get_user_type(request) name = organization_dict["first_name"] + " " + organization_dict["last_name"] customer = dealer.entity.create_customer( commit=False, customer_model_kwargs={ "customer_name": name, "address_1": organization_dict["address"], "phone": organization_dict["phone_number"], "email": organization_dict["email"], } ) image = request.FILES.get("logo") if image: file_name = default_storage.save("images/{}".format(image.name), image) file_url = default_storage.url(file_name) organization_dict["logo"] = file_url organization_dict["pk"] = str(customer.pk) customer.additional_info.update({"customer_info": organization_dict}) customer.additional_info.update({"type": "organization"}) customer.save() messages.success(request, _("Organization created successfully.")) return redirect("organization_list") else: form = forms.OrganizationForm() return render(request, "organizations/organization_form.html", {"form": form}) @login_required def OrganizationUpdateView(request,pk): organization = get_object_or_404(CustomerModel, pk=pk) if request.method == "POST": form = forms.OrganizationForm(request.POST) organization_dict = { x: request.POST[x] for x in request.POST if x != "csrfmiddlewaretoken" } dealer = get_user_type(request) instance = dealer.entity.get_customers().get( pk=organization.additional_info["customer_info"]["pk"] ) name = organization_dict["first_name"] + " " + organization_dict["last_name"] instance.customer_name = name instance.address_1 = organization_dict["address"] instance.phone = organization_dict["phone_number"] instance.email = organization_dict["email"] image = request.FILES.get("logo") if image: file_name = default_storage.save("images/{}".format(image.name), image) file_url = default_storage.url(file_name) organization_dict["logo"] = file_url else: organization_dict["logo"] = organization.additional_info["customer_info"]["logo"] organization_dict["pk"] = str(instance.pk) instance.additional_info["customer_info"] = organization_dict instance.additional_info["type"] = "organization" instance.save() messages.success(request, _("Organization created successfully.")) return redirect("organization_list") else: form = forms.OrganizationForm( initial=organization.additional_info["customer_info"] or {} ) # form.fields.pop("logo", None) return render(request, "organizations/organization_form.html", {"form": form}) # class OrganizationDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteView): # model = models.Organization # template_name = "organizations/organization_confirm_delete.html" # success_url = reverse_lazy("organization_list") # success_message = "Organization deleted successfully." @login_required def OrganizationDeleteView(request, pk): organization = get_object_or_404(CustomerModel, pk=pk) try: User.objects.get(email=organization.email).delete() organization.delete() messages.success(request, _("Organization deleted successfully.")) except Exception as e: print("unable to delete user", e) messages.error(request,_("Unable to delete organization")) return redirect("organization_list") class RepresentativeListView(LoginRequiredMixin, ListView): model = models.Representative template_name = "representatives/representative_list.html" context_object_name = "representatives" paginate_by = 10 def get_queryset(self): query = self.request.GET.get("q") dealer = get_user_type(self.request) representative = models.Representative.objects.filter(dealer=dealer) return apply_search_filters(representative, query) class RepresentativeDetailView(LoginRequiredMixin,DetailView): model = models.Representative template_name = "representatives/representative_detail.html" context_object_name = "representative" class RepresentativeCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView): model = models.Representative form_class = forms.RepresentativeForm template_name = "representatives/representative_form.html" success_url = reverse_lazy("representative_list") success_message = "Representative created successfully." def form_valid(self, form): if form.is_valid(): form.instance.dealer = self.request.user.dealer form.save() return super().form_valid(form) else: return form.errors class RepresentativeUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView): model = models.Representative form_class = forms.RepresentativeForm template_name = "representatives/representative_form.html" success_url = reverse_lazy("representative_list") success_message = "Representative updated successfully." class RepresentativeDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteView): model = models.Representative template_name = "representatives/representative_confirm_delete.html" success_url = reverse_lazy("representative_list") success_message = "Representative deleted successfully." # BANK ACCOUNT class BankAccountListView(LoginRequiredMixin,PermissionRequiredMixin, ListView): model = BankAccountModel template_name = "ledger/bank_accounts/bank_account_list.html" context_object_name = "bank_accounts" paginate_by = 10 permission_required = ['inventory.view_carfinance'] def get_queryset(self): query = self.request.GET.get("q") dealer = get_user_type(self.request) bank_accounts = BankAccountModel.objects.filter(entity_model=dealer.entity) return apply_search_filters(bank_accounts, query) class BankAccountCreateView(LoginRequiredMixin,PermissionRequiredMixin, SuccessMessageMixin, CreateView): model = BankAccountModel form_class = BankAccountCreateForm template_name = "ledger/bank_accounts/bank_account_form.html" success_url = reverse_lazy("bank_account_list") success_message = "Bank account created successfully." permission_required = ['inventory.view_carfinance'] def form_valid(self, form): dealer = get_user_type(self.request) form.instance.entity_model = dealer.entity return super().form_valid(form) def get_form_kwargs(self): dealer = get_user_type(self.request) entity = dealer.entity kwargs = super().get_form_kwargs() kwargs["entity_slug"] = entity.slug kwargs["user_model"] = entity.admin return kwargs class BankAccountDetailView(LoginRequiredMixin,PermissionRequiredMixin, DetailView): model = BankAccountModel template_name = "ledger/bank_accounts/bank_account_detail.html" context_object_name = "bank_account" permission_required = ['inventory.view_carfinance'] class BankAccountUpdateView(LoginRequiredMixin,PermissionRequiredMixin, SuccessMessageMixin, UpdateView): model = BankAccountModel form_class = BankAccountUpdateForm template_name = "ledger/bank_accounts/bank_account_form.html" success_url = reverse_lazy("bank_account_list") success_message = "Bank account updated successfully." permission_required = ['inventory.view_carfinance'] def get_form_kwargs(self): dealer = get_user_type(self.request) entity = dealer.entity kwargs = super().get_form_kwargs() kwargs["entity_slug"] = entity.slug # Get entity_slug from URL kwargs["user_model"] = entity.admin # Get user_model from the request return kwargs @login_required def bank_account_delete(request, pk): bank_account = get_object_or_404(BankAccountModel, pk=pk) if request.method == "POST": bank_account.delete() messages.success(request, "Bank account deleted successfully.") return redirect("bank_account_list") return render( request, "ledger/bank_accounts/bank_account_delete.html", {"bank_account": bank_account}, ) # Accounts class AccountListView(LoginRequiredMixin,PermissionRequiredMixin, ListView): model = AccountModel template_name = "ledger/coa_accounts/account_list.html" context_object_name = "accounts" paginate_by = 10 permission_required = ['inventory.view_carfinance'] def get_queryset(self): query = self.request.GET.get("q") dealer = get_user_type(self.request) accounts = dealer.entity.get_all_accounts() return apply_search_filters(accounts, query) class AccountCreateView(LoginRequiredMixin,PermissionRequiredMixin, SuccessMessageMixin, CreateView): model = AccountModel form_class = AccountModelCreateForm template_name = "ledger/coa_accounts/account_form.html" success_url = reverse_lazy("account_list") success_message = "Account created successfully." permission_required = ['inventory.view_carfinance'] def form_valid(self, form): dealer = get_user_type(self.request) form.instance.entity_model = dealer.entity form.instance.coa_model = dealer.entity.get_default_coa() form.instance.depth = 0 form.instance.path = form.instance.code return super().form_valid(form) def get_form_kwargs(self): dealer = get_user_type(self.request) kwargs = super().get_form_kwargs() kwargs["coa_model"] = dealer.entity.get_default_coa() return kwargs def get_form(self, form_class=None): form = super().get_form(form_class) entity = get_user_type(self.request).entity form.initial['coa_model'] = entity.get_default_coa() return form class AccountDetailView(LoginRequiredMixin,PermissionRequiredMixin, DetailView): model = AccountModel template_name = "ledger/coa_accounts/account_detail.html" context_object_name = "account" slug_field = "uuid" DEFAULT_TXS_DAYS = 30 permission_required = ['inventory.view_carfinance'] extra_context = { "DEFAULT_TXS_DAYS": DEFAULT_TXS_DAYS, "header_subtitle_icon": "ic:round-account-tree", } def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) account_model: AccountModel = context["object"] context["header_title"] = f"Account {account_model.code} - {account_model.name}" context["page_title"] = f"Account {account_model.code} - {account_model.name}" context["total_debits"] = sum( x.amount for x in account_model.transactionmodel_set.filter(tx_type="debit") ) context["total_credits"] = sum( x.amount for x in account_model.transactionmodel_set.filter(tx_type="credit") ) txs_qs = ( account_model.transactionmodel_set.all() .posted() .order_by("journal_entry__timestamp") .select_related( "journal_entry", "journal_entry__entity_unit", "journal_entry__ledger__billmodel", "journal_entry__ledger__invoicemodel", ) ) return context class AccountUpdateView(LoginRequiredMixin,PermissionRequiredMixin, SuccessMessageMixin, UpdateView): model = AccountModel form_class = AccountModelUpdateForm template_name = "ledger/coa_accounts/account_form.html" success_url = reverse_lazy("account_list") success_message = "Account updated successfully." permission_required = ['inventory.view_carfinance'] def get_form(self, form_class=None): form = super().get_form(form_class) form.fields["_ref_node_id"].widget = HiddenInput() form.fields["_position"].widget = HiddenInput() return form @login_required @permission_required('inventory.view_carfinance') def account_delete(request, pk): account = get_object_or_404(AccountModel, pk=pk) account.delete() messages.success(request, "Account deleted successfully.") return redirect("account_list") # Sales list @login_required @permission_required('inventory.view_lead',raise_exception=True) def sales_list_view(request): dealer = get_user_type(request) entity = dealer.entity transactions = ItemTransactionModel.objects.for_entity(entity_slug=entity.slug, user_model=dealer.user) paginator = Paginator(transactions, 10) page_number = request.GET.get('page') page_obj = paginator.get_page(page_number) txs = get_item_transactions(page_obj) context = {"txs": txs, "page_obj": page_obj} return render(request, "sales/sales_list.html", context) # Estimates class EstimateListView(LoginRequiredMixin,PermissionRequiredMixin, ListView): model = EstimateModel template_name = "sales/estimates/estimate_list.html" context_object_name = "estimates" paginate_by = 20 permission_required = ['django_ledger.view_estimatemodel'] def get_queryset(self): dealer = get_user_type(self.request) entity = dealer.entity status = self.request.GET.get('status') queryset = entity.get_estimates() if status: queryset = queryset.filter(status=status) return queryset # @csrf_exempt @login_required @permission_required('django_ledger.add_estimatemodel',raise_exception=True) def create_estimate(request,pk=None): dealer = get_user_type(request) entity = dealer.entity if request.method == "POST": # try: data = json.loads(request.body) title = data.get("title") customer_id = data.get("customer") terms = data.get("terms") customer = entity.get_customers().filter(pk=customer_id).first() items = data.get("item", []) quantities = data.get("quantity", []) if not all([items, quantities]): return JsonResponse( {"status": "error", "message": "Items and Quantities are required"}, status=400, ) if isinstance(quantities, list): if "0" in quantities: return JsonResponse( {"status": "error", "message": "Quantity must be greater than zero"} ) else: if int(quantities) <= 0: return JsonResponse( {"status": "error", "message": "Quantity must be greater than zero"} ) if isinstance(items, list): for item, quantity in zip(items, quantities): if int(quantity) > models.Car.objects.filter(hash=item,status='available').count(): return JsonResponse( {"status": "error", "message": "Quantity must be less than or equal to the number of cars in stock"}, ) else: if int(quantities) > models.Car.objects.filter(hash=items,status='available').count(): return JsonResponse( {"status": "error", "message": "Quantity must be less than or equal to the number of cars in stock"}, ) estimate = entity.create_estimate( estimate_title=title, customer_model=customer, contract_terms=terms ) if isinstance(items, list): item_quantity_map = {} for item, quantity in zip(items, quantities): if item in item_quantity_map: item_quantity_map[item] += int(quantity) else: item_quantity_map[item] = int(quantity) item_list = list(item_quantity_map.keys()) quantity_list = list(item_quantity_map.values()) items_list = [ {"item_id": item_list[i], "quantity": quantity_list[i]} for i in range(len(item_list)) ] items_txs = [] for item in items_list: car_instance = ItemModel.objects.filter(additional_info__car_info__hash=item.get("item_id")).all() for i in car_instance[:int(quantities[0])]: items_txs.append( { "item_number": i.item_number, "quantity": 1, "unit_cost": i.additional_info.get('car_finance').get("selling_price"), "unit_revenue": i.additional_info.get('car_finance').get("selling_price"), "total_amount": (i.additional_info.get('car_finance').get("total_vat")) } ) estimate_itemtxs = { item.get("item_number"): { "unit_cost": item.get("unit_cost"), "unit_revenue": item.get("unit_revenue"), "quantity": item.get("quantity"), "total_amount": item.get("total_amount"), } for item in items_txs } else: item = entity.get_items_all().filter(pk=items).first() instance = models.Car.objects.get(vin=item.name) estimate_itemtxs = { item.item_number: { "unit_cost": instance.finances.cost_price, "unit_revenue": instance.finances.selling_price, "quantity": Decimal(quantities), "total_amount": instance.finances.total_vat * int(quantities), } } estimate.migrate_itemtxs( itemtxs=estimate_itemtxs, commit=True, operation=EstimateModel.ITEMIZE_APPEND, ) if isinstance(items, list): for item in estimate_itemtxs.keys(): item_instance = ItemModel.objects.filter(item_number=item).first() instance = models.Car.objects.get(vin=item_instance.name) reserve_car(instance, request) else: item_instance = ItemModel.objects.filter(additioinal_info__car_info__hash=items).first() instance = models.Car.objects.get(hash=item) response = reserve_car(instance, request) opportunity_id = data.get("opportunity_id") if opportunity_id != "None": opportunity = models.Opportunity.objects.get(pk=int(opportunity_id)) opportunity.estimate = estimate opportunity.save() url = reverse("estimate_detail", kwargs={"pk": estimate.pk}) return JsonResponse( { "status": "success", "message": "Quotation created successfully!", "url": f"{url}", } ) form = forms.EstimateModelCreateForm( entity_slug=entity.slug, user_model=entity.admin ) form.fields["customer"].queryset = entity.get_customers().filter(active=True,additional_info__type="customer") if pk: opportunity = models.Opportunity.objects.get(pk=pk) customer = opportunity.customer form.initial['customer'] = customer car_list = models.Car.objects.filter(dealer=dealer,colors__isnull=False,finances__isnull=False,status="available").annotate(color=F('colors__exterior__rgb'),color_name=F('colors__exterior__name')).values_list( 'id_car_make__name', 'id_car_model__name','id_car_serie__name','id_car_trim__name','color','color_name','hash').annotate(hash_count=Count('hash')).distinct() context = { "form": form, "items": [ { 'make':x[0], 'model':x[1], 'serie':x[2], 'trim':x[3], 'color':x[4], 'color_name':x[5], 'hash': x[6], 'hash_count': x[7] } for x in car_list ], "opportunity_id": pk if pk else None, "customer_count": entity.get_customers().count() } return render(request, "sales/estimates/estimate_form.html", context) class EstimateDetailView(LoginRequiredMixin, PermissionRequiredMixin,DetailView): model = EstimateModel template_name = "sales/estimates/estimate_detail.html" context_object_name = "estimate" permission_required = ['django_ledger.view_estimatemodel'] def get_context_data(self, **kwargs): estimate = kwargs.get("object") if estimate.get_itemtxs_data(): calculator = CarFinanceCalculator(estimate) finance_data = calculator.get_finance_data() kwargs["data"] = finance_data kwargs["invoice"] = ( InvoiceModel.objects.all().filter(ce_model=estimate).first() ) return super().get_context_data(**kwargs) @login_required @permission_required('inventory.add_saleorder', raise_exception=True) def create_sale_order(request, pk): estimate = get_object_or_404(EstimateModel, pk=pk) items = estimate.get_itemtxs_data()[0].all() if request.method == "POST": form = forms.SaleOrderForm(request.POST) if form.is_valid(): form.save() if not estimate.is_approved(): estimate.mark_as_approved() estimate.save() for item in estimate.get_itemtxs_data()[0].all(): try: item.item_model.additional_info['car_info']['status'] = 'sold' item.item_model.save() except KeyError: pass models.Car.objects.get(vin=item.item_model.name).mark_as_sold(request) messages.success(request, "Sale Order created successfully") return redirect("estimate_detail", pk=pk) form = forms.SaleOrderForm() form.fields["estimate"].queryset = EstimateModel.objects.filter(pk=pk) form.initial["estimate"] = estimate calculator = CarFinanceCalculator(estimate) finance_data = calculator.get_finance_data() return render( request, "sales/estimates/sale_order_form.html", {"form": form, "estimate": estimate, "items": items, "data": finance_data}, ) @login_required def preview_sale_order(request, pk): estimate = get_object_or_404(EstimateModel, pk=pk) data = get_car_finance_data(estimate) return render( request, "sales/estimates/sale_order_preview.html", {"order": estimate.sale_orders.first(), "data": data, "estimate": estimate}, ) class PaymentRequest(LoginRequiredMixin,PermissionRequiredMixin,DetailView): model = EstimateModel template_name = "sales/estimates/payment_request_detail.html" context_object_name = "estimate" permission_required = ['django_ledger.view_invoicemodel'] def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["cars"] = [ models.Car.objects.get(vin=car.item_model.name) for car in context["estimate"].get_itemtxs_data()[0].all() ] return context class EstimatePreviewView(LoginRequiredMixin,PermissionRequiredMixin,DetailView): model = EstimateModel context_object_name = "estimate" template_name = "sales/estimates/estimate_preview.html" permission_required = ['django_ledger.view_estimatemodel'] def get_context_data(self, **kwargs): dealer = get_user_type(self.request) estimate = kwargs.get("object") if estimate.get_itemtxs_data(): data = get_financial_values(estimate) kwargs["vat_amount"] = data["vat_amount"] kwargs["total"] = data["grand_total"] kwargs["discount_amount"] = data["discount_amount"] kwargs["vat"] = data["vat"] kwargs["additional_services"] = data["additional_services"] kwargs["dealer"] = dealer return super().get_context_data(**kwargs) @login_required @permission_required('django_ledger.change_estimatemodel', raise_exception=True) def estimate_mark_as(request, pk): estimate = get_object_or_404(EstimateModel, pk=pk) dealer = get_user_type(request) entity = dealer.entity mark = request.GET.get("mark") if mark: if mark == "review": if not estimate.can_review(): messages.error(request, _("Estimate is not ready for review")) return redirect("estimate_detail", pk=estimate.pk) estimate.mark_as_review() elif mark == "approved": if not estimate.can_approve(): messages.error(request, _("Estimate is not ready for approval")) return redirect("estimate_detail", pk=estimate.pk) estimate.mark_as_approved() messages.success(request, _("Estimate approved successfully.")) elif mark == "rejected": if not estimate.can_cancel(): messages.error(request, _("Estimate is not ready for rejection")) return redirect("estimate_detail", pk=estimate.pk) estimate.mark_as_canceled() messages.success(request, _("Estimate canceled successfully.")) elif mark == "completed": if not estimate.can_complete(): messages.error(request, _("Estimate is not ready for completion")) return redirect("estimate_detail", pk=estimate.pk) elif mark == "canceled": if not estimate.can_cancel(): messages.error(request, _("Estimate is not ready for cancelation")) return redirect("estimate_detail", pk=estimate.pk) estimate.mark_as_canceled() try: car = models.Car.objects.get(vin=estimate.get_itemtxs_data()[0].first().item_model.name) car.status = "available" car.save() except Exception as e: pass messages.success(request, _("Estimate canceled successfully.")) estimate.save() messages.success(request, "Estimate marked as " + mark.upper()) return redirect("estimate_detail", pk=estimate.pk) # Invoice class InvoiceListView(LoginRequiredMixin,PermissionRequiredMixin, ListView): model = InvoiceModel template_name = "sales/invoices/invoice_list.html" context_object_name = "invoices" paginate_by = 20 permission_required = ['django_ledger.view_invoicemodel'] def get_queryset(self): query = self.request.GET.get("q") dealer = get_user_type(self.request) invoices = dealer.entity.get_invoices() return apply_search_filters(invoices, query) class InvoiceDetailView(LoginRequiredMixin,PermissionRequiredMixin, DetailView): model = InvoiceModel template_name = "sales/invoices/invoice_detail.html" context_object_name = "invoice" permission_required = ['django_ledger.view_invoicemodel'] def get_context_data(self, **kwargs): invoice = kwargs.get("object") if invoice.get_itemtxs_data(): calculator = CarFinanceCalculator(invoice) finance_data = calculator.get_finance_data() kwargs["data"] = finance_data kwargs["payments"] = JournalEntryModel.objects.filter( ledger=invoice.ledger ).all() return super().get_context_data(**kwargs) class DraftInvoiceModelUpdateFormView(LoginRequiredMixin,PermissionRequiredMixin, UpdateView): model = InvoiceModel form_class = DraftInvoiceModelUpdateForm template_name = "sales/invoices/draft_invoice_update.html" success_url = reverse_lazy("invoice_list") permission_required = ['django_ledger.view_invoicemodel'] def get_form_kwargs(self): kwargs = super().get_form_kwargs() dealer = get_user_type(self.request) kwargs["entity_slug"] = dealer.entity kwargs["user_model"] = dealer.entity.admin return kwargs class ApprovedInvoiceModelUpdateFormView(LoginRequiredMixin,PermissionRequiredMixin, UpdateView): model = InvoiceModel form_class = ApprovedInvoiceModelUpdateForm template_name = "sales/invoices/approved_invoice_update.html" success_url = reverse_lazy("invoice_list") permission_required = ['django_ledger.view_invoicemodel'] def get_form_kwargs(self): kwargs = super().get_form_kwargs() dealer = get_user_type(self.request) kwargs["entity_slug"] = dealer.entity kwargs["user_model"] = dealer.entity.admin return kwargs def get_success_url(self): return reverse_lazy("invoice_detail", kwargs={"pk": self.object.pk}) class PaidInvoiceModelUpdateFormView(LoginRequiredMixin,PermissionRequiredMixin, UpdateView): model = InvoiceModel form_class = PaidInvoiceModelUpdateForm template_name = "sales/invoices/paid_invoice_update.html" success_url = reverse_lazy("invoice_list") permission_required = ['django_ledger.view_invoicemodel'] def get_form_kwargs(self): kwargs = super().get_form_kwargs() dealer = get_user_type(self.request) kwargs["entity_slug"] = dealer.entity kwargs["user_model"] = dealer.entity.admin return kwargs def get_success_url(self): return reverse_lazy("invoice_detail", kwargs={"pk": self.object.pk}) def form_valid(self, form): invoice = form.save() if invoice.get_amount_open() > 0: messages.error(self.request, "Invoice is not fully paid") return redirect("invoice_detail", pk=invoice.pk) else: invoice.post_ledger() invoice.save() return super().form_valid(form) @login_required @permission_required('django_ledger.change_invoicemodel',raise_exception=True) def invoice_mark_as(request, pk): invoice = get_object_or_404(InvoiceModel, pk=pk) dealer = get_user_type(request) mark = request.GET.get("mark") if mark and mark == "accept": if not invoice.can_approve(): messages.error(request, "invoice is not ready for approval") return redirect("invoice_detail", pk=invoice.pk) invoice.mark_as_approved( entity_slug=dealer.entity.slug, user_model=dealer.entity.admin ) invoice.save() return redirect("invoice_detail", pk=invoice.pk) @login_required @permission_required('django_ledger.add_invoicemodel',raise_exception=True) def invoice_create(request, pk): estimate = get_object_or_404(EstimateModel, pk=pk) dealer = get_user_type(request) entity = dealer.entity if request.method == "POST": form = forms.InvoiceModelCreateForm( request.POST, entity_slug=entity.slug, user_model=entity.admin ) if form.is_valid(): invoice = form.save(commit=False) ledger = entity.create_ledger(name=str(invoice.pk)) invoice.ledgar = ledger ledger.invoicemodel = invoice ledger.save() invoice.save() calculator = CarFinanceCalculator(estimate) finance_data = calculator.get_finance_data() invoice_itemtxs = { i.get("item_number"): { "unit_cost": i.get("total_vat"), "quantity": i.get("quantity"), "total_amount": i.get("total_vat"), } for i in finance_data.get("cars") } invoice_itemtxs = invoice.migrate_itemtxs( itemtxs=invoice_itemtxs, commit=True, operation=InvoiceModel.ITEMIZE_APPEND, ) invoice.bind_estimate(estimate) invoice.mark_as_review() estimate.mark_as_completed() estimate.save() invoice.save() messages.success(request, "Invoice created successfully!") return redirect("invoice_detail", pk=invoice.pk) form = forms.InvoiceModelCreateForm( entity_slug=entity.slug, user_model=entity.admin ) form.initial.update( { "customer": estimate.customer, "cash_account": dealer.settings.invoice_cash_account, "prepaid_account": dealer.settings.invoice_prepaid_account, "unearned_account": dealer.settings.invoice_unearned_account, } ) context = { "form": form, "estimate": estimate, } return render(request, "sales/invoices/invoice_create.html", context) class InvoicePreviewView(LoginRequiredMixin,PermissionRequiredMixin, DetailView): model = InvoiceModel context_object_name = "invoice" template_name = "sales/invoices/invoice_preview.html" permission_required = ['django_ledger.view_invoicemodel'] def get_context_data(self, **kwargs): dealer = get_user_type(self.request) invoice = kwargs.get("object") if invoice.get_itemtxs_data(): calculator = CarFinanceCalculator(invoice) finance_data = calculator.get_finance_data() kwargs["data"] = finance_data kwargs['dealer'] = dealer return super().get_context_data(**kwargs) # payments @login_required @permission_required('django_ledger.add_journalentrymodel',raise_exception=True) def PaymentCreateView(request, pk): invoice = InvoiceModel.objects.filter(pk=pk).first() bill = BillModel.objects.filter(pk=pk).first() model = invoice if invoice else bill dealer = get_user_type(request) entity = dealer.entity form = forms.PaymentForm() if request.method == "POST": form = forms.PaymentForm(request.POST) if form.is_valid(): amount = form.cleaned_data.get("amount") invoice = form.cleaned_data.get("invoice") bill = form.cleaned_data.get("bill") payment_method = form.cleaned_data.get("payment_method") redirect_url = "invoice_detail" if invoice else "bill_detail" model = invoice if invoice else bill if not model.is_approved(): model.mark_as_approved(user_model=entity.admin) if model.amount_paid == model.amount_due: messages.error(request,"fully paid") return redirect(redirect_url, pk=model.pk) if model.amount_paid + amount > model.amount_due: messages.error(request,"Amount exceeds due amount") return redirect(redirect_url, pk=model.pk) try: if invoice: set_invoice_payment(dealer, entity, invoice, amount, payment_method) elif bill: set_bill_payment(dealer, entity, bill, amount, payment_method) messages.success(request, "Payment created successfully!") return redirect(redirect_url, pk=model.pk) except Exception as e: messages.error(request, f"Error creating payment: {str(e)}") else: messages.error(request, f"Invalid form data: {str(form.errors)}") form = forms.PaymentForm() if model: form.initial["amount"] = model.amount_due - model.amount_paid if isinstance(model, InvoiceModel): form.initial["invoice"] = model form.fields["bill"].widget = HiddenInput() elif isinstance(model, BillModel): form.initial["bill"] = model form.fields["invoice"].widget = HiddenInput() return render( request, "sales/payments/payment_form.html", {"model": model, "form": form} ) @login_required @permission_required('django_ledger.view_journalentrymodel',raise_exception=True) def PaymentListView(request): dealer = get_user_type(request) entity = dealer.entity journals = JournalEntryModel.objects.filter(ledger__entity=entity).all() return render(request, "sales/payments/payment_list.html", {"journals": journals}) @login_required @permission_required('django_ledger.view_journalentrymodel',raise_exception=True) def PaymentDetailView(request, pk): journal = JournalEntryModel.objects.filter(pk=pk).first() transactions = ( TransactionModel.objects.filter(journal_entry=journal) .order_by("account__code") .all() ) return render( request, "sales/payments/payment_details.html", {"journal": journal, "transactions": transactions}, ) @login_required @permission_required('django_ledger.change_journalentrymodel',raise_exception=True) def payment_mark_as_paid(request, pk): invoice = get_object_or_404(InvoiceModel, pk=pk) if request.method == "POST": try: if invoice.amount_due == invoice.amount_paid: if not invoice.is_paid() and invoice.can_pay(): invoice.mark_as_paid( entity_slug=invoice.ledger.entity.slug, user_model=invoice.ledger.entity.admin, ) invoice.save() invoice.ledger.lock_journal_entries() invoice.ledger.post_journal_entries() invoice.ledger.post() invoice.ledger.save() messages.success(request, "Payment created successfully!") else: messages.error( request, "Invoice is not fully paid. Payment cannot be marked as paid.", ) except Exception as e: messages.error(request, f"Error: {str(e)}") return redirect("invoice_detail", pk=invoice.pk) # activity log class UserActivityLogListView(LoginRequiredMixin,ListView): model = models.UserActivityLog template_name = "dealers/activity_log.html" context_object_name = "logs" paginate_by = 20 def get_queryset(self): queryset = super().get_queryset() if "user" in self.request.GET: queryset = queryset.filter(user__email=self.request.GET["user"]) return queryset # CRM RELATED VIEWS class LeadListView(LoginRequiredMixin,PermissionRequiredMixin,ListView): model = models.Lead template_name = "crm/leads/lead_list.html" context_object_name = "leads" paginate_by = 10 permission_required = ['inventory.view_lead'] def get_queryset(self): dealer = get_user_type(self.request) qs = models.Lead.objects.filter(dealer=dealer) if self.request.is_dealer: return qs staffmember = getattr(self.request.user, "staffmember", None) if staffmember and getattr(staffmember, "staff", None): return qs.filter(staff=staffmember.staff) return models.Lead.objects.none() class LeadDetailView(LoginRequiredMixin,PermissionRequiredMixin,DetailView): model = models.Lead template_name = "crm/leads/lead_detail.html" permission_required = ['inventory.view_lead'] def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["notes"] = models.Notes.objects.filter( content_type__model="lead", object_id=self.object.id ) email_qs = models.Email.objects.filter( content_type__model="lead", object_id=self.object.id ) context["emails"] = { "sent": email_qs.filter(status="SENT"), "draft": email_qs.filter(status="DRAFT"), } context["activities"] = models.Activity.objects.filter( content_type__model="lead", object_id=self.object.id ) context["status_history"] = models.LeadStatusHistory.objects.filter( lead=self.object ) context["transfer_form"] = forms.LeadTransferForm() return context @login_required @permission_required('inventory.add_lead',raise_exception=True) def lead_create(request): form = forms.LeadForm() make = request.GET.get("id_car_make", None) if make: form.fields['id_car_model'].queryset = models.CarModel.objects.filter(id_car_make=int(make)) if request.method == "POST": form = forms.LeadForm(request.POST) # Filter car models based on the selected make (for POST requests) if 'id_car_make' in request.POST: form.fields['id_car_model'].queryset = models.CarModel.objects.filter( id_car_make=int(request.POST['id_car_make']) ) try: if form.is_valid(): instance = form.save(commit=False) dealer = get_user_type(request) instance.dealer = dealer instance.staff = form.cleaned_data.get("staff") # creating customer in ledger customer = dealer.entity.get_customers().filter(email=instance.email).first() if not customer: customer = dealer.entity.create_customer( commit=False, customer_model_kwargs={ "customer_name": instance.full_name, "address_1": instance.address, "phone": instance.phone_number, "email": instance.email, "sales_tax_rate": 0.15, } ) customer_info = { "first_name": instance.first_name, "last_name": instance.last_name, "address": instance.address, "phone_number": str(instance.phone_number), "email": instance.email, "crn": form.cleaned_data["crn"], "vrn": form.cleaned_data["vrn"], } customer.additional_info.update({"customer_info": customer_info }) customer.additional_info.update({"type":"lead"}) customer.save() instance.customer = customer # try: # user = User.objects.get(email=customer.email) # user.first_name = instance.first_name # user.last_name = instance.last_name # user.save() # except Exception as e: # print(e) instance.save() messages.success(request, "Lead created successfully!") return redirect("lead_list") except Exception as e: messages.error(request, f"Lead was not created ... : {str(e)}") return render(request, "crm/leads/lead_form.html", {"form": form}) class LeadUpdateView(LoginRequiredMixin,PermissionRequiredMixin,UpdateView): model = models.Lead form_class = forms.LeadForm template_name = "crm/leads/lead_form.html" success_url = reverse_lazy("lead_list") permission_required = ['inventory.change_lead'] def get_form(self, form_class=None): form = super().get_form(form_class) form.fields["id_car_model"].queryset = form.instance.id_car_make.carmodel_set.all() return form @login_required @permission_required('inventory.delete_lead',raise_exception=True) def LeadDeleteView(request,pk): lead = get_object_or_404(models.Lead, pk=pk) try: User.objects.get(email=lead.customer.email).delete() lead.customer.delete() except Exception as e: print(e) lead.delete() messages.success(request, "Lead deleted successfully!") return redirect("lead_list") @login_required @permission_required('inventory.add_notes',raise_exception=True) def add_note_to_lead(request, pk): lead = get_object_or_404(models.Lead, pk=pk) if request.method == "POST": form = forms.NoteForm(request.POST) if form.is_valid(): note = form.save(commit=False) note.content_object = lead note.created_by = request.user note.save() messages.success(request, "Note added successfully!") return redirect("lead_detail", pk=lead.pk) else: form = forms.NoteForm() return render(request, "crm/note_form.html", {"form": form, "lead": lead}) @login_required @permission_required('inventory.add_notes',raise_exception=True) def add_note_to_opportunity(request, pk): opportunity = get_object_or_404(models.Opportunity, pk=pk) if request.method == "POST": notes = request.POST.get("notes") if not notes: messages.error(request, "Notes field is required.") else: models.Notes.objects.create(content_object=opportunity, created_by=request.user,note=notes) messages.success(request, "Note added successfully!") return redirect("opportunity_detail", pk=opportunity.pk) @login_required @permission_required('inventory.change_notes',raise_exception=True) def update_note(request, pk): note = get_object_or_404(models.Notes, pk=pk, created_by=request.user) lead_pk = note.content_object.pk if request.method == "POST": form = forms.NoteForm(request.POST, instance=note) if form.is_valid(): updated_note = form.save(commit=False) updated_note.content_object = note.content_object updated_note.created_by = request.user updated_note.save() messages.success(request, "Note updated successfully!") return redirect("lead_detail", pk=lead_pk) else: form = forms.NoteForm(instance=note) return render(request, "crm/note_form.html", {"form": form, "note": note}) @login_required @permission_required('inventory.delete_notes',raise_exception=True) def delete_note(request, pk): note = get_object_or_404(models.Notes, pk=pk, created_by=request.user) lead_pk = note.content_object.pk note.delete() messages.success(request, _("Note deleted successfully.")) return redirect("lead_detail", pk=lead_pk) @login_required @permission_required('inventory.change_lead',raise_exception=True) def lead_convert(request, pk): lead = get_object_or_404(models.Lead, pk=pk) dealer = get_user_type(request) if hasattr(lead, "opportunity"): messages.error(request, "Lead is already converted to customer.") else: customer = lead.convert_to_customer(dealer.entity,lead) models.Opportunity.objects.create(dealer=dealer,customer=customer,lead=lead,probability=50,stage=models.Stage.PROSPECT,staff=lead.staff,status=models.Status.QUALIFIED) messages.success(request, "Lead converted to customer successfully!") return redirect("lead_list") @login_required @permission_required('inventory.add_appointmentrequest',raise_exception=True) def schedule_lead(request, pk): if not request.is_staff: messages.error(request, "You do not have permission to schedule lead.") return redirect("lead_list") dealer = get_user_type(request) lead = get_object_or_404(models.Lead, pk=pk, dealer=dealer) if request.method == "POST": form = forms.ScheduleForm(request.POST) if form.is_valid(): instance = form.save(commit=False) instance.lead = lead instance.scheduled_by = request.user instance.customer = lead.customer # Create AppointmentRequest service = Service.objects.filter(name=instance.scheduled_type).first() if not service: messages.error(request, "Service not found!") return redirect("lead_list") try: appointment_request = AppointmentRequest.objects.create( date=instance.scheduled_at.date(), start_time=instance.scheduled_at.time(), end_time=(instance.scheduled_at + instance.duration).time(), service=service, staff_member=request.user.staffmember, ) except ValidationError as e: messages.error(request, str(e)) return redirect("schedule_lead", pk=lead.pk) client = get_object_or_404(User, email=lead.email) # Create Appointment Appointment.objects.create( client=client, appointment_request=appointment_request, phone=lead.phone_number, address=lead.address, ) instance.save() messages.success(request, "Lead scheduled and appointment created successfully!") return redirect("lead_list") else: messages.error(request, f"Invalid form data: {str(form.errors)}") return redirect("lead_list") form = forms.ScheduleForm() return render(request, "crm/leads/schedule_lead.html", {"lead": lead, "form": form}) @login_required @permission_required('inventory.change_lead',raise_exception=True) def lead_transfer(request,pk): lead = get_object_or_404(models.Lead, pk=pk) if request.method == "POST": form = forms.LeadTransferForm(request.POST) if form.is_valid(): lead.staff = form.cleaned_data["transfer_to"] lead.save() messages.success(request, "Lead transferred successfully!") else: messages.error(request, f"Invalid form data: {str(form.errors)}") return redirect("lead_list") @login_required @permission_required('inventory.add_email',raise_exception=True) def send_lead_email(request, pk,email_pk=None): lead = get_object_or_404(models.Lead, pk=pk) status = request.GET.get("status") dealer = get_user_type(request) if status == 'draft': models.Email.objects.create(content_object=lead, created_by=request.user,from_email="manager@tenhal.com", to_email=request.GET.get("to"), subject=request.GET.get("subject"), message=request.GET.get("message"),status=models.EmailStatus.DRAFT) models.Activity.objects.create(dealer=dealer,content_object=lead, notes="Email Draft",created_by=request.user,activity_type=models.ActionChoices.EMAIL) messages.success(request, _("Email Draft successfully!")) response = HttpResponse(redirect("lead_detail", pk=lead.pk)) response['HX-Redirect'] = reverse('lead_detail', args=[lead.pk]) return response lead.status = models.Status.CONTACTED lead.save() # lead.convert_to_customer(dealer.entity) if request.method == "POST": email_pk = request.POST.get('email_pk') if email_pk not in [None,"None",""]: email = get_object_or_404(models.Email, pk=int(email_pk)) email.status = models.EmailStatus.SENT email.save() else: models.Email.objects.create(content_object=lead, created_by=request.user,from_email="manager@tenhal.com", to_email=request.POST.get("to"), subject=request.POST.get("subject"), message=request.POST.get("message"),status=models.EmailStatus.SENT) send_email( "manager@tenhal.com", request.POST.get("to"), request.POST.get("subject"), request.POST.get("message"), ) dealer = get_user_type(request) models.Activity.objects.create(dealer=dealer,content_object=lead, notes="Email sent",created_by=request.user,activity_type=models.ActionChoices.EMAIL) messages.success(request, _("Email sent successfully!")) return redirect("lead_list") msg = f""" السلام عليكم Dear {lead.full_name}, أود أن أشارككم تقدير المشروع الذي ناقشناه. يرجى العثور على الوثيقة التفصيلية للمقترح المرفقة. I hope this email finds you well. I wanted to share with you the estimate for the project we discussed. Please find the detailed estimate document attached. يرجى مراجعة المقترح وإعلامي إذا كانت لديك أي أسئلة أو مخاوف. إذا كانت كل شيء يبدو جيدًا، يمكننا المضي قدمًا في المشروع. Please review the estimate and let me know if you have any questions or concerns. If everything looks good, we can proceed with the project. شكراً لاهتمامكم بهذا الأمر. Thank you for your attention to this matter. تحياتي, Best regards, [Your Name] [Your Position] [Your Company] [Your Contact Information] """ subject = "" if email_pk: email = get_object_or_404(models.Email, pk=email_pk) msg = email.message subject = email.subject return render( request, "crm/leads/lead_send.html", {"lead": lead, "message": msg,"subject":subject, "email_pk": email_pk}, ) @login_required def add_activity_to_lead(request, pk): lead = get_object_or_404(models.Lead, pk=pk) if request.method == "POST": form = forms.ActivityForm(request.POST) if form.is_valid(): activity = form.save(commit=False) activity.content_object = lead activity.created_by = request.user activity.save() return redirect("lead_detail", pk=pk) else: form = forms.ActivityForm() return render(request, "crm/add_activity.html", {"form": form, "lead": lead}) class OpportunityCreateView(CreateView, LoginRequiredMixin): model = models.Opportunity form_class = forms.OpportunityForm template_name = "crm/opportunities/opportunity_form.html" def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) dealer = get_user_type(self.request) return context def get_initial(self): initial = super().get_initial() if self.kwargs.get('pk',None): lead = models.Lead.objects.get(pk=self.kwargs.get('pk')) initial['customer'] = lead.customer return initial def form_valid(self, form): dealer = get_user_type(self.request) form.instance.dealer = dealer return super().form_valid(form) def get_success_url(self): return reverse_lazy("opportunity_detail", kwargs={"pk": self.object.pk}) class OpportunityUpdateView(LoginRequiredMixin,UpdateView): model = models.Opportunity form_class = forms.OpportunityForm template_name = "crm/opportunities/opportunity_form.html" def get_success_url(self): return reverse_lazy("opportunity_detail", kwargs={"pk": self.object.pk}) class OpportunityDetailView(LoginRequiredMixin,DetailView): model = models.Opportunity template_name = "crm/opportunities/opportunity_detail.html" context_object_name = "opportunity" def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) form = forms.OpportunityStatusForm() url = reverse("opportunity_update_status", args=[self.object.pk]) form.fields["status"].widget.attrs["hx-get"] = url form.fields["stage"].widget.attrs["hx-get"] = url form.fields["status"].initial = self.object.status form.fields["stage"].initial = self.object.stage context["status_form"] = form context["notes"] = models.Notes.objects.filter(content_type__model="opportunity", object_id=self.object.id) context["activities"] = models.Activity.objects.filter(content_type__model="opportunity", object_id=self.object.id) email_qs = models.Email.objects.filter(content_type__model="opportunity", object_id=self.object.id) context["emails"] = { "sent": email_qs.filter(status="SENT"), "draft": email_qs.filter(status="DRAFT"), } return context class OpportunityListView(LoginRequiredMixin,ListView): model = models.Opportunity template_name = "crm/opportunities/opportunity_list.html" context_object_name = "opportunities" paginate_by = 10 def get_queryset(self): dealer = get_user_type(self.request) return models.Opportunity.objects.filter(dealer=dealer).all() @login_required def delete_opportunity(request, pk): opportunity = get_object_or_404(models.Opportunity, pk=pk) opportunity.delete() messages.success(request, _("Opportunity deleted successfully.")) return redirect("opportunity_list") @login_required def opportunity_update_status(request,pk): opportunity = get_object_or_404(models.Opportunity, pk=pk) status = request.GET.get("status") stage = request.GET.get("stage") if status: opportunity.status = status if stage: opportunity.stage = stage opportunity.save() messages.success(request,"Opportunity status updated successfully") response = HttpResponse(redirect("opportunity_detail",pk=opportunity.pk)) response['HX-Refresh'] = 'true' return response class NotificationListView(LoginRequiredMixin, ListView): model = models.Notification template_name = "crm/notifications_history.html" context_object_name = "notifications" paginate_by = 20 ordering = "-created" def get_queryset(self): return models.Notification.objects.filter(user=self.request.user) @login_required def mark_notification_as_read(request, pk): notification = get_object_or_404(models.Notification, pk=pk, user=request.user) notification.is_read = True notification.save() messages.success(request, _("Notification marked as read.")) return redirect("notifications_history") @login_required def fetch_notifications(request): notifications = models.Notification.objects.filter( user=request.user, is_read=False ).order_by("-created") return render(request, "notifications.html", {"notifications_": notifications}) class ItemServiceCreateView(LoginRequiredMixin,PermissionRequiredMixin, SuccessMessageMixin, CreateView): model = models.AdditionalServices form_class = forms.AdditionalServiceForm template_name = "items/service/service_create.html" success_url = reverse_lazy("item_service_list") success_message = _("Service created successfully.") context_object_name = "service" permission_required = ["django_ledger.add_itemmodel"] def form_valid(self, form): vat = models.VatRate.objects.get(is_active=True) dealer = get_user_type(self.request) form.instance.dealer = dealer if form.instance.taxable: form.instance.price = (form.instance.price * vat.rate) + form.instance.price return super().form_valid(form) class ItemServiceUpdateView(LoginRequiredMixin,PermissionRequiredMixin, SuccessMessageMixin, UpdateView): model = models.AdditionalServices form_class = forms.AdditionalServiceForm template_name = "items/service/service_create.html" success_url = reverse_lazy("item_service_list") success_message = _("Service updated successfully.") context_object_name = "service" permission_required = ["django_ledger.change_itemmodel"] def form_valid(self, form): vat = models.VatRate.objects.get(is_active=True) dealer = get_user_type(self.request) form.instance.dealer = dealer if form.instance.taxable: form.instance.price = (form.instance.price * vat.rate) + form.instance.price return super().form_valid(form) class ItemServiceListView(LoginRequiredMixin,PermissionRequiredMixin,ListView): model = models.AdditionalServices template_name = "items/service/service_list.html" context_object_name = "services" paginate_by = 20 permission_required = ["django_ledger.view_itemmodel"] def get_queryset(self): dealer = get_user_type(self.request) return models.AdditionalServices.objects.filter(dealer=dealer).all() class ItemExpenseCreateView(LoginRequiredMixin,PermissionRequiredMixin, CreateView): model = ItemModel form_class = ExpenseItemCreateForm template_name = "items/expenses/expense_create.html" success_url = reverse_lazy("item_expense_list") permission_required = ["django_ledger.add_itemmodel"] def get_form_kwargs(self): dealer = get_user_type(self.request) kwargs = super().get_form_kwargs() kwargs["entity_slug"] = dealer.entity.slug kwargs["user_model"] = dealer.entity.admin return kwargs def form_valid(self, form): dealer = get_user_type(self.request) form.instance.entity = dealer.entity return super().form_valid(form) class ItemExpenseUpdateView(LoginRequiredMixin,PermissionRequiredMixin, UpdateView): model = ItemModel form_class = ExpenseItemUpdateForm template_name = "items/expenses/expense_update.html" success_url = reverse_lazy("item_expense_list") permission_required = ["django_ledger.change_itemmodel"] def get_form_kwargs(self): dealer = get_user_type(self.request) kwargs = super().get_form_kwargs() kwargs["entity_slug"] = dealer.entity.slug kwargs["user_model"] = dealer.entity.admin return kwargs def form_valid(self, form): dealer = get_user_type(self.request) form.instance.entity = dealer.entity return super().form_valid(form) class ItemExpenseListView(LoginRequiredMixin,PermissionRequiredMixin,ListView): model = ItemModel template_name = "items/expenses/expenses_list.html" context_object_name = "expenses" paginate_by = 20 permission_required = ["django_ledger.view_itemmodel"] def get_queryset(self): dealer = get_user_type(self.request) return dealer.entity.get_items_expenses() class BillListView(LoginRequiredMixin,PermissionRequiredMixin, ListView): model = BillModel template_name = "ledger/bills/bill_list.html" context_object_name = "bills" permission_required = ["django_ledger.view_billmodel"] def get_queryset(self): dealer = get_user_type(self.request) qs = dealer.entity.get_bills() return qs class BillDetailView(LoginRequiredMixin,PermissionRequiredMixin, DetailView): model = BillModel template_name = "ledger/bills/bill_detail.html" context_object_name = "bill" permission_required = ["django_ledger.view_billmodel"] def get_context_data(self, **kwargs): bill = kwargs.get("object") if bill.get_itemtxs_data(): txs = bill.get_itemtxs_data()[0] transactions = [ { "item": x, "total": Decimal(x.unit_cost) * Decimal(x.quantity), } for x in txs ] grand_total = sum( Decimal(x.unit_cost) * Decimal(x.quantity) for x in txs ) kwargs["transactions"] = transactions kwargs["grand_total"] = grand_total return super().get_context_data(**kwargs) class InReviewBillView(LoginRequiredMixin,PermissionRequiredMixin, UpdateView): model = BillModel form_class = InReviewBillModelUpdateForm template_name = "ledger/bills/bill_update_form.html" success_url = reverse_lazy("bill_list") success_message = _("Bill updated successfully.") context_object_name = "bill" permission_required = ["django_ledger.change_billmodel"] def get_form_kwargs(self): kwargs = super().get_form_kwargs() dealer = get_user_type(self.request) kwargs["entity_model"] = dealer.entity kwargs["user_model"] = dealer.entity.admin return kwargs def get_success_url(self): return reverse_lazy("bill_detail", kwargs={"pk": self.kwargs["pk"]}) def form_valid(self, form): dealer = get_user_type(self.request) form.instance.entity = dealer.entity self.object.mark_as_review() return super().form_valid(form) class ApprovedBillModelView(LoginRequiredMixin,PermissionRequiredMixin, UpdateView): model = BillModel form_class = ApprovedBillModelUpdateForm template_name = "ledger/bills/bill_update_form.html" success_url = reverse_lazy("bill_list") success_message = _("Bill updated successfully.") context_object_name = "bill" permission_required = ["django_ledger.change_billmodel"] def get_form_kwargs(self): kwargs = super().get_form_kwargs() dealer = get_user_type(self.request) kwargs["entity_model"] = dealer.entity kwargs["user_model"] = dealer.entity.admin return kwargs def get_success_url(self): return reverse_lazy("bill_detail", kwargs={"pk": self.kwargs["pk"]}) def form_valid(self, form): dealer = get_user_type(self.request) form.instance.entity = dealer.entity if not self.object.is_approved(): self.object.mark_as_approved(user_model=dealer.entity.admin) return super().form_valid(form) @login_required @permission_required("django_ledger.change_billmodel", raise_exception=True) def bill_mark_as_approved(request, pk): bill = get_object_or_404(BillModel, pk=pk) if request.method == "POST": dealer = get_user_type(request) if bill.is_approved(): messages.error(request, _("Bill is already approved.")) return redirect("bill_detail", pk=bill.pk) bill.mark_as_approved(user_model=dealer.entity.admin) bill.save() messages.success(request, _("Bill marked as approved successfully.")) return redirect("bill_detail", pk=bill.pk) @login_required @permission_required("django_ledger.change_billmodel", raise_exception=True) def bill_mark_as_paid(request, pk): bill = get_object_or_404(BillModel, pk=pk) if request.method == "POST": dealer = get_user_type(request) if bill.is_paid(): messages.error(request, _("Bill is already paid.")) return redirect("bill_detail", pk=bill.pk) if bill.amount_due == bill.amount_paid: bill.mark_as_paid(user_model=dealer.entity.admin) bill.save() bill.ledger.lock_journal_entries() bill.ledger.post_journal_entries() bill.ledger.post() bill.ledger.save() messages.success(request, _("Bill marked as paid successfully.")) else: messages.error(request, _("Amount paid is not equal to amount due.")) return redirect("bill_detail", pk=bill.pk) @login_required @permission_required("django_ledger.add_billmodel", raise_exception=True) def bill_create(request): dealer = get_user_type(request) entity = dealer.entity if request.method == "POST": data = json.loads(request.body) vendor_id = data.get("vendor") terms = data.get("terms") vendor = entity.get_vendors().filter(pk=vendor_id).first() items = data.get("item", []) quantities = data.get("quantity", []) if not all([items, quantities]): return JsonResponse( {"status": "error", "message": "Items and Quantities are required"}, status=400, ) if isinstance(quantities, list): if "0" in quantities: return JsonResponse( {"status": "error", "message": "Quantity must be greater than zero"} ) else: if int(quantities) <= 0: return JsonResponse( {"status": "error", "message": "Quantity must be greater than zero"} ) bill = entity.create_bill(vendor_model=vendor, terms=terms) if isinstance(items, list): item_quantity_map = {} for item, quantity in zip(items, quantities): if item in item_quantity_map: item_quantity_map[item] += int(quantity) else: item_quantity_map[item] = int(quantity) item_list = list(item_quantity_map.keys()) quantity_list = list(item_quantity_map.values()) items_list = [ {"item_id": item_list[i], "quantity": quantity_list[i]} for i in range(len(item_list)) ] items_txs = [] for item in items_list: item_instance = ItemModel.objects.get(pk=item.get("item_id")) car = models.Car.objects.get(vin=item_instance.name) quantity = Decimal(item.get("quantity")) items_txs.append( { "item_number": item_instance.item_number, "quantity": quantity, "unit_cost": car.finances.cost_price, "total_amount": car.finances.cost_price * quantity, } ) bill_itemtxs = { item.get("item_number"): { "unit_cost": item.get("unit_cost"), "quantity": item.get("quantity"), "total_amount": item.get("total_amount"), } for item in items_txs } else: item = entity.get_items_all().filter(pk=items).first() instance = models.Car.objects.get(vin=item.name) bill_itemtxs = { item.item_number: { "unit_cost": instance.finances.cost_price, "quantity": Decimal(quantities), "total_amount": instance.finances.cost_price * Decimal(quantities), } } bill_itemtxs = bill.migrate_itemtxs( itemtxs=bill_itemtxs, commit=True, operation=BillModel.ITEMIZE_APPEND, ) url = reverse("bill_detail", kwargs={"pk": bill.pk}) return JsonResponse( { "status": "success", "message": "Bill created successfully!", "url": f"{url}", } ) form = forms.BillModelCreateForm(entity_model=entity) form.initial.update( { "cash_account": dealer.settings.bill_cash_account, "prepaid_account": dealer.settings.bill_prepaid_account, "unearned_account": dealer.settings.bill_unearned_account } ) car_list = models.Car.objects.filter(dealer=dealer) context = { "form": form, "items": [ { "car": x, "product": entity.get_items_products().filter(name=x.vin).first(), } for x in car_list ], } return render(request, "ledger/bills/bill_form.html", context) @login_required @permission_required("django_ledger.delete_billmodel", raise_exception=True) def BillDeleteView(request, pk): bill = get_object_or_404(BillModel, pk=pk) bill.delete() return redirect("bill_list") # orders class OrderListView(LoginRequiredMixin,PermissionRequiredMixin, ListView): model = models.SaleOrder template_name = "sales/orders/order_list.html" context_object_name = "orders" permission_required = ["inventory.view_saleorder"] def get_queryset(self): dealer = get_user_type(self.request) qs = super().get_queryset() return qs.filter(estimate__entity=dealer.entity) # email @login_required @permission_required("django_ledger.view_estimate", raise_exception=True) def send_email_view(request, pk): dealer = get_user_type(request) estimate = get_object_or_404(EstimateModel, pk=pk) if not estimate.get_itemtxs_data()[0]: messages.error(request, _("Quotation has no items")) return redirect("estimate_detail", pk=estimate.pk) link = request.build_absolute_uri(reverse_lazy("estimate_preview", kwargs={"pk": estimate.pk})) msg = f""" السلام عليكم Dear {estimate.customer.customer_name}, أود أن أشارككم عرض السعر. I wanted to share with you the quotation. يرجى مراجعة عرض السعر وإعلامي إذا كانت لديك أي استفسارات أو ملاحظات. إذا كان كل شيء على ما يرام، يمكننا المتابعة في الإجراءات. Please review the quotation and let me know if you have any questions or concerns. If everything looks good, we can proceed with the process. رابط عرض السعر: {link} تحياتي, Best regards, {dealer.get_local_name} {dealer.phone_number} هيكل | Haikal """ send_email( settings.DEFAULT_FROM_EMAIL, estimate.customer.email, _("Quotation"), msg, ) estimate.mark_as_review() messages.success(request, _("Email sent successfully!")) return redirect("estimate_detail", pk=estimate.pk) # errors def custom_page_not_found_view(request, exception=None): return render(request, "errors/404.html", {}) def custom_error_view(request, exception=None): return render(request, "errors/500.html", {}) def custom_permission_denied_view(request, exception=None): return render(request, "errors/403.html", {}) def custom_bad_request_view(request, exception=None): return render(request, "errors/400.html", {}) # BALANCE SHEET class BaseBalanceSheetRedirectView(RedirectView): def get_redirect_url(self, *args, **kwargs): year = get_localdate().year return reverse( "entity-bs-year", kwargs={"entity_slug": self.kwargs["entity_slug"], "year": year}, ) def get_login_url(self): return reverse('account_login') class FiscalYearBalanceSheetViewBase(FiscalYearBalanceSheetView,DjangoLedgerSecurityMixIn): template_name = "ledger/reports/balance_sheet.html" # AUTHORIZE_SUPERUSER = False # permission_required = [] # def get_authorized_entity_queryset(self): # dealer = get_user_type(self.request) # return EntityModel.objects.filter(admin=dealer) def get_login_url(self): return reverse('account_login') class QuarterlyBalanceSheetView(FiscalYearBalanceSheetViewBase, QuarterlyReportMixIn, DjangoLedgerSecurityMixIn): """ Quarter Balance Sheet View. """ class MonthlyBalanceSheetView(FiscalYearBalanceSheetViewBase, MonthlyReportMixIn, DjangoLedgerSecurityMixIn): """ Monthly Balance Sheet View. """ class DateBalanceSheetView(FiscalYearBalanceSheetViewBase, DateReportMixIn, DjangoLedgerSecurityMixIn): """ Date Balance Sheet View. """ # Income Statement ----------- class BaseIncomeStatementRedirectViewBase(BaseIncomeStatementRedirectView, DjangoLedgerSecurityMixIn): def get_redirect_url(self, *args, **kwargs): year = get_localdate().year dealer = get_user_type(self.request) return reverse( "entity-ic-year", kwargs={"entity_slug": dealer.entity.slug, "year": year} ) def get_login_url(self): return reverse('account_login') class FiscalYearIncomeStatementViewBase(FiscalYearIncomeStatementView, DjangoLedgerSecurityMixIn): template_name = "ledger/reports/income_statement.html" permission_required = ['inventory.view_carfinance'] def get_login_url(self): return reverse('account_login') class QuarterlyIncomeStatementView( FiscalYearIncomeStatementViewBase, QuarterlyReportMixIn, DjangoLedgerSecurityMixIn ): """ Quarter Income Statement View. """ class MonthlyIncomeStatementView(FiscalYearIncomeStatementViewBase, MonthlyReportMixIn, DjangoLedgerSecurityMixIn): """ Monthly Income Statement View. """ class DateModelIncomeStatementView(FiscalYearIncomeStatementViewBase, DateReportMixIn, DjangoLedgerSecurityMixIn): """ Date Income Statement View. """ # Cash Flow ----------- class BaseCashFlowStatementRedirectViewBase(BaseCashFlowStatementRedirectView, DjangoLedgerSecurityMixIn): def get_redirect_url(self, *args, **kwargs): year = get_localdate().year dealer = get_user_type(self.request) return reverse( "entity-cf-year", kwargs={"entity_slug": dealer.entity.slug, "year": year} ) def get_login_url(self): return reverse('account_login') class FiscalYearCashFlowStatementViewBase(FiscalYearCashFlowStatementView, DjangoLedgerSecurityMixIn): template_name = "ledger/reports/cash_flow_statement.html" permission_required = ['inventory.view_carfinance'] def get_login_url(self): return reverse('account_login') class QuarterlyCashFlowStatementView( FiscalYearCashFlowStatementViewBase, QuarterlyReportMixIn, DjangoLedgerSecurityMixIn ): """ Quarter Cash Flow Statement View. """ class MonthlyCashFlowStatementView( FiscalYearCashFlowStatementViewBase, MonthlyReportMixIn, DjangoLedgerSecurityMixIn ): """ Monthly Cash Flow Statement View. """ class DateCashFlowStatementView(FiscalYearCashFlowStatementViewBase, DateReportMixIn, DjangoLedgerSecurityMixIn): """ Date Cash Flow Statement View. """ # Dashboard class EntityModelDetailHandlerViewBase(EntityModelDetailHandlerView, DjangoLedgerSecurityMixIn): def get_redirect_url(self, *args, **kwargs): loc_date = get_localdate() dealer = get_user_type(self.request) unit_slug = self.get_unit_slug() if unit_slug: return reverse('unit-dashboard-month', kwargs={ 'entity_slug': dealer.entity.slug, 'unit_slug': unit_slug, 'year': loc_date.year, 'month': loc_date.month, }) return reverse('entity-dashboard-month', kwargs={ 'entity_slug': dealer.entity.slug, 'year': loc_date.year, 'month': loc_date.month, }) class EntityModelDetailBaseViewBase(EntityModelDetailBaseView, DjangoLedgerSecurityMixIn): template_name = "ledger/reports/dashboard.html" def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) dealer = get_user_type(self.request) entity_model: EntityModel = dealer.entity context['page_title'] = entity_model.name context['header_title'] = entity_model.name context['header_subtitle'] = _('Dashboard') context['header_subtitle_icon'] = 'mdi:monitor-dashboard' unit_slug = context.get('unit_slug', self.get_unit_slug()) KWARGS = dict(entity_slug=self.kwargs['entity_slug']) if unit_slug: KWARGS['unit_slug'] = unit_slug url_pointer = 'entity' if not unit_slug else 'unit' context['pnl_chart_id'] = f'djl-entity-pnl-chart-{randint(10000, 99999)}' context['pnl_chart_endpoint'] = reverse(f'django_ledger:{url_pointer}-json-pnl', kwargs=KWARGS) context['payables_chart_id'] = f'djl-entity-payables-chart-{randint(10000, 99999)}' context['payables_chart_endpoint'] = reverse(f'django_ledger:{url_pointer}-json-net-payables', kwargs=KWARGS) context['receivables_chart_id'] = f'djl-entity-receivables-chart-{randint(10000, 99999)}' context['receivables_chart_endpoint'] = reverse(f'django_ledger:{url_pointer}-json-net-receivables', kwargs=KWARGS) return context class FiscalYearEntityModelDashboardView(EntityModelDetailBaseViewBase, DjangoLedgerSecurityMixIn): """ Entity Fiscal Year Dashboard View. """ permission_required = ['inventory.view_carfinance'] def get_login_url(self): return reverse('account_login') class QuarterlyEntityDashboardView(FiscalYearEntityModelDashboardView, QuarterlyReportMixIn, DjangoLedgerSecurityMixIn): """ Entity Quarterly Dashboard View. """ class MonthlyEntityDashboardView(FiscalYearEntityModelDashboardView, MonthlyReportMixIn, DjangoLedgerSecurityMixIn): """ Monthly Entity Dashboard View. """ class DateEntityDashboardView(FiscalYearEntityModelDashboardView, DateReportMixIn, DjangoLedgerSecurityMixIn): """ Date-specific Entity Dashboard View. """ class PayableNetAPIView(DjangoLedgerSecurityMixIn, EntityUnitMixIn, View): http_method_names = ['get'] def get(self, request, *args, **kwargs): if request.user.is_authenticated: dealer = get_user_type(request) bill_qs = BillModel.objects.for_entity( entity_slug=dealer.entity.slug, user_model=dealer.entity.admin, ).unpaid() net_summary = accruable_net_summary(bill_qs) net_payables = { 'net_payable_data': net_summary } return JsonResponse({ 'results': net_payables }) return JsonResponse({ 'message': 'Unauthorized' }, status=401) class ReceivableNetAPIView(DjangoLedgerSecurityMixIn, EntityUnitMixIn, View): http_method_names = ['get'] def get(self, request, *args, **kwargs): if request.user.is_authenticated: dealer = get_user_type(request) invoice_qs = InvoiceModel.objects.for_entity( entity_slug=dealer.entity.slug, user_model=dealer.entity.admin, ).unpaid() net_summary = accruable_net_summary(invoice_qs) net_receivable = { 'net_receivable_data': net_summary } return JsonResponse({ 'results': net_receivable }) return JsonResponse({ 'message': 'Unauthorized' }, status=401) class PnLAPIView(DjangoLedgerSecurityMixIn, EntityUnitMixIn, View): http_method_names = ['get'] def get(self, request, *args, **kwargs): if request.user.is_authenticated: dealer = get_user_type(request) entity = EntityModel.objects.for_user( user_model=dealer.entity.admin).get( slug__exact=dealer.entity.slug) unit_slug = self.get_unit_slug() io_digest = entity.digest( user_model=self.request.user, unit_slug=unit_slug, equity_only=True, signs=False, by_period=True, process_groups=True, from_date=self.request.GET.get('fromDate'), to_date=self.request.GET.get('toDate'), # todo: For PnL to display proper period values must not use closing entries. use_closing_entries=False ) io_data = io_digest.get_io_data() group_balance_by_period = io_data['group_balance_by_period'] group_balance_by_period = dict(sorted((k, v) for k, v in group_balance_by_period.items())) entity_data = { f'{month_name[k[1]]} {k[0]}': {d: float(f) for d, f in v.items()} for k, v in group_balance_by_period.items()} entity_pnl = { 'entity_slug': entity.slug, 'entity_name': entity.name, 'pnl_data': entity_data } return JsonResponse({ 'results': entity_pnl }) return JsonResponse({ 'message': 'Unauthorized' }, status=401) class EmployeeCalendarView(LoginRequiredMixin, ListView): template_name = 'crm/employee_calendar.html' model = Appointment context_object_name = 'appointments' def get_queryset(self): query = self.request.GET.get('q') dealer = get_user_type(self.request) staff = getattr(self.request, 'staff', None) if staff: appointments = Appointment.objects.filter(appointment_request__staff_member=staff, ppointment_request__date__gt=timezone.now()) appointments = Appointment.objects.filter(appointment_request__date__gt=timezone.now()) return apply_search_filters(appointments, query) def apply_search_filters(queryset, query): if not query: return queryset search_filters = Q() model = queryset.model for field in model._meta.get_fields(): if hasattr(field, 'attname') and field.get_internal_type() in ["CharField", "TextField", "EmailField"]: search_filters |= Q(**{f"{field.name}__icontains": query}) return queryset.filter(search_filters).distinct() class CarListViewTable(LoginRequiredMixin, ExportMixin, SingleTableView): model = models.Car table_class = tables.CarTable template_name = "inventory/car_list_table.html" def get_queryset(self): dealer = get_user_type(self.request) return models.Car.objects.select_related( "finances", "colors__exterior", "colors__interior" ).filter(dealer=dealer) @login_required def DealerSettingsView(request,pk): dealer_setting = get_object_or_404(models.DealerSettings, pk=pk) dealer = get_user_type(request) if request.method == 'POST': form = forms.DealerSettingsForm(request.POST, instance=dealer_setting) if form.is_valid(): instance = form.save(commit=False) instance.dealer = dealer instance.save() messages.success(request, 'Settings updated') return redirect('dealer_detail', pk=dealer.pk) else: print(form.errors) form = forms.DealerSettingsForm(instance=dealer_setting,initial={'dealer':dealer}) form.fields['invoice_cash_account'].queryset = dealer.entity.get_all_accounts().filter(role=roles.ASSET_CA_CASH) form.fields['invoice_prepaid_account'].queryset = dealer.entity.get_all_accounts().filter(role=roles.ASSET_CA_RECEIVABLES) form.fields['invoice_unearned_account'].queryset = dealer.entity.get_all_accounts().filter(role=roles.LIABILITY_CL_DEFERRED_REVENUE) form.fields['bill_cash_account'].queryset = dealer.entity.get_all_accounts().filter(role=roles.ASSET_CA_CASH) form.fields['bill_prepaid_account'].queryset = dealer.entity.get_all_accounts().filter(role=roles.ASSET_CA_PREPAID) form.fields['bill_unearned_account'].queryset = dealer.entity.get_all_accounts().filter(role=roles.LIABILITY_CL_ACC_PAYABLE) return render(request, 'account/user_settings.html', {'form': form}) @login_required def schedule_cancel(request,pk): schedule = get_object_or_404(models.Schedule, pk=pk) schedule.status = "Canceled" schedule.save() response = HttpResponse() response.status_code = 200 return response @login_required def assign_car_makes(request): dealer = get_user_type(request) if request.method == "POST": form = forms.DealersMakeForm(request.POST, dealer=dealer) if form.is_valid(): form.save() return redirect("dealer_detail", pk=dealer.pk) else: # Pre-fill the form with existing selections existing_car_makes = models.DealersMake.objects.filter(dealer=dealer).values_list("car_make", flat=True) form = forms.DealersMakeForm(initial={"car_makes": existing_car_makes}, dealer=dealer) return render(request, "dealers/assign_car_makes.html", {"form": form})