from django_ledger.models import EntityModel, InvoiceModel,BankAccountModel,AccountModel,JournalEntryModel,TransactionModel,EstimateModel,CustomerModel from django.core.mail import send_mail from django_ledger.forms.bank_account import BankAccountCreateForm,BankAccountUpdateForm from django_ledger.forms.account import AccountModelCreateForm,AccountModelUpdateForm from django_ledger.forms.estimate import EstimateModelCreateForm import logging import json import datetime from decimal import Decimal from django.shortcuts import HttpResponse 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.urls import reverse, reverse_lazy from django.contrib import messages from django.db.models import Sum, F, Count from django.db import transaction from .services import ( elm, decodevin, get_make, get_model, normalize_name, get_ledger_data, ) from . import models, forms from django.contrib.auth.mixins import PermissionRequiredMixin from django.contrib.messages.views import SuccessMessageMixin from django.contrib.auth.models import Group from .utils import get_calculations from django.contrib.auth.models import User from allauth.account import views 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("/") def dealer_signup(request, *args, **kwargs): if request.method == "POST": data = json.loads(request.body) wf1 = data.get("wizardValidationForm1") wf2 = data.get("wizardValidationForm2") wf3 = data.get("wizardValidationForm3") name = wf1.get("name") arabic_name = wf1.get("arabic_name") email = wf1.get("email") phone = wf2.get("phone_number") crn = wf2.get("crn") vrn = wf2.get("vrn") address = wf2.get("address") password = wf3.get("password") password_confirm = wf3.get("confirm_password") if password != password_confirm: return JsonResponse({"error": "Passwords do not match."}, status=400) try: with transaction.atomic(): user = User.objects.create(email=email, password=password) user.set_password(password) user.save() models.Dealer.objects.create(user=user, name=name, arabic_name=arabic_name, crn=crn, vrn=vrn, phone_number=phone, address=address, dealer_type="OWNER",) return JsonResponse({"message": "User created successfully."}, status=200) except Exception as e: return JsonResponse({"error": str(e)}, status=400) form1 = forms.WizardForm1() form2 = forms.WizardForm2() form3 = forms.WizardForm3() return render(request, "account/signup-wizard.html",{"form1": form1, "form2": form2, "form3": form3}) class Login(views.LoginView): template_name = "account/login.html" redirect_authenticated_user = True class HomeView(TemplateView): template_name = "index.html" class AccountingDashboard(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 = "welcome.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 DealerDetailView(LoginRequiredMixin, DetailView): model = models.Dealer template_name = "dealers/dealer_detail.html" context_object_name = "dealer" 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 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, 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, 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, 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.") def form_valid(self, form): form.instance.dealer = self.request.user.dealer return super().form_valid(form) class VendorUpdateView( LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, 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, 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, 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}) #Ledger #BANK ACCOUNT class BankAccountListView(LoginRequiredMixin, ListView): model = BankAccountModel template_name = "ledger/bank_accounts/bank_account_list.html" context_object_name = "bank_accounts" def get_queryset(self): return BankAccountModel.objects.filter(entity_model=self.request.user.dealer.entity) class BankAccountCreateView(LoginRequiredMixin, 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." def form_valid(self, form): form.instance.entity_model = self.request.user.dealer.entity return super().form_valid(form) def get_form_kwargs(self): """ Override this method to pass additional keyword arguments to the form. """ entity = self.request.user.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 class BankAccountDetailView(LoginRequiredMixin, DetailView): model = BankAccountModel template_name = "ledger/bank_accounts/bank_account_detail.html" context_object_name = "bank_account" class BankAccountUpdateView(LoginRequiredMixin, 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." def get_form_kwargs(self): """ Override this method to pass additional keyword arguments to the form. """ entity = self.request.user.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, ListView): model = AccountModel template_name = "ledger/coa_accounts/account_list.html" context_object_name = "accounts" def get_queryset(self): entity = self.request.user.dealer.entity qs = entity.get_all_accounts() return qs class AccountCreateView(LoginRequiredMixin, 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." def form_valid(self, form): form.instance.entity_model = self.request.user.dealer.entity form.instance.depth = 0 return super().form_valid(form) def get_form_kwargs(self): """ Override this method to pass additional keyword arguments to the form. """ entity = self.request.user.dealer.entity kwargs = super().get_form_kwargs() kwargs['coa_model'] = entity.get_default_coa() return kwargs class AccountDetailView(LoginRequiredMixin, DetailView): model = AccountModel template_name = "ledger/coa_accounts/account_detail.html" context_object_name = "account" class AccountUpdateView(LoginRequiredMixin, 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." @login_required def account_delete(request, pk): account = get_object_or_404(AccountModel, pk=pk) if request.method == "POST": account.delete() messages.success(request, "Account deleted successfully.") return redirect("account_list") return render(request, "ledger/coa_accounts/account_delete.html", {"account": account}) #Estimates class EstimateListView(LoginRequiredMixin, ListView): model = EstimateModel template_name = "sales/estimates/estimate_list.html" context_object_name = "estimates" class EstimateCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView): model = EstimateModel form_class = EstimateModelCreateForm template_name = "sales/estimates/estimate_form.html" success_url = reverse_lazy("estimate_list") success_message = "Estimate created successfully." def get_form_kwargs(self): """ Override this method to pass additional keyword arguments to the form. """ entity = self.request.user.dealer.entity kwargs = super().get_form_kwargs() kwargs['entity_slug'] = entity.slug kwargs['user_model'] = entity.admin return kwargs def get_context_data(self, **kwargs): entity = self.request.user.dealer.entity kwargs['items'] = entity.get_items_all() return super().get_context_data(**kwargs) def get_customer_queryset(self): entity = self.request.user.dealer.entity return entity.get_customer_queryset() def form_valid(self, form): form.instance.entity = self.request.user.dealer.entity return super().form_valid(form) # @csrf_exempt @login_required def create_estimate(request): entity = request.user.dealer.entity if request.method == 'POST': try: data = json.loads(request.body) title = data['title'] customer_id = data['customer'] terms = data['terms'] customer = entity.get_customers().filter(pk=customer_id).first() estimate = entity.create_estimate( estimate_title=title, customer_model=customer, contract_terms=terms) items = data.get('item[]', []) quantities = data.get('quantity[]', []) unit_costs = data.get('unitCost[]', []) unit_sales_prices = data.get('unitSalesPrice[]', []) total_costs = data.get('totalCost[]', []) # total_revenues = data.get('totalRevenue[]', []) if items: if isinstance(items, list): items = [entity.get_items_all().filter(pk=item).first() for item in items] estimate_itemtxs = { item.item_number: { 'unit_cost': float(unit_costs[i]), 'unit_revenue': float(unit_sales_prices[i]), 'quantity': float(quantities[i]), 'total_amount': float(total_costs[i]) } for i, item in enumerate(items) } else: item = entity.get_items_all().filter(pk=items).first() estimate_itemtxs = { item.item_number: { 'unit_cost': float(unit_costs), 'unit_revenue': float(unit_sales_prices), 'quantity': float(quantities), 'total_amount': float(total_costs) } } estimate.migrate_itemtxs( itemtxs=estimate_itemtxs, commit=True, operation=EstimateModel.ITEMIZE_APPEND ) url = reverse_lazy('estimate_detail', kwargs={'pk': estimate.pk}) return JsonResponse({'status': 'success', 'message': 'Estimate created successfully!','url':url}) except Exception as e: return JsonResponse({'status': 'error', 'message': f'An error occurred while processing the request.{e}'}, status=400) form = EstimateModelCreateForm(entity_slug=entity.slug,user_model=entity.admin) context = {'form': form,"items":entity.get_items_all()} return render(request, 'sales/estimates/estimate_form.html', context) class EstimateDetailView(LoginRequiredMixin, DetailView): model = EstimateModel template_name = "sales/estimates/estimate_detail.html" context_object_name = "estimate" def get_context_data(self, **kwargs): estimate = kwargs.get("object") if estimate.get_itemtxs_data(): total = sum(x.ce_cost_estimate for x in estimate.get_itemtxs_data()[0].all()) vat = models.VatRate.objects.filter(is_active=True).first() kwargs["vate_amount"] = (total * vat.vat_rate) kwargs["total"] = (total * vat.vat_rate) + total kwargs["vat"] = vat.rate return super().get_context_data(**kwargs) @login_required def estimate_mark_as(request, pk): estimate = get_object_or_404(EstimateModel, pk=pk) 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 == "accepted": 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() elif mark == "complete": if not estimate.can_complete(): messages.error(request, "Estimate is not ready for completion") return redirect("estimate_detail", pk=estimate.pk) estimate.mark_as_completed() messages.success(request, "Estimate marked as " + mark.upper()) return redirect("estimate_detail", pk=estimate.pk) def send_email(request,pk): estimate = get_object_or_404(EstimateModel, pk=pk) if not estimate.can_review(): messages.error(request, "Estimate is not ready for review") return redirect("estimate_detail", pk=estimate.pk) msg = f""" السلام عليكم {estimate.customer.customer_name}, أود أن أشارككم تقدير المشروع الذي ناقشناه. يرجى العثور على الوثيقة التفصيلية للمقترح المرفقة. يرجى مراجعة المقترح وإعلامي إذا كانت لديك أي أسئلة أو مخاوف. إذا كانت كل شيء يبدو جيدًا، يمكننا المضي قدمًا في المشروع. شكراً لاهتمامكم بهذا الأمر. Estimate تحياتي, Dear {estimate.customer.customer_name}, I hope this email finds you well. I wanted to share with you the estimate for the project we discussed. Please find attached the detailed estimate document. 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 """ subject = f'Estimate-{estimate.estimate_number} Review' message = msg from_email = 'manager@tenhal.sa' recipient_list = ['cutomer@tenhal.sa'] send_mail(subject, message, from_email, recipient_list) messages.success(request, "Email sent successfully!") estimate.mark_as_review() return redirect("estimate_detail", pk=estimate.pk) class EstimatePreviewView(LoginRequiredMixin, DetailView): model = EstimateModel context_object_name = "estimate" template_name = "sales/estimates/estimate_preview.html" def get_context_data(self, **kwargs): estimate = kwargs.get("object") if estimate.get_itemtxs_data(): total = sum(x.ce_cost_estimate for x in estimate.get_itemtxs_data()[0].all()) vat = models.VatRate.objects.filter(is_active=True).first() kwargs["vate_amount"] = (total * vat.vat_rate) kwargs["total"] = (total * vat.vat_rate) + total kwargs["vat"] = vat.rate return super().get_context_data(**kwargs)