from django_ledger.models import EntityModel, InvoiceModel import logging import json import datetime from decimal import Decimal from django_ledger.models import TransactionModel, AccountModel,JournalEntryModel from django.shortcuts import HttpResponse from django.template.loader import render_to_string # from weasyprint import HTML # from weasyprint.fonts import FontConfiguration from django.views.decorators.csrf import csrf_exempt from vin import VIN from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.decorators import login_required from django.http import JsonResponse from django.shortcuts import render, get_object_or_404, redirect from django.utils.translation import gettext_lazy as _ from django.db.models import Q from django.views.generic import ( View, ListView, DetailView, CreateView, UpdateView, DeleteView, TemplateView, ) from django.utils import timezone, translation from django.conf import settings from urllib.parse import urlparse, urlunparse from django.forms import ChoiceField, ModelForm, RadioSelect from django.urls import reverse, reverse_lazy from django.contrib import messages from django.db.models import Sum, F, Count from inventory.mixins import AddDealerInstanceMixin from .services import elm, decodevin, get_make, get_model, normalize_name from .services import ( elm, decodevin, get_make, get_model, normalize_name, get_ledger_data, ) from . import models, forms from django_tables2.export.views import ExportMixin from django.contrib.auth.mixins import PermissionRequiredMixin from django.contrib.auth.decorators import user_passes_test from django.contrib.messages.views import SuccessMessageMixin from django.contrib.auth.models import Group from django.contrib.auth import get_user_model from .utils import get_calculations User = get_user_model() logger = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO) 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("/") class HomeView(LoginRequiredMixin, TemplateView): template_name = "dashboards/accounting.html" def dispatch(self, request, *args, **kwargs): if ( not any(hasattr(request.user, attr) for attr in ["dealer", "subdealer"]) or not request.user.is_authenticated ): messages.error(request, _("You are not associated with any dealer.")) return redirect("welcome") return super().dispatch(request, *args, **kwargs) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) total_cars = models.Car.objects.filter(dealer=self.request.user.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 context['dealer'] = self.request.user.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 return context class WelcomeView(TemplateView): template_name = "index.html" class CarCreateView(LoginRequiredMixin, CreateView): model = models.Car form_class = forms.CarForm template_name = "inventory/car_form.html" # success_url = reverse_lazy('inventory_stats') 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): form.instance.dealer = self.request.user.dealer.get_root_dealer form.save() messages.success(self.request, "Car saved successfully.") return super().form_valid(form) 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, } 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") 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() make = get_make(manufacturer_name) model = get_model(model_name, make) logger.info( f"VIN decoded using {decoding_method}: Make={manufacturer_name}, Model={model_name}, Year={year_model}" ) car_model = model car_make = make 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") # Validate inputs if not model_id or not year: return JsonResponse( {"error": "Missing required parameters: model_id or year"}, status=400 ) try: year = int(year) except ValueError: return JsonResponse({"error": "Invalid year format"}, status=400) series = models.CarSerie.objects.filter(id_car_model=model_id).values( "id_car_serie", "name", "arabic_name" ) 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) class CarInventory(LoginRequiredMixin, ListView): model = models.Car home_label = _("inventory") template_name = "inventory/car_inventory.html" context_object_name = "cars" paginate_by = 10 ordering = ["receiving_date"] 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'] cars = models.Car.objects.filter( dealer=self.request.user.dealer.get_root_dealer, id_car_make=make_id, id_car_model=model_id, id_car_trim=trim_id, ).order_by("receiving_date") if query: cars = cars.filter(Q(vin__icontains=query)) return cars 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, CreateView): model = models.CarColors form_class = forms.CarColorsForm template_name = "inventory/add_colors.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_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 @login_required def inventory_stats_view(request): dealer = request.user.dealer # Annotate total cars by make, model, and trim cars = ( models.Car.objects.filter(dealer=dealer.get_root_dealer) .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': {} } 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 result = { "total_cars": cars.count(), "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, DetailView): model = models.Car template_name = "inventory/car_detail.html" context_object_name = "car" class CarFinanceCreateView(LoginRequiredMixin, CreateView): model = models.CarFinance form_class = forms.CarFinanceForm template_name = "inventory/car_finance_form.html" 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 class CarFinanceUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView): model = models.CarFinance form_class = forms.CarFinanceForm template_name = "inventory/car_finance_form.html" success_message = _("Car finance details updated successfully.") 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 class CarUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView): model = models.Car form_class = forms.CarUpdateForm template_name = "inventory/car_edit.html" success_message = _("Car updated successfully.") def get_success_url(self): return reverse("car_detail", kwargs={"pk": self.object.pk}) class CarDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteView): model = models.Car template_name = "inventory/car_confirm_delete.html" success_url = reverse_lazy("inventory_stats") def delete(self, request, *args, **kwargs): messages.success(request, _("Car deleted successfully.")) return super().delete(request, *args, **kwargs) class CarLocationCreateView(CreateView): 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}) def form_valid(self, form): form.instance.car = get_object_or_404(models.Car, pk=self.kwargs["car_pk"]) form.instance.owner = self.request.user.dealer form.save() messages.success(self.request, "Car saved successfully.") return super().form_valid(form) class CarLocationUpdateView(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 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"]}) @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) try: reserved_until = timezone.now() + timezone.timedelta(hours=24) models.CarReservation.objects.create( car=car, reserved_by=request.user, reserved_until=reserved_until ) messages.success(request, _("Car reserved successfully.")) except Exception as e: messages.error(request, f"Error reserving car: {e}") return redirect("car_detail", pk=car.pk) 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": reservation.delete() 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 DealerListView(LoginRequiredMixin, ListView): model = models.Dealer template_name = "dealer_list.html" context_object_name = "dealers" class DealerDetailView(LoginRequiredMixin, DetailView): model = models.Dealer template_name = "dealers/dealer_detail.html" context_object_name = "dealer" class DealerCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView): model = models.Dealer form_class = forms.DealerForm template_name = "dealer_form.html" success_url = reverse_lazy("dealer_list") success_message = _("Dealer created successfully.") 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}) def get_form(self, form_class=None): form = super().get_form(form_class) if hasattr(form.fields, "dealer_type"): form.fields.pop("dealer_type") return form def get_form_class(self): if self.request.user.dealer.dealer_type == "Owner": return forms.DealerForm else: return forms.UserForm class DealerDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteView): model = models.Dealer template_name = "dealer_confirm_delete.html" success_url = reverse_lazy("dealer_list") success_message = _("Dealer deleted successfully.") class CustomerListView(LoginRequiredMixin, PermissionRequiredMixin, ListView): model = models.Customer home_label = _("customers") context_object_name = "customers" paginate_by = 10 template_name = "customers/customer_list.html" permission_required = ("inventory.view_customer",) def get_queryset(self): query = self.request.GET.get("q") customers = models.Customer.objects.filter( dealer=self.request.user.dealer.get_root_dealer ) if query: customers = customers.filter( Q(national_id__icontains=query) | Q(first_name__icontains=query) | Q(last_name__icontains=query) ) return customers 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 = models.Customer template_name = "customers/view_customer.html" context_object_name = "customer" permission_required = ("inventory.view_customer",) class CustomerCreateView( LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, AddDealerInstanceMixin, CreateView, ): model = models.Customer form_class = forms.CustomerForm template_name = "customers/customer_form.html" success_url = reverse_lazy("customer_list") permission_required = ("inventory.add_customer",) success_message = _("Customer created successfully.") class CustomerUpdateView( LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, AddDealerInstanceMixin, UpdateView, ): model = models.Customer form_class = forms.CustomerForm template_name = "customers/customer_form.html" success_url = reverse_lazy("customer_list") permission_required = ("inventory.change_customer",) success_message = _("Customer updated successfully.") @login_required def delete_customer(request, pk): customer = get_object_or_404(models.Customer, pk=pk) customer.delete() messages.success(request, _("Customer deleted successfully.")) return redirect("customer_list") class VendorListView(LoginRequiredMixin, PermissionRequiredMixin, ListView): model = models.Vendor context_object_name = "vendors" paginate_by = 10 template_name = "vendors/vendors_list.html" permission_required = ("inventory.view_vendor",) class VendorDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView): model = models.Vendor template_name = "vendors/view_vendor.html" permission_required = ("inventory.view_vendor",) class VendorCreateView( LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, AddDealerInstanceMixin, CreateView, ): model = models.Vendor form_class = forms.VendorForm template_name = "vendors/vendor_form.html" success_url = reverse_lazy("vendor_list") permission_required = ("inventory.add_vendor",) success_message = _("Vendor created successfully.") class VendorUpdateView( LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, AddDealerInstanceMixin, UpdateView, ): model = models.Vendor form_class = forms.VendorForm template_name = "vendors/vendor_form.html" success_url = reverse_lazy("vendor_list") permission_required = ("inventory.change_vendor",) success_message = _("Vendor updated successfully.") @login_required def delete_vendor(request, pk): vendor = get_object_or_404(models.Vendor, pk=pk) vendor.delete() messages.success(request, _("Vendor deleted successfully.")) return redirect("vendor_list") class QuotationCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView): model = models.SaleQuotation form_class = forms.QuotationForm template_name = "sales/quotation_form.html" permission_required = ("inventory.add_salequotation",) def form_valid(self, form): dealer = self.request.user.dealer.get_root_dealer form.instance.dealer = dealer quotation = form.save() selected_cars = form.cleaned_data.get("cars") for car in selected_cars: car_finance = car.finances if car_finance: models.SaleQuotationCar.objects.create( quotation=quotation, car=car, ) messages.success(self.request, _("Quotation created successfully.")) return redirect("quotation_list") class QuotationListView(LoginRequiredMixin, PermissionRequiredMixin, ListView): model = models.SaleQuotation template_name = "sales/quotation_list.html" context_object_name = "quotations" paginate_by = 10 permission_required = ("inventory.view_salequotation",) def get_queryset(self): status = self.request.GET.get("status") queryset = self.request.user.dealer.get_root_dealer.sales.all() if status: queryset = queryset.filter(status=status) return queryset class QuotationDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView): model = models.SaleQuotation template_name = "sales/quotation_detail.html" context_object_name = "quotation" permission_required = ("inventory.view_salequotation",) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) quotation = self.object context_result = get_calculations(quotation) context.update(context_result) return context @login_required def generate_invoice(request, pk): quotation = get_object_or_404(models.SaleQuotation, pk=pk) dealer = request.user.dealer.get_root_dealer entity = dealer.entity if not quotation.is_approved: messages.error( request, "Quotation must be approved before converting to an invoice." ) else: coa_qs, coa_map = entity.get_all_coa_accounts() cash_account = coa_qs.first().get_coa_accounts().filter(name="Cash") recivable_account = coa_qs.first().get_coa_accounts().filter(name="Accounts Receivable") customer = entity.get_customers().filter(customer_name=quotation.customer.get_full_name).first() invoice_model = entity.create_invoice( customer_model=customer, terms=InvoiceModel.TERMS_ON_RECEIPT, cash_account=cash_account.first(), prepaid_account=recivable_account.first(), coa_model=coa_qs.first(), ) name_list = [f"{instance.car.year} {instance.car.id_car_make} {instance.car.id_car_model} {instance.car.id_car_trim}" for instance in quotation.quotation_cars.all()] invoices_item_models = invoice_model.get_item_model_qs().filter(name__in=name_list) invoice_itemtxs = { im.item_number: { "unit_cost": im.default_amount, "quantity": 1, "total_amount": im.default_amount, } for im in invoices_item_models } invoice_itemtxs = invoice_model.migrate_itemtxs( itemtxs=invoice_itemtxs, commit=True, operation=InvoiceModel.ITEMIZE_APPEND ) ledger = entity.get_ledgers().filter(name=f"Payment Ledger for Invoice {invoice_model}").first() if not ledger: ledger = entity.create_ledger(name=f"Payment Ledger for Invoice {invoice_model}",posted=True) journal_entry = JournalEntryModel.objects.create( posted=False, description=f"Payment for Invoice {invoice_model}", ledger=ledger, locked=False, origin="Payment", ) quotation.payment_id = journal_entry.pk quotation.is_approved = True date = datetime.datetime.now() quotation.date_draft = date invoice_model.date_draft = date invoice_model.save() quotation.save() if not invoice_model.can_review(): messages.error(request, "Quotation is not ready for review") return redirect("quotation_detail", pk=pk) invoice_model.mark_as_review() invoice_model.date_in_review = date quotation.date_in_review = date quotation.status = "In Review" invoice_model.save() quotation.save() # elif status == "approved": # if qoutation.status == "Approved": # messages.error(request, "Quotation is already approved") # return redirect("quotation_detail", pk=pk) # invoice_model = invoice_model.filter(date_in_review=qoutation.date_in_review).first() # if not invoice_model.can_approve(): # messages.error(request, "Quotation is not ready for approval") # return redirect("quotation_detail", pk=pk) # invoice_model.mark_as_approved(entity_slug=entity.slug, user_model=request.user.dealer.get_root_dealer.user) # invoice_model.date_approved = date # qoutation.date_approved = date # invoice_model.save() # qoutation.status = "Approved" # qoutation.save() # messages.success(request, _("Quotation Approved")) # ledger = entity.create_ledger( # name=f"Payment Ledger for Invoice {invoice_model}", # posted=True # ) # entity_unit,created = EntityUnitModel.objects.get_or_create( # name="Sales Department", # entity=entity, # document_prefix="SD" # ) # journal_entry = JournalEntryModel.objects.create( # entity_unit=entity_unit, # posted=False, # description=f"Payment for Invoice {invoice_model}", # ledger=ledger, # locked=False, # origin="Payment", # ) # accounts_receivable = coa_qs.first().get_coa_accounts().filter(name="Accounts Receivable").first() # if not accounts_receivable: # accounts_receivable = entity.create_account( # code="AR", # role="asset", # name="Accounts Receivable", # coa_model=coa_qs.first(), # balance_type="credit" # ) # TransactionModel.objects.create( # journal_entry=journal_entry, # account=cash_account.first(), # Debit Cash # amount=invoice_model.amount_due, # Payment amount # tx_type='debit', # description="Payment Received", # ) # TransactionModel.objects.create( # journal_entry=journal_entry, # account=accounts_receivable, # Credit Accounts Receivable # amount=invoice_model.amount_due, # Payment amount # tx_type='credit', # description="Payment Received", # ) # invoice_model.mark_as_review() # print("reviewed") # invoice_model.mark_as_approved(entity_slug=entity.slug, user_model=request.user.dealer.get_root_dealer.user) # print("approved") # invoice_model.mark_as_paid(entity_slug=entity.slug, user_model=request.user.dealer.get_root_dealer.user) # print("paid") # invoice_model.save() messages.success(request, "Invoice created") return redirect("quotation_detail", pk=pk) # return redirect('django_ledger:invoice-detail', entity_slug=quotation.entity.slug, invoice_pk=invoice.uuid) @login_required def post_quotation(request, pk): qoutation = get_object_or_404(models.SaleQuotation, pk=pk) dealer = request.user.dealer.get_root_dealer entity = dealer.entity if qoutation.posted: messages.error(request, "Quotation is already posted") return redirect("quotation_detail", pk=pk) coa_qs, coa_map = entity.get_all_coa_accounts() cash_account = coa_qs.first().get_coa_accounts().filter(name="Cash") recivable_account = coa_qs.first().get_coa_accounts().filter(name="Accounts Receivable") customer = entity.get_customers().filter(customer_name=qoutation.customer.get_full_name).first() invoice_model = entity.get_invoices().filter(customer=customer,date_paid=qoutation.date_paid).first() ledger = entity.get_ledgers().filter(name=f"Payment Ledger for Invoice {invoice_model}").first() return # if not ledger: # ledger = entity.create_ledger(name=f"Payment Ledger for Invoice {invoice_model}",posted=True) # entity_unit,created = EntityUnitModel.objects.get_or_create( # name="Sales Department", # entity=entity, # document_prefix="SD" # ) # journal_entry = JournalEntryModel.objects.create( # entity_unit=entity_unit, # posted=False, # description=f"Payment for Invoice {invoice_model}", # ledger=ledger, # locked=False, # origin="Payment", # ) # TransactionModel.objects.create( # journal_entry=journal_entry, # account=cash_account.first(), # Debit Cash # amount=invoice_model.amount_due, # Payment amount # tx_type='debit', # description="Payment Received", # ) # TransactionModel.objects.create( # journal_entry=journal_entry, # account=recivable_account.first(), # Credit Accounts Receivable # amount=invoice_model.amount_due, # Payment amount # tx_type='credit', # description="Payment Received", # ) # journal_entry.posted = True # qoutation.posted = True # qoutation.save() # journal_entry.save() # messages.success(request, "Invoice posted") # return redirect("quotation_detail", pk=pk) @login_required def mark_quotation(request, pk): qoutation = get_object_or_404(models.SaleQuotation, pk=pk) status = request.GET.get("status") dealer = request.user.dealer.get_root_dealer entity = dealer.entity date = datetime.datetime.now() customer = entity.get_customers().filter(customer_name=qoutation.customer.get_full_name).first() invoice_model = entity.get_invoices().filter(customer=customer) if status == "approved": if qoutation.status == "Approved": messages.error(request, "Quotation is already approved") return redirect("quotation_detail", pk=pk) invoice_model = invoice_model.filter(date_in_review=qoutation.date_in_review).first() if not invoice_model.can_approve(): messages.error(request, "Quotation is not ready for approval") return redirect("quotation_detail", pk=pk) invoice_model.mark_as_approved(entity_slug=entity.slug, user_model=request.user.dealer.get_root_dealer.user) invoice_model.date_approved = date qoutation.date_approved = date invoice_model.save() qoutation.status = "Approved" qoutation.save() for car in qoutation.quotation_cars.all(): car.car.status = "reserved" car.car.save() messages.success(request, _("Quotation Approved")) elif status == "paid": if qoutation.status == "Paid": messages.error(request, "Quotation is already paid") return redirect("quotation_detail", pk=pk) invoice_model = invoice_model.filter(date_approved=qoutation.date_approved).first() if not invoice_model.can_pay(): messages.error(request, "Quotation is not ready for payment") return redirect("quotation_detail", pk=pk) invoice_model.mark_as_paid(entity_slug=entity.slug, user_model=request.user.dealer.get_root_dealer.user) invoice_model.date_paid = date qoutation.date_paid = date invoice_model.save() qoutation.status = "Paid" qoutation.save() messages.success(request, _("Quotation Paid")) return redirect("quotation_detail", pk=pk) @login_required def confirm_quotation(request, pk): quotation = get_object_or_404(models.SaleQuotation, pk=pk) if quotation.is_approved: messages.error(request, _("Quotation already approved.")) return redirect("quotation_detail", pk=pk) try: # quotation.confirm() # quotation_cars = quotation.quotation_cars.annotate(total_price=F('car__total') * F('quantity')) # total = quotation.quotation_cars.aggregate(total_price=Sum(F('car__finances__selling_price') * F('quantity'))) models.SalesOrder.objects.create( quotation=quotation, total_amount=quotation.total_vat, # total_amount=quotation.quotation_cars.aggregate(Sum("total_amount"))["total_amount__sum"], ) quotation.is_approved = True quotation.save() messages.success(request, _("Quotation confirmed and sales order created.")) except ValueError as e: messages.error(request, str(e)) return redirect("quotation_detail", pk=pk) class SalesOrderDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView): model = models.SalesOrder template_name = "sales/sales_order_detail.html" context_object_name = "sales_order" permission_required = ("inventory.view_salequotation",) slug_field = "order_id" slug_url_kwarg = "order_id" # Users class UserListView(LoginRequiredMixin, PermissionRequiredMixin, ListView): model = models.Dealer context_object_name = "users" paginate_by = 10 template_name = "users/user_list.html" permission_required = ("inventory.view_dealer",) def get_queryset(self): query = self.request.GET.get("q") users = self.request.user.dealer.sub_dealers if query: users = users.filter( Q(name__icontains=query) | Q(arabic_name__icontains=query) | Q(phone_number__icontains=query) | Q(address__icontains=query) ) return users.all() class UserDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView): model = models.Dealer template_name = "users/user_detail.html" context_object_name = "user_" permission_required = ("inventory.view_dealer",) class UserCreateView( LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, AddDealerInstanceMixin, CreateView, ): model = models.Dealer form_class = forms.UserForm template_name = "users/user_form.html" success_url = reverse_lazy("user_list") permission_required = ("inventory.add_dealer",) success_message = _("User created successfully.") def get_form(self, form_class=None): form = super().get_form(form_class) form.fields["dealer_type"].choices = [ t for t in form.fields["dealer_type"].choices if t[0] != "Owner" ] return form def form_valid(self, form): dealer = self.request.user.dealer.get_root_dealer if dealer.sub_dealers.count() >= dealer.get_active_plan.max_users: messages.error( self.request, _("You have reached the maximum number of users.") ) return redirect("user_list") user = User.objects.create_user(username=form.cleaned_data["name"]) user.set_password("Tenhal@123") user.save() form.instance.user = user form.instance.parent_dealer = dealer for group in user.groups.all(): group.user_set.remove(user) Group.objects.get(name=form.cleaned_data["dealer_type"].lower()).user_set.add( user ) form.save() return super().form_valid(form) class UserUpdateView( LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, AddDealerInstanceMixin, UpdateView, ): model = models.Dealer form_class = forms.UserForm template_name = "users/user_form.html" success_url = reverse_lazy("user_list") permission_required = ("inventory.change_dealer",) success_message = _("User updated successfully.") def get_form(self, form_class=None): form = super().get_form(form_class) if not self.request.user.has_perms(["inventory.change_dealer_type"]): field = form.fields["dealer_type"] field.widget = field.hidden_widget() form.fields["dealer_type"].choices = [ t for t in form.fields["dealer_type"].choices if t[0] != "Owner" ] return form def form_valid(self, form): user = form.instance.user for group in user.groups.all(): group.user_set.remove(user) Group.objects.get(name=form.cleaned_data["dealer_type"].lower()).user_set.add( user ) form.save() return super().form_valid(form) def UserDeleteview(request, pk): user = get_object_or_404(models.Dealer, pk=pk) user.delete() messages.success(request, _("User deleted successfully.")) return redirect("user_list") # errors def custom_page_not_found_view(request, exception): 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", {}) class OrganizationListView(LoginRequiredMixin, ListView): model = models.Organization template_name = "organizations/organization_list.html" context_object_name = "organizations" class OrganizationDetailView(DetailView): model = models.Organization template_name = "organizations/organization_detail.html" context_object_name = "organization" class OrganizationCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView): model = models.Organization form_class = forms.OrganizationForm template_name = "organizations/organization_form.html" success_url = reverse_lazy("organization_list") success_message = "Organization created successfully." def form_valid(self, form): if form.is_valid(): form.instance.dealer = self.request.user.dealer.get_root_dealer form.save() return super().form_valid(form) else: return form.errors class OrganizationUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView): model = models.Organization form_class = forms.OrganizationForm template_name = "organizations/organization_form.html" success_url = reverse_lazy("organization_list") success_message = "Organization updated successfully." 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." class RepresentativeListView(LoginRequiredMixin, ListView): model = models.Representative template_name = "representatives/representative_list.html" context_object_name = "representatives" class RepresentativeDetailView(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.get_root_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." # def quotation_pdf_view(request, pk): # # Get the quotation object # quotation = models.SaleQuotation.objects.get(pk=pk) # # # Render the HTML template for the quotation page # context = { # "quotation": quotation, # } # context_result = get_calculations(quotation) # context = context.update(context_result) # # html_content = render_to_string("sales/quotation_pdf.html", context) # # # Create a PDF file # # pdf_file = HTML(string=html_content).render() # # # Save the PDF file to a file # with open("quotation.pdf", "wb") as f: # f.write(pdf_file.write_pdf()) # # # Return the PDF file as a response # return HttpResponse(pdf_file, content_type="application/pdf") @login_required def download_quotation_pdf(request, quotation_id): try: # Retrieve the quotation object quotation = models.SaleQuotation.objects.get(id=quotation_id) cars = models.SaleQuotationCar.objects.get(id=quotation_id) print(cars) services = cars.finance.additional_services.all() print(services) # Create a response object response = HttpResponse(content_type='application/pdf') response['Content-Disposition'] = f'attachment; filename="quotation_{quotation.id}.pdf"' # Call the PDF generation function # generate_quotation_pdf(response, quotation, services) return response except models.SaleQuotation.DoesNotExist: return HttpResponse("Quotation not found", status=404) @login_required def invoice_detail(request,pk): quotation = get_object_or_404(models.SaleQuotation, pk=pk) dealer = request.user.dealer.get_root_dealer entity = dealer.entity customer = entity.get_customers().filter(customer_name=quotation.customer.get_full_name).first() invoice_model = entity.get_invoices() invoice = invoice_model.filter(customer=customer,date_draft=quotation.date_draft).first() return redirect('quotation_detail', pk=pk) @login_required def payment_invoice(request,pk): quotation = get_object_or_404(models.SaleQuotation, pk=pk) dealer = request.user.dealer.get_root_dealer entity = dealer.entity customer = entity.get_customers().filter(customer_name=quotation.customer.get_full_name).first() invoice_model = entity.get_invoices() invoice = invoice_model.filter(customer=customer,date_draft=quotation.date_draft).first() return redirect('quotation_detail', pk=pk) # class PaymentCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView): # model = models.Payment # form_class = forms.PaymentForm # template_name = "sales/payments/payment_form.html" # success_url = reverse_lazy("quotation_list") # success_message = "Payment created successfully." # def form_valid(self, form): # quotation = get_object_or_404(models.SaleQuotation, pk=self.kwargs["pk"]) # form.instance.quotation = quotation # form.save() # return super().form_valid(form) # def get_context_data(self, **kwargs): # context = super().get_context_data(**kwargs) # context["quotation"] = get_object_or_404(models.SaleQuotation, pk=self.kwargs["pk"]) # return context def payment_create(request, pk): quotation = get_object_or_404(models.SaleQuotation, pk=pk) dealer = request.user.dealer.get_root_dealer if request.method == "POST": form = forms.PaymentForm(request.POST) if form.is_valid(): form.instance.quotation = quotation insatnce = form.save() dealer = request.user.dealer.get_root_dealer entity = dealer.entity customer = entity.get_customers().filter(customer_name=quotation.customer.get_full_name).first() coa_qs, coa_map = entity.get_all_coa_accounts() cash_account = coa_qs.first().get_coa_accounts().filter(name="Cash") recivable_account = coa_qs.first().get_coa_accounts().filter(name="Accounts Receivable") journal_entry = JournalEntryModel.objects.filter(pk=quotation.payment_id).first() TransactionModel.objects.create( journal_entry=journal_entry, account=cash_account.first(), # Debit Cash amount=insatnce.amount, # Payment amount tx_type='debit', description="Payment Received", ) TransactionModel.objects.create( journal_entry=journal_entry, account=recivable_account.first(), # Credit Accounts Receivable amount=insatnce.amount, # Payment amount tx_type='credit', description="Payment Received", ) journal_entry.posted = True quotation.posted = True quotation.save() journal_entry.save() invoice_model = entity.get_invoices().filter(date_approved=quotation.date_approved).first() invoice_model.mark_as_paid(entity_slug=entity.slug, user_model=request.user.dealer.get_root_dealer.user) date = timezone.now() invoice_model.date_paid = date quotation.date_paid = date invoice_model.save() quotation.status = "Paid" quotation.save() messages.success(request, "Payment created successfully.") return redirect("quotation_detail", pk=pk) else: form = forms.PaymentForm() return render(request, "sales/payments/payment_create.html", {"quotation": quotation,"form": form})