haikal/inventory/views.py
2025-01-14 12:00:43 +00:00

2870 lines
98 KiB
Python

from decimal import Decimal
from django.core.paginator import Paginator
from django.forms import DateField, DateInput, HiddenInput, TextInput
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from django_ledger.models import (
EntityModel,
InvoiceModel,
BankAccountModel,
AccountModel,
JournalEntryModel,
TransactionModel,
EstimateModel,
CustomerModel,
LedgerModel,
ItemModel,
)
from django_ledger.forms.bank_account import (
BankAccountCreateForm,
BankAccountUpdateForm,
)
from django_ledger.forms.invoice import (
DraftInvoiceModelUpdateForm,
ApprovedInvoiceModelUpdateForm,
PaidInvoiceModelUpdateForm,
)
from django_ledger.forms.account import AccountModelCreateForm, AccountModelUpdateForm
from django_ledger.forms.estimate import EstimateModelCreateForm
from django_ledger.forms.invoice import InvoiceModelCreateForm
from django_ledger.forms.item import (
ServiceCreateForm,
ExpenseItemCreateForm,
ExpenseItemUpdateForm,
)
from django_ledger.forms.journal_entry import JournalEntryModelCreateForm
from django_ledger.io import roles
from django.contrib.admin.models import LogEntry
import logging
import json
import datetime
from django.db.models.functions import Coalesce
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 (
decodevin,
get_make,
get_model,
)
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 (
calculate_vat_amount,
get_calculations,
get_financial_values,
reserve_car,
send_email,
get_user_type,
)
from django.contrib.auth.models import User
from allauth.account import views
from django.db.models import Count, F, Value
from django.contrib.auth import authenticate
import cv2
import numpy as np
from pyzbar.pyzbar import decode
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")
# username = wf1.get("username")
email = wf1.get("email")
password = wf1.get("password")
password_confirm = wf1.get("confirm_password")
name = wf2.get("name")
arabic_name = wf2.get("arabic_name")
phone = wf2.get("phone_number")
crn = wf3.get("crn")
vrn = wf3.get("vrn")
address = wf3.get("address")
if password != password_confirm:
return JsonResponse({"error": "Passwords do not match."}, status=400)
try:
with transaction.atomic():
# user = User.objects.create(username=username, email=email)
user = User.objects.create(username=email, email=email)
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,
)
user = authenticate(request, email=email, password=password)
if user is not None:
return JsonResponse(
{"message": "User created successfully."}, status=200
)
else:
return JsonResponse({"error": "User creation failed."}, status=400)
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 TestView(TemplateView):
template_name = "test.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"])
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)
dealer = get_user_type(self.request)
total_cars = models.Car.objects.filter(dealer=dealer).count()
total_reservations = models.CarReservation.objects.filter(
reserved_until__gte=timezone.now()
).count()
stats = models.CarFinance.objects.aggregate(
total_cost_price=Sum("cost_price"),
total_selling_price=Sum("selling_price"),
)
total_cost_price = stats["total_cost_price"] or 0
total_selling_price = stats["total_selling_price"] or 0
total_profit = total_selling_price - total_cost_price
context["dealer"] = dealer
context["total_cars"] = total_cars
context["total_reservations"] = total_reservations
context["total_cost_price"] = total_cost_price
context["total_selling_price"] = total_selling_price
context["total_profit"] = total_profit
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):
dealer = get_user_type(self.request)
form.instance.dealer = 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,
"get_equipments": self.get_equipments,
"get_options": self.get_options,
}
handler = handlers.get(action)
if handler:
return handler(request)
else:
return JsonResponse({"error": "Invalid action"}, status=400)
def decode_vin(self, request):
vin_no = request.GET.get("vin_no")
car_existed = models.Car.objects.filter(vin=vin_no).exists()
if car_existed:
return JsonResponse({"error": _("VIN number exists")}, status=400)
if not vin_no or len(vin_no.strip()) != 17:
return JsonResponse(
{"success": False, "error": "Invalid VIN number provided."}, status=400
)
vin_no = vin_no.strip()
vin_data = {}
decoding_method = ""
# manufacturer_name = model_name = year_model = None
if not (result := decodevin(vin_no)):
return JsonResponse(
{"success": False, "error": "VIN not found in all sources."}, status=404
)
manufacturer_name, model_name, year_model = result.values()
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, year_begin__lte=year, year_end__gte=year
).values("id_car_serie", "name", "arabic_name", "generation_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)
def get_equipments(self, request):
trim_id = request.GET.get("trim_id")
equipments = (
models.CarEquipment.objects.filter(id_car_trim=trim_id)
.values("id_car_equipment", "name")
.order_by("name")
)
return JsonResponse(list(equipments), safe=False)
def get_options(self, request):
equipment_id = request.GET.get("equipment_id")
car_option_values = models.CarOptionValue.objects.filter(
id_car_equipment=equipment_id
)
options_by_parent = {}
for value in car_option_values:
option = value.id_car_option
parent = option.id_parent
parent_id = parent.id_car_option if parent else 0
parent_name = parent.name if parent else "Root"
if parent_id not in options_by_parent:
options_by_parent[parent_id] = {
"parent_name": parent_name,
"options": [],
}
option_data = {
"option_id": option.id_car_option,
"option_name": option.name,
"is_base": value.is_base,
"equipment_name": value.id_car_equipment.name,
}
options_by_parent[parent_id]["options"].append(option_data)
serialized_options = [
{"parent_name": v["parent_name"], "options": v["options"]}
for v in options_by_parent.values()
]
return JsonResponse(serialized_options, safe=False)
@method_decorator(csrf_exempt, name='dispatch')
class SearchCodeView(View):
template_name = "inventory/scan_vin.html"
def get(self, request, *args, **kwargs):
"""Render the form page."""
return render(request, self.template_name)
def post(self, request, *args, **kwargs):
image_file = request.FILES.get('image')
if image_file:
print("image received!")
image = cv2.imdecode(np.frombuffer(image_file.read(), np.uint8), cv2.IMREAD_COLOR)
decoded_objects = decode(image)
if decoded_objects:
print("image decoded!")
print(decoded_objects[0])
code = decoded_objects[0].data.decode('utf-8')
print("code received!")
print(code)
car = get_object_or_404(models.Car, vin=code)
name = car.id_car_make.get_local_name
print(name)
return redirect('car_detail', pk=car.pk)
else:
print("back to else statement")
return JsonResponse({'success': False, 'error': 'No code detected'})
else:
return JsonResponse({'success': False, 'error': 'No image provided'})
class CarInventory(LoginRequiredMixin, 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"]
dealer = get_user_type(self.request)
cars = models.Car.objects.filter(
dealer=dealer,
id_car_make=make_id,
id_car_model=model_id,
id_car_trim=trim_id,
).order_by("receiving_date")
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 = get_user_type(request)
# Annotate total cars by make, model, and trim
cars = (
models.Car.objects.filter(dealer=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": {},
}
try:
inventory[make.id_car_make]["models"][model.id_car_model]["total_cars"] += 1
trim = car.id_car_trim
if (
trim
and trim.id_car_trim
not in inventory[make.id_car_make]["models"][model.id_car_model][
"trims"
]
):
inventory[make.id_car_make]["models"][model.id_car_model]["trims"][
trim.id_car_trim
] = {
"trim_id": trim.id_car_trim,
"trim_name": trim.name,
"total_cars": 0,
}
inventory[make.id_car_make]["models"][model.id_car_model]["trims"][
trim.id_car_trim
]["total_cars"] += 1
except Exception as e:
print(e)
result = {
"total_cars": 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
def get_form(self, form_class=None):
form = super().get_form(form_class)
dealer = get_user_type(self.request)
form.fields["additional_finances"].queryset = models.AdditionalServices.objects.filter(dealer=dealer)
return form
# def get_initial(self):
# initial = super().get_initial()
# instance = self.get_object()
# dealer = get_user_type(self.request.user.dealer)
# selected_items = instance.additional_services.filter(dealer=dealer)
# initial["additional_finances"] = selected_items
# return initial
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
def get_initial(self):
initial = super().get_initial()
instance = self.get_object()
dealer = get_user_type(self.request)
selected_items = instance.additional_services.filter(dealer=dealer)
initial["additional_finances"] = selected_items
return initial
def get_form(self, form_class=None):
form = super().get_form(form_class)
dealer = get_user_type(self.request)
form.fields[
"additional_finances"
].queryset = models.AdditionalServices.objects.filter(dealer=dealer)
return form
class CarUpdateView(LoginRequiredMixin, 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"])
dealer = get_user_type(self.request)
form.instance.owner = dealer
form.save()
messages.success(self.request, "Car saved successfully.")
return super().form_valid(form)
class CarLocationUpdateView(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)
response = reserve_car(car, request)
return response
return JsonResponse(
{"success": False, "message": "Invalid request method."}, status=400
)
@login_required
def manage_reservation(request, reservation_id):
reservation = get_object_or_404(
models.CarReservation, pk=reservation_id, reserved_by=request.user
)
if request.method == "POST":
action = request.POST.get("action")
if action == "renew":
reservation.reserved_until = timezone.now() + timezone.timedelta(hours=24)
reservation.save()
messages.success(request, _("Reservation renewed successfully."))
return redirect("car_detail", pk=reservation.car.pk)
elif action == "cancel":
car = reservation.car
reservation.delete()
car.status = models.CarStatusChoices.AVAILABLE
car.save()
messages.success(request, _("Reservation canceled successfully."))
return redirect("car_detail", pk=reservation.car.pk)
else:
return JsonResponse(
{"success": False, "message": _("Invalid action.")}, status=400
)
return JsonResponse(
{"success": False, "message": _("Invalid request method.")}, status=400
)
class DealerDetailView(LoginRequiredMixin, DetailView):
model = models.Dealer
template_name = "dealers/dealer_detail.html"
context_object_name = "dealer"
def get_queryset(self):
total_count = models.Dealer.objects.annotate(
staff_count=Coalesce(Count("staff"), Value(0)),
total_count=F("staff_count") + Value(1),
)
return total_count
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, ListView):
model = models.Customer
home_label = _("customers")
context_object_name = "customers"
paginate_by = 10
template_name = "customers/customer_list.html"
def get_queryset(self):
query = self.request.GET.get("q")
dealer = get_user_type(self.request)
customers = models.Customer.objects.filter(dealer=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, DetailView):
model = models.Customer
template_name = "customers/view_customer.html"
context_object_name = "customer"
def get_context_data(self, **kwargs):
dealer = get_user_type(self.request)
entity = dealer.entity
context = super().get_context_data(**kwargs)
name = f"{context['customer'].first_name} {context['customer'].middle_name} {context['customer'].last_name}"
context["estimates"] = entity.get_estimates().filter(
customer__customer_name=name
)
context["notes"] = models.Notes.objects.filter(
content_type__model="customer", object_id=self.object.id
)
context["activities"] = models.Activity.objects.filter(
content_type__model="customer", object_id=self.object.id
)
return context
def add_note_to_customer(request, pk):
customer = get_object_or_404(models.Customer, pk=pk)
if request.method == "POST":
form = forms.NoteForm(request.POST)
if form.is_valid():
note = form.save(commit=False)
note.content_object = customer
note.created_by = request.user
note.save()
return redirect("customer_detail", pk=pk)
else:
form = forms.NoteForm()
return render(request, "crm/add_note.html", {"form": form, "customer": customer})
def add_activity_to_customer(request, pk):
customer = get_object_or_404(models.Customer, pk=pk)
if request.method == "POST":
form = forms.ActivityForm(request.POST)
if form.is_valid():
activity = form.save(commit=False)
activity.content_object = customer
activity.created_by = request.user
activity.save()
return redirect("customer_detail", pk=pk)
else:
form = forms.ActivityForm()
return render(
request, "crm/add_activity.html", {"form": form, "customer": customer}
)
class CustomerCreateView(
LoginRequiredMixin,
SuccessMessageMixin,
CreateView,
):
model = models.Customer
form_class = forms.CustomerForm
template_name = "customers/customer_form.html"
success_url = reverse_lazy("customer_list")
success_message = _("Customer created successfully.")
def form_valid(self, form):
form.instance.dealer = get_user_type(self.request)
return super().form_valid(form)
class CustomerUpdateView(
LoginRequiredMixin,
SuccessMessageMixin,
UpdateView,
):
model = models.Customer
form_class = forms.CustomerForm
template_name = "customers/customer_form.html"
success_url = reverse_lazy("customer_list")
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, ListView):
model = models.Vendor
context_object_name = "vendors"
paginate_by = 10
template_name = "vendors/vendors_list.html"
class VendorDetailView(LoginRequiredMixin, DetailView):
model = models.Vendor
template_name = "vendors/view_vendor.html"
class VendorCreateView(
LoginRequiredMixin,
SuccessMessageMixin,
CreateView,
):
model = models.Vendor
form_class = forms.VendorForm
template_name = "vendors/vendor_form.html"
success_url = reverse_lazy("vendor_list")
success_message = _("Vendor created successfully.")
def form_valid(self, form):
form.instance.dealer = get_user_type(self.request)
return super().form_valid(form)
class VendorUpdateView(
LoginRequiredMixin,
SuccessMessageMixin,
UpdateView,
):
model = models.Vendor
form_class = forms.VendorForm
template_name = "vendors/vendor_form.html"
success_url = reverse_lazy("vendor_list")
success_message = _("Vendor updated successfully.")
@login_required
def delete_vendor(request, pk):
vendor = get_object_or_404(models.Vendor, pk=pk)
vendor.delete()
messages.success(request, _("Vendor deleted successfully."))
return redirect("vendor_list")
class QuotationCreateView(LoginRequiredMixin, CreateView):
model = models.SaleQuotation
form_class = forms.QuotationForm
template_name = "sales/quotation_form.html"
def form_valid(self, form):
form.instance.dealer = get_user_type(self.request)
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, ListView):
model = models.SaleQuotation
template_name = "sales/quotation_list.html"
context_object_name = "quotations"
paginate_by = 10
def get_queryset(self):
status = self.request.GET.get("status")
dealer = get_user_type(self.request)
queryset = dealer.sales.all()
if status:
queryset = queryset.filter(status=status)
return queryset
class QuotationDetailView(LoginRequiredMixin, DetailView):
model = models.SaleQuotation
template_name = "sales/quotation_detail.html"
context_object_name = "quotation"
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 = get_user_type(request)
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 = get_user_type(request)
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
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
)
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
)
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, DetailView):
model = models.SalesOrder
template_name = "sales/sales_order_detail.html"
context_object_name = "sales_order"
slug_field = "order_id"
slug_url_kwarg = "order_id"
# Users
class UserListView(LoginRequiredMixin, ListView):
model = models.Staff
context_object_name = "users"
paginate_by = 10
template_name = "users/user_list.html"
def get_queryset(self):
dealer = get_user_type(self.request)
staff = models.Staff.objects.filter(dealer=dealer).all()
return staff
class UserDetailView(LoginRequiredMixin, DetailView):
model = models.Staff
template_name = "users/user_detail.html"
context_object_name = "user_"
class UserCreateView(
LoginRequiredMixin,
SuccessMessageMixin,
CreateView,
):
model = models.Staff
form_class = forms.StaffForm
template_name = "users/user_form.html"
success_url = reverse_lazy("user_list")
success_message = _("User created successfully.")
def form_valid(self, form):
dealer = get_user_type(self.request)
form.instance.dealer = dealer
email = form.cleaned_data["email"]
password = "Tenhal@123"
user = User.objects.create_user(username=email, email=email, password=password)
staff = form.save(commit=False)
staff.user = user
staff.save()
return super().form_valid(form)
class UserUpdateView(
LoginRequiredMixin,
SuccessMessageMixin,
UpdateView,
):
model = models.Staff
form_class = forms.StaffForm
template_name = "users/user_form.html"
success_url = reverse_lazy("user_list")
success_message = _("User updated successfully.")
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs["instance"] = self.get_object() # Pass the Staff instance to the form
return kwargs
def UserDeleteview(request, pk):
user = get_object_or_404(models.Staff, 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"
paginate_by = 10
def get_queryset(self):
dealer = get_user_type(self.request)
data = models.Organization.objects.filter(dealer=dealer).all()
return data
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
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"
def get_queryset(self):
dealer = get_user_type(self.request)
data = models.Representative.objects.filter(dealer=dealer).all()
return data
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
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
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
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 = get_user_type(request)
if request.method == "POST":
form = forms.PaymentForm(request.POST)
if form.is_valid():
form.instance.quotation = quotation
insatnce = form.save()
dealer = 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
)
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):
dealer = get_user_type(self.request)
return BankAccountModel.objects.filter(entity_model=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):
dealer = get_user_type(self.request)
form.instance.entity_model = dealer.entity
return super().form_valid(form)
def get_form_kwargs(self):
dealer = get_user_type(self.request)
entity = dealer.entity
kwargs = super().get_form_kwargs()
kwargs["entity_slug"] = entity.slug
kwargs["user_model"] = entity.admin
return kwargs
class BankAccountDetailView(LoginRequiredMixin, 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):
dealer = get_user_type(self.request)
entity = dealer.entity
kwargs = super().get_form_kwargs()
kwargs["entity_slug"] = entity.slug # Get entity_slug from URL
kwargs["user_model"] = entity.admin # Get user_model from the request
return kwargs
@login_required
def bank_account_delete(request, pk):
bank_account = get_object_or_404(BankAccountModel, pk=pk)
if request.method == "POST":
bank_account.delete()
messages.success(request, "Bank account deleted successfully.")
return redirect("bank_account_list")
return render(
request,
"ledger/bank_accounts/bank_account_delete.html",
{"bank_account": bank_account},
)
# Accounts
class AccountListView(LoginRequiredMixin, ListView):
model = AccountModel
template_name = "ledger/coa_accounts/account_list.html"
context_object_name = "accounts"
paginate_by = 10
def get_queryset(self):
dealer = get_user_type(self.request)
entity = dealer.entity
qs = entity.get_all_accounts()
paginator = Paginator(qs, 20)
page_number = self.request.GET.get("page", 1) # Default to page 1
page_obj = paginator.get_page(page_number)
return page_obj
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."
def get_form(self, form_class=None):
form = super().get_form(form_class)
form.fields['_ref_node_id'].widget = HiddenInput()
form.fields['_position'].widget = HiddenInput()
return form
@login_required
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"
def get_queryset(self):
entity = self.request.user.dealer.entity
return entity.get_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):
dealer = get_user_type(request)
entity = dealer.entity
if request.method == "POST":
# try:
data = json.loads(request.body)
title = data.get("title")
customer_id = data.get("customer")
terms = data.get("terms")
customer = entity.get_customers().filter(pk=customer_id).first()
items = data.get("item", [])
quantities = data.get("quantity", [])
if not all([items, quantities]):
return JsonResponse(
{"status": "error", "message": "Items and Quantities are required"},
status=400,
)
if isinstance(quantities, list):
if "0" in quantities:
return JsonResponse(
{"status": "error", "message": "Quantity must be greater than zero"}
)
else:
if int(quantities) <= 0:
return JsonResponse(
{"status": "error", "message": "Quantity must be greater than zero"}
)
estimate = entity.create_estimate(
estimate_title=title, customer_model=customer, contract_terms=terms
)
if isinstance(items, list):
item_quantity_map = {}
for item, quantity in zip(items, quantities):
if item in item_quantity_map:
item_quantity_map[item] += int(quantity)
else:
item_quantity_map[item] = int(quantity)
item_list = list(item_quantity_map.keys())
quantity_list = list(item_quantity_map.values())
items_list = [
{"item_id": item_list[i], "quantity": quantity_list[i]}
for i in range(len(item_list))
]
items_txs = []
for item in items_list:
item_instance = ItemModel.objects.get(pk=item.get("item_id"))
car_instance = models.Car.objects.get(vin=item_instance.name)
items_txs.append(
{
"item_number": item_instance.item_number,
"quantity": Decimal(item.get("quantity")),
"unit_cost": car_instance.finances.cost_price,
"unit_revenue": car_instance.finances.selling_price,
"total_amount": (car_instance.finances.total_vat)
* int(item.get("quantity")),
}
)
estimate_itemtxs = {
item.get("item_number"): {
"unit_cost": item.get("unit_cost"),
"unit_revenue": item.get("unit_revenue"),
"quantity": item.get("quantity"),
"total_amount": item.get("total_amount"),
}
for item in items_txs
}
else:
item = entity.get_items_all().filter(pk=items).first()
instance = models.Car.objects.get(vin=item.name)
estimate_itemtxs = {
item.item_number: {
"unit_cost": instance.finances.cost_price,
"unit_revenue": instance.finances.selling_price,
"quantity": Decimal(quantities),
"total_amount": instance.finances.total_vat * int(quantities),
}
}
estimate.migrate_itemtxs(
itemtxs=estimate_itemtxs,
commit=True,
operation=EstimateModel.ITEMIZE_APPEND,
)
if isinstance(items, list):
for item in items:
item_instance = ItemModel.objects.get(pk=item)
instance = models.Car.objects.get(vin=item_instance.name)
reserve_car(instance, request)
else:
item_instance = ItemModel.objects.get(pk=items)
instance = models.Car.objects.get(vin=item_instance.name)
response = reserve_car(instance, request)
url = reverse("estimate_detail", kwargs={"pk": estimate.pk})
return JsonResponse(
{
"status": "success",
"message": "Estimate created successfully!",
"url": f"{url}",
}
)
form = EstimateModelCreateForm(entity_slug=entity.slug, user_model=entity.admin)
car_list = models.Car.objects.filter(
dealer=dealer, finances__selling_price__gt=0
).exclude(status="reserved")
context = {
"form": form,
"items": [
{
"car": x,
"product": entity.get_items_all()
.filter(item_role=ItemModel.ITEM_ROLE_PRODUCT, name=x.vin)
.first(),
}
for x in car_list
],
}
print(context)
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():
data = get_financial_values(estimate)
kwargs["vat_amount"] = data["vat_amount"]
kwargs["total"] = data["grand_total"]
kwargs["discount_amount"] = data["discount_amount"]
kwargs["vat"] = data["vat"]
kwargs["car_and_item_info"] = data["car_and_item_info"]
kwargs["additional_services"] = data["additional_services"]
kwargs["invoice"] = (
InvoiceModel.objects.all().filter(ce_model=estimate).first()
)
return super().get_context_data(**kwargs)
class PaymentRequest(LoginRequiredMixin, DetailView):
model = EstimateModel
template_name = "sales/estimates/payment_request_detail.html"
context_object_name = "estimate"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["cars"] = [
models.Car.objects.get(vin=car.item_model.name)
for car in context["estimate"].get_itemtxs_data()[0].all()
]
return context
class EstimatePreviewView(LoginRequiredMixin, 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():
data = get_financial_values(estimate)
kwargs["vat_amount"] = data["vat_amount"]
kwargs["total"] = data["grand_total"]
kwargs["discount_amount"] = data["discount_amount"]
kwargs["vat"] = data["vat"]
kwargs["car_and_item_info"] = data["car_and_item_info"]
kwargs["additional_services"] = data["additional_services"]
return super().get_context_data(**kwargs)
@login_required
def estimate_mark_as(request, pk):
estimate = get_object_or_404(EstimateModel, pk=pk)
entity = estimate.entity
mark = request.GET.get("mark")
if mark:
if mark == "review":
if not estimate.can_review():
messages.error(request, "Estimate is not ready for review")
return redirect("estimate_detail", pk=estimate.pk)
estimate.mark_as_review()
elif mark == "approved":
if not estimate.can_approve():
messages.error(request, "Estimate is not ready for approval")
return redirect("estimate_detail", pk=estimate.pk)
estimate.mark_as_approved()
messages.success(request, "Estimate approved successfully.")
elif mark == "rejected":
if not estimate.can_cancel():
messages.error(request, "Estimate is not ready for rejection")
return redirect("estimate_detail", pk=estimate.pk)
estimate.mark_as_canceled()
messages.success(request, "Estimate canceled successfully.")
elif mark == "completed":
if not estimate.can_complete():
messages.error(request, "Estimate is not ready for completion")
return redirect("estimate_detail", pk=estimate.pk)
estimate.save()
messages.success(request, "Estimate marked as " + mark.upper())
return redirect("estimate_detail", pk=estimate.pk)
# Invoice
class InvoiceListView(LoginRequiredMixin, ListView):
model = InvoiceModel
template_name = "sales/invoices/invoice_list.html"
context_object_name = "invoices"
def get_queryset(self):
dealer = get_user_type(self.request)
entity = dealer.entity
return entity.get_invoices()
class InvoiceDetailView(LoginRequiredMixin, DetailView):
model = InvoiceModel
template_name = "sales/invoices/invoice_detail.html"
context_object_name = "invoice"
def get_context_data(self, **kwargs):
invoice = kwargs.get("object")
if invoice.get_itemtxs_data():
data = get_financial_values(invoice)
kwargs["vat_amount"] = data["vat_amount"]
kwargs["total"] = data["grand_total"]
kwargs["discount_amount"] = data["discount_amount"]
kwargs["vat"] = data["vat"]
kwargs["car_and_item_info"] = data["car_and_item_info"]
kwargs["additional_services"] = data["additional_services"]
kwargs["payments"] = JournalEntryModel.objects.filter(
ledger=invoice.ledger
).all()
return super().get_context_data(**kwargs)
class DraftInvoiceModelUpdateFormView(LoginRequiredMixin, UpdateView):
model = InvoiceModel
form_class = DraftInvoiceModelUpdateForm
template_name = "sales/invoices/draft_invoice_update.html"
success_url = reverse_lazy("invoice_list")
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
dealer = get_user_type(self.request.user.dealer)
kwargs["entity_slug"] = dealer.entity
kwargs["user_model"] = dealer.entity.admin
return kwargs
class ApprovedInvoiceModelUpdateFormView(LoginRequiredMixin, UpdateView):
model = InvoiceModel
form_class = ApprovedInvoiceModelUpdateForm
template_name = "sales/invoices/approved_invoice_update.html"
success_url = reverse_lazy("invoice_list")
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
dealer = get_user_type(self.request.user.dealer)
kwargs["entity_slug"] = dealer.entity
kwargs["user_model"] = dealer.entity.admin
return kwargs
def get_success_url(self):
return reverse_lazy("invoice_detail", kwargs={"pk": self.object.pk})
class PaidInvoiceModelUpdateFormView(LoginRequiredMixin, UpdateView):
model = InvoiceModel
form_class = PaidInvoiceModelUpdateForm
template_name = "sales/invoices/paid_invoice_update.html"
success_url = reverse_lazy("invoice_list")
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
dealer = get_user_type(self.request.user.dealer)
kwargs["entity_slug"] = dealer.entity
kwargs["user_model"] = dealer.entity.admin
return kwargs
def get_success_url(self):
return reverse_lazy("invoice_detail", kwargs={"pk": self.object.pk})
def form_valid(self, form):
invoice = form.save()
if invoice.get_amount_open() > 0:
messages.error(self.request, "Invoice is not fully paid")
return redirect("invoice_detail", pk=invoice.pk)
else:
invoice.post_ledger()
invoice.save()
return super().form_valid(form)
@login_required
def invoice_mark_as(request, pk):
invoice = get_object_or_404(InvoiceModel, pk=pk)
entity = EntityModel.objects.first() # will change later
user = entity.admin
mark = request.GET.get("mark")
if mark:
if mark == "accept":
if not invoice.can_approve():
messages.error(request, "invoice is not ready for approval")
return redirect("invoice_detail", pk=invoice.pk)
invoice.mark_as_approved(entity_slug=entity.slug, user_model=user)
# invoice.post_ledger()
invoice.save()
ledger = (
entity.get_ledgers().filter(name=f"Invoice {str(invoice.pk)}").first()
)
if not ledger:
ledger = entity.create_ledger(name=f"Invoice {str(invoice.pk)}")
ledger.invoicemodel = invoice
ledger.save()
# elif mark == "complete":
# if not invoice.can_complete():
# messages.error(request, "invoice is not ready for completion")
return redirect("invoice_detail", pk=invoice.pk)
def invoice_create(request, pk):
estimate = get_object_or_404(EstimateModel, pk=pk)
entity = request.user.dealer.entity
if request.method == "POST":
form = forms.InvoiceModelCreateForm(
request.POST, entity_slug=entity.slug, user_model=entity.admin
)
if form.is_valid():
invoice = form.save(commit=False)
ledger = entity.create_ledger(name=str(invoice.pk))
invoice.ledgar = ledger
ledger.invoicemodel = invoice
ledger.save()
invoice.save()
unit_items = estimate.get_itemtxs_data()[0]
itemtxs = []
for item in unit_items:
car = models.Car.objects.get(vin=item.item_model.name)
itemtxs.append(
{
"item_number": item.item_model.item_number,
"unit_cost": car.finances.total_vat,
"unit_revenue": car.finances.total_vat,
"quantity": item.ce_quantity,
"total_amount": Decimal(car.finances.total_vat)
* Decimal(item.ce_quantity),
}
)
invoice_itemtxs = {
i.get("item_number"): {
"unit_cost": i.get("unit_cost"),
"quantity": i.get("quantity"),
"total_amount": i.get("total_amount"),
}
for i in itemtxs
}
invoice_itemtxs = invoice.migrate_itemtxs(
itemtxs=invoice_itemtxs,
commit=True,
operation=InvoiceModel.ITEMIZE_APPEND,
)
invoice.bind_estimate(estimate)
invoice.mark_as_review()
estimate.mark_as_completed()
estimate.save()
invoice.save()
messages.success(request, "Invoice created successfully!")
return redirect("invoice_detail", pk=invoice.pk)
form = forms.InvoiceModelCreateForm(
entity_slug=entity.slug, user_model=entity.admin
)
form.initial.update(
{
"customer": estimate.customer,
"cash_account": entity.get_default_coa_accounts().get(name="Cash"),
"prepaid_account": entity.get_default_coa_accounts().get(
name="Accounts Receivable"
),
"unearned_account": entity.get_default_coa_accounts().get(
name="Deferred Revenue"
),
}
)
context = {
"form": form,
"estimate": estimate,
}
return render(request, "sales/invoices/invoice_create.html", context)
class InvoicePreviewView(LoginRequiredMixin, DetailView):
model = InvoiceModel
context_object_name = "invoice"
template_name = "sales/invoices/invoice_preview.html"
def get_context_data(self, **kwargs):
invoice = kwargs.get("object")
if invoice.get_itemtxs_data():
data = get_financial_values(invoice)
kwargs["vat_amount"] = data["vat_amount"]
kwargs["total"] = data["grand_total"]
kwargs["discount_amount"] = data["discount_amount"]
kwargs["vat"] = data["vat"]
kwargs["car_and_item_info"] = data["car_and_item_info"]
kwargs["additional_services"] = data["additional_services"]
return super().get_context_data(**kwargs)
# payments
def PaymentCreateView(request, pk=None):
invoice = InvoiceModel.objects.filter(pk=pk).first()
entity = request.user.dealer.entity
if request.method == "POST":
form = forms.PaymentForm(request.POST)
if form.is_valid():
amount = form.cleaned_data.get("amount")
invoice = form.cleaned_data.get("invoice")
payment_method = form.cleaned_data.get("payment_method")
ledger = None
try:
vat_amount = 0
total_amount = 0
if invoice.terms == "on_receipt":
for x in invoice.get_itemtxs_data()[0].all():
vat_amount += models.Car.objects.get(
vin=x.item_model.name
).finances.vat_amount
total_amount += models.Car.objects.get(
vin=x.item_model.name
).finances.total_discount
ledger = LedgerModel.objects.filter(
name=str(invoice.pk), entity=entity
).first()
journal = JournalEntryModel.objects.create(
posted=False,
description=f"Payment for Invoice {invoice.invoice_number}",
ledger=ledger,
locked=False,
origin="Payment",
)
credit_account = entity.get_default_coa_accounts().get(
name="Sales Revenue"
)
debit_account = None
if payment_method == "cash":
debit_account = entity.get_default_coa_accounts().get(
name="Cash", active=True
)
elif payment_method == "credit":
debit_account = entity.get_default_coa_accounts().get(
name="Accounts Receivable", active=True
)
else:
debit_account = entity.get_default_coa_accounts().get(
name="Cash in Bank", active=True
)
vat_payable_account = entity.get_default_coa_accounts().get(
name="VAT Payable", active=True
)
TransactionModel.objects.create(
journal_entry=journal,
account=debit_account, # Debit Cash
amount=amount, # Payment amount
tx_type="debit",
description="Payment Received",
)
TransactionModel.objects.create(
journal_entry=journal,
account=credit_account, # Credit Accounts Receivable
amount=total_amount, # Payment amount
tx_type="credit",
description="Payment Received",
)
if vat_amount > 0:
TransactionModel.objects.create(
journal_entry=journal,
account=vat_payable_account, # Credit VAT Payable
amount=vat_amount,
tx_type="credit",
description="VAT Payable on Invoice",
)
invoice.make_payment(amount)
invoice.save()
except Exception as e:
messages.error(request, f"Error creating payment: {str(e)}")
else:
messages.error(request, f"Invalid form data: {str(form.errors)}")
return redirect("invoice_detail", pk=invoice.pk)
form = forms.PaymentForm()
form.initial["amount"] = invoice.amount_due
if invoice:
form.initial["invoice"] = invoice
return render(
request, "sales/payments/payment_form.html", {"invoice": invoice, "form": form}
)
def PaymentListView(request):
dealer = get_user_type(request)
entity = dealer.entity
journals = JournalEntryModel.objects.filter(ledger__entity=entity).all()
return render(request, "sales/payments/payment_list.html", {"journals": journals})
def PaymentDetailView(request, pk):
journal = JournalEntryModel.objects.filter(pk=pk).first()
transactions = (
TransactionModel.objects.filter(journal_entry=journal)
.order_by("account__code")
.all()
)
return render(
request,
"sales/payments/payment_details.html",
{"journal": journal, "transactions": transactions},
)
def payment_mark_as_paid(request, pk):
invoice = get_object_or_404(InvoiceModel, pk=pk)
if request.method == "POST":
try:
if invoice.amount_due == invoice.amount_paid:
if not invoice.is_paid() and invoice.can_pay():
invoice.mark_as_paid(
entity_slug=invoice.ledger.entity.slug,
user_model=invoice.ledger.entity.admin,
)
invoice.save()
invoice.ledger.lock_journal_entries()
invoice.ledger.post_journal_entries()
invoice.ledger.post()
invoice.ledger.save()
messages.success(request, "Payment created successfully!")
except Exception as e:
messages.error(request, f"Error: {str(e)}")
return redirect("invoice_detail", pk=invoice.pk)
# activity log
class UserActivityLogListView(ListView):
model = models.UserActivityLog
template_name = "dealers/activity_log.html"
context_object_name = "logs"
paginate_by = 10
def get_queryset(self):
queryset = super().get_queryset()
if "user" in self.request.GET:
queryset = queryset.filter(user__email=self.request.GET["user"])
return queryset
# CRM RELATED VIEWS
class LeadListView(ListView):
model = models.Lead
template_name = "crm/leads/lead_list.html"
context_object_name = "leads"
paginate_by = 10
def get_queryset(self):
dealer = get_user_type(self.request)
leads = models.Lead.objects.filter(dealer=dealer).all()
return leads
class LeadDetailView(DetailView):
model = models.Lead
template_name = "crm/leads/lead_detail.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["notes"] = models.Notes.objects.filter(
content_type__model="lead", object_id=self.object.id
)
context["activities"] = models.Activity.objects.filter(
content_type__model="lead", object_id=self.object.id
)
context["status_history"] = models.LeadStatusHistory.objects.filter(
lead=self.object
)
return context
class LeadCreateView(CreateView, SuccessMessageMixin, LoginRequiredMixin):
model = models.Lead
form_class = forms.LeadForm
template_name = 'crm/leads/lead_form.html'
# success_message = "Lead created successfully!"
success_url = reverse_lazy('lead_list')
def form_valid(self, form):
print("Form data:", form.cleaned_data) # Debug form data
dealer = get_user_type(self.request)
form.instance.dealer = dealer
return super().form_valid(form)
def get_car_models(request):
make_id = request.GET.get('id_car_make')
if make_id:
car_models = models.CarModel.objects.filter(id_car_make=make_id).values('id_car_model', 'name', 'arabic_name')
return JsonResponse(list(car_models), safe=False)
return JsonResponse([], safe=False)
class LeadUpdateView(UpdateView):
model = models.Lead
form_class = forms.LeadForm
template_name = 'crm/leads/lead_form.html'
success_url = reverse_lazy('lead_list')
class LeadDeleteView(DeleteView):
model = models.Lead
template_name = "crm/leads/lead_confirm_delete.html"
success_url = reverse_lazy("lead_list")
def add_note_to_lead(request, pk):
lead = get_object_or_404(models.Lead, pk=pk)
if request.method == "POST":
form = forms.NoteForm(request.POST)
if form.is_valid():
note = form.save(commit=False)
note.content_object = lead
note.created_by = request.user
note.save()
return redirect("lead_detail", pk=pk)
else:
form = forms.NoteForm()
return render(request, "crm/add_note.html", {"form": form, "lead": lead})
def add_activity_to_lead(request, pk):
lead = get_object_or_404(models.Lead, pk=pk)
if request.method == "POST":
form = forms.ActivityForm(request.POST)
if form.is_valid():
activity = form.save(commit=False)
activity.content_object = lead
activity.created_by = request.user
activity.save()
return redirect("lead_detail", pk=pk)
else:
form = forms.ActivityForm()
return render(request, "crm/add_activity.html", {"form": form, "lead": lead})
class OpportunityCreateView(CreateView):
model = models.Opportunity
form_class = forms.OpportunityForm
template_name = "crm/opportunities/opportunity_form.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
dealer = get_user_type(self.request)
context["customer"] = models.Customer.objects.filter(dealer=dealer)
context["cars"] = models.Car.objects.filter(dealer=dealer)
return context
def form_valid(self, form):
dealer = get_user_type(self.request)
form.instance.dealer = dealer
staff = self.request.user.staff
print(dealer)
print(staff)
form.instance.staff = staff
return super().form_valid(form)
def get_success_url(self):
return reverse_lazy("opportunity_detail", kwargs={"pk": self.object.pk})
class OpportunityUpdateView(UpdateView):
model = models.Opportunity
form_class = forms.OpportunityForm
template_name = "crm/opportunities/opportunity_form.html"
def get_success_url(self):
return reverse_lazy("opportunity_detail", kwargs={"pk": self.object.pk})
class OpportunityDetailView(DetailView):
model = models.Opportunity
template_name = "crm/opportunities/opportunity_detail.html"
context_object_name = "opportunity"
class OpportunityListView(ListView):
model = models.Opportunity
template_name = "crm/opportunities/opportunity_list.html"
context_object_name = "opportunities"
def get_queryset(self):
dealer = get_user_type(self.request)
data = models.Opportunity.objects.filter(dealer=dealer).all()
return data
@login_required
def delete_opportunity(request, pk):
opportunity = get_object_or_404(models.Opportunity, pk=pk)
opportunity.delete()
messages.success(request, _("Opportunity deleted successfully."))
return redirect("opportunity_list")
# class OpportunityLogsView(LoginRequiredMixin, ListView):
# model = models.OpportunityLog
# template_name = "crm/opportunity_logs.html"
# context_object_name = "logs"
#
# def get_queryset(self):
# opportunity_id = self.kwargs["pk"]
# return models.OpportunityLog.objects.filter(
# opportunity_id=opportunity_id
# ).order_by("-created_at")
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context["opportunity"] = models.Opportunity.objects.get(pk=self.kwargs["pk"])
# return context
class NotificationListView(LoginRequiredMixin, ListView):
model = models.Notification
template_name = "crm/notifications_history.html"
context_object_name = "notifications"
paginate_by = 10
def get_queryset(self):
return models.Notification.objects.filter(user=self.request.user).order_by(
"-created"
)
@login_required
def mark_notification_as_read(request, pk):
notification = get_object_or_404(models.Notification, pk=pk, user=request.user)
notification.is_read = True
notification.save()
messages.success(request, _("Notification marked as read."))
return redirect("notifications_history")
@login_required
def fetch_notifications(request):
notifications = models.Notification.objects.filter(
user=request.user, is_read=False
).order_by("-created")
notifications_data = [
{
"id": notification.id,
"message": notification.message,
"created": notification.created.strftime("%Y-%m-%d %H:%M:%S"),
}
for notification in notifications
]
return JsonResponse({"notifications": notifications_data})
class ItemServiceCreateView(CreateView):
model = models.AdditionalServices
form_class = forms.AdditionalServiceForm
template_name = "items/service/service_create.html"
success_url = reverse_lazy("item_service_list")
def form_valid(self, form):
vat = models.VatRate.objects.get(is_active=True)
form.instance.dealer = get_user_type(self.request.user.dealer)
if form.instance.taxable:
form.instance.price = (form.instance.price * vat.rate) + form.instance.price
return super().form_valid(form)
class ItemServiceListView(ListView):
model = ItemModel
template_name = "items/service/service_list.html"
context_object_name = "services"
def get_queryset(self):
dealer = get_user_type(self.request)
items = dealer.entity.get_items_services()
return items
class ItemExpenseCreateView(CreateView):
model = ItemModel
form_class = ExpenseItemCreateForm
template_name = "items/expenses/expense_create.html"
success_url = reverse_lazy("item_expense_list")
def get_form_kwargs(self):
dealer = get_user_type(self.request.user.dealer)
kwargs = super().get_form_kwargs()
kwargs["entity_slug"] = dealer.entity.slug
kwargs["user_model"] = dealer.entity.admin
return kwargs
def form_valid(self, form):
dealer = get_user_type(self.request.user.dealer)
form.instance.entity = dealer.entity
return super().form_valid(form)
class ItemExpenseUpdateView(UpdateView):
model = ItemModel
form_class = ExpenseItemUpdateForm
template_name = "items/expenses/expense_update.html"
success_url = reverse_lazy("item_expense_list")
def get_form_kwargs(self):
dealer = get_user_type(self.request.user.dealer)
kwargs = super().get_form_kwargs()
kwargs["entity_slug"] = dealer.entity.slug
kwargs["user_model"] = dealer.entity.admin
return kwargs
def form_valid(self, form):
dealer = get_user_type(self.request.user.dealer)
form.instance.entity = dealer.entity
return super().form_valid(form)
class ItemExpenseListView(ListView):
model = ItemModel
template_name = "items/expenses/expenses_list.html"
context_object_name = "expenses"
def get_queryset(self):
dealer = get_user_type(self.request)
items = dealer.entity.get_items_expenses()
return items
class SubscriptionPlans(ListView):
model = models.SubscriptionPlan
template_name = "subscriptions/subscription_plan.html"
context_object_name = "plans"
# email
def send_email_view(request, pk):
estimate = get_object_or_404(EstimateModel, pk=pk)
if request.method == "POST":
# if not estimate.can_review():
# messages.error(request, "Estimate is not ready for review")
# return redirect("estimate_detail", pk=estimate.pk)
if not estimate.get_itemtxs_data()[0]:
messages.error(request, "Estimate has no items")
return redirect("estimate_detail", pk=estimate.pk)
send_email(
"manager@tenhal.com",
request.POST.get("to"),
request.POST.get("subject"),
request.POST.get("message"),
)
estimate.mark_as_review()
messages.success(request, "Email sent successfully!")
return redirect("estimate_detail", pk=estimate.pk)
link = reverse_lazy("estimate_preview", kwargs={"pk": estimate.pk})
msg = f"""
السلام عليكم
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 the detailed estimate document attached.
يرجى مراجعة المقترح وإعلامي إذا كانت لديك أي أسئلة أو مخاوف. إذا كانت كل شيء يبدو جيدًا، يمكننا المضي قدمًا في المشروع.
Please review the estimate and let me know if you have any questions or concerns. If everything looks good, we can proceed with the project.
Estimate Link:
{link}
شكراً لاهتمامكم بهذا الأمر.
Thank you for your attention to this matter.
تحياتي,
Best regards,
[Your Name]
[Your Position]
[Your Company]
[Your Contact Information]
"""
return render(
request,
"sales/estimates/estimate_send.html",
{"estimate": estimate, "message": msg},
)
# 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", {})