2600 lines
88 KiB
Python
2600 lines
88 KiB
Python
from decimal import Decimal
|
|
from django.core.paginator import Paginator
|
|
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
|
|
|
|
|
|
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)
|
|
|
|
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):
|
|
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,
|
|
}
|
|
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).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"]
|
|
|
|
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.user.dealer)
|
|
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.user.dealer)
|
|
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.user.dealer)
|
|
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"])
|
|
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)
|
|
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):
|
|
context = super().get_context_data(**kwargs)
|
|
name = f"{context['customer'].first_name} {context['customer'].middle_name} {context['customer'].last_name}"
|
|
context["estimates"] = self.request.entity.get_estimates().filter(
|
|
customer__customer_name=name
|
|
)
|
|
return context
|
|
|
|
|
|
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 = self.request.user.dealer
|
|
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):
|
|
dealer = self.request.user.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, 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")
|
|
queryset = self.request.user.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 = request.user.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
|
|
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"
|
|
|
|
|
|
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
|
|
|
|
|
|
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"
|
|
|
|
|
|
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):
|
|
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"
|
|
paginate_by = 10
|
|
|
|
def get_queryset(self):
|
|
entity = self.request.user.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."
|
|
|
|
|
|
@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
|
|
],
|
|
}
|
|
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
|
|
|
|
form = InvoiceModelCreateForm(entity_slug=entity.slug, user_model=entity.admin)
|
|
if request.method == "POST":
|
|
form = 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.initial["customer"] = estimate.customer
|
|
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
|
|
form = forms.PaymentForm()
|
|
if request.method == "POST":
|
|
form = forms.PaymentForm(request.POST)
|
|
if form.is_valid():
|
|
amount = form.cleaned_data.get("amount")
|
|
invoice = form.cleaned_data.get("invoice")
|
|
if amount > invoice.amount_due:
|
|
messages.error(
|
|
request, "Payment amount is greater than invoice amount due"
|
|
)
|
|
return redirect("payment_create", pk=invoice.pk)
|
|
if amount <= 0:
|
|
messages.error(request, "Payment amount must be greater than 0")
|
|
return redirect("payment_create", pk=invoice.pk)
|
|
if (
|
|
invoice.amount_due == invoice.amount_paid
|
|
or invoice.invoice_status == "paid"
|
|
):
|
|
messages.error(request, "Invoice is already fully paid")
|
|
return redirect("invoice_detail", pk=invoice.pk)
|
|
|
|
ledger = None
|
|
try:
|
|
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",
|
|
)
|
|
cash_account = entity.get_default_coa_accounts().get(name="Cash")
|
|
accounts_receivable = entity.get_default_coa_accounts().get(
|
|
name="Accounts Receivable"
|
|
)
|
|
TransactionModel.objects.create(
|
|
journal_entry=journal,
|
|
account=cash_account, # Debit Cash
|
|
amount=amount, # Payment amount
|
|
tx_type="debit",
|
|
description="Payment Received",
|
|
)
|
|
|
|
TransactionModel.objects.create(
|
|
journal_entry=journal,
|
|
account=accounts_receivable, # Credit Accounts Receivable
|
|
amount=amount, # Payment amount
|
|
tx_type="credit",
|
|
description="Payment Received",
|
|
)
|
|
journal.posted = True
|
|
invoice.make_payment(amount)
|
|
journal.save()
|
|
invoice.save()
|
|
|
|
if invoice.amount_due == invoice.amount_paid:
|
|
invoice.mark_as_paid(
|
|
entity_slug=entity.slug, user_model=entity.admin
|
|
)
|
|
invoice.save()
|
|
ledger.post()
|
|
ledger.save()
|
|
messages.success(request, "Payment created successfully!")
|
|
return redirect("invoice_detail", pk=invoice.pk)
|
|
except Exception as e:
|
|
messages.error(request, f"Error creating payment: {str(e)}")
|
|
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()
|
|
return render(request, "sales/payments/payment_details.html", {"journal": journal})
|
|
|
|
|
|
# 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
|
|
def create_lead(request, pk):
|
|
customer = get_object_or_404(models.Customer, pk=pk)
|
|
if customer.is_lead:
|
|
messages.warning(request, _("Customer is already a lead."))
|
|
else:
|
|
customer.is_lead = True
|
|
customer.save()
|
|
messages.success(request, _("Customer successfully marked as a lead."))
|
|
return redirect(reverse("customer_detail", kwargs={"pk": customer.pk}))
|
|
|
|
|
|
class LeadListView(ListView):
|
|
model = models.Customer
|
|
template_name = "crm/lead_list.html"
|
|
context_object_name = "customers"
|
|
|
|
def get_queryset(self):
|
|
query = self.request.GET.get("q")
|
|
dealer = get_user_type(self.request)
|
|
|
|
customers = models.Customer.objects.filter(dealer=dealer, is_lead=True)
|
|
|
|
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 OpportunityCreateView(CreateView):
|
|
model = models.Opportunity
|
|
form_class = forms.OpportunityForm
|
|
template_name = "crm/opportunity_form.html"
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context["customer"] = models.Customer.objects.get(pk=self.kwargs["customer_id"])
|
|
context["cars"] = models.Car.objects.all()
|
|
return context
|
|
|
|
def form_valid(self, form):
|
|
form.instance.customer = models.Customer.objects.get(
|
|
pk=self.kwargs["customer_id"]
|
|
)
|
|
form.instance.created_by = self.request.user.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/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/opportunity_detail.html"
|
|
context_object_name = "opportunity"
|
|
|
|
|
|
class OpportunityListView(ListView):
|
|
model = models.Opportunity
|
|
template_name = "crm/opportunity_list.html"
|
|
context_object_name = "opportunities"
|
|
|
|
|
|
@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 = "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_at"
|
|
)
|
|
|
|
|
|
@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_at")
|
|
notifications_data = [
|
|
{
|
|
"id": notification.id,
|
|
"message": notification.message,
|
|
"created_at": notification.created_at.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", {})
|