1700 lines
61 KiB
Python
1700 lines
61 KiB
Python
from django.views.decorators.csrf import csrf_exempt
|
|
from django_ledger.models import EntityModel, InvoiceModel,BankAccountModel,AccountModel,JournalEntryModel,TransactionModel,EstimateModel,CustomerModel
|
|
|
|
from django_ledger.forms.bank_account import BankAccountCreateForm,BankAccountUpdateForm
|
|
from django_ledger.forms.account import AccountModelCreateForm,AccountModelUpdateForm
|
|
from django_ledger.forms.estimate import EstimateModelCreateForm
|
|
|
|
import logging
|
|
import json
|
|
import datetime
|
|
from decimal import Decimal
|
|
from django.shortcuts import HttpResponse
|
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
|
from django.contrib.auth.decorators import login_required
|
|
from django.http import JsonResponse
|
|
from django.shortcuts import render, get_object_or_404, redirect
|
|
from django.utils.translation import gettext_lazy as _
|
|
from django.db.models import Q
|
|
from django.views.generic import (
|
|
View,
|
|
ListView,
|
|
DetailView,
|
|
CreateView,
|
|
UpdateView,
|
|
DeleteView,
|
|
TemplateView,
|
|
)
|
|
from django.utils import timezone, translation
|
|
from django.conf import settings
|
|
from urllib.parse import urlparse, urlunparse
|
|
from django.urls import reverse, reverse_lazy
|
|
from django.contrib import messages
|
|
from django.db.models import Sum, F, Count
|
|
from django.db import transaction
|
|
|
|
from .services import (
|
|
elm,
|
|
decodevin,
|
|
get_make,
|
|
get_model,
|
|
normalize_name,
|
|
get_ledger_data,
|
|
)
|
|
from . import models, forms
|
|
from django.contrib.auth.mixins import PermissionRequiredMixin
|
|
from django.contrib.messages.views import SuccessMessageMixin
|
|
from django.contrib.auth.models import Group
|
|
from .utils import get_calculations
|
|
from django.contrib.auth.models import User
|
|
from allauth.account import views
|
|
|
|
logger = logging.getLogger(__name__)
|
|
logging.basicConfig(level=logging.INFO)
|
|
|
|
|
|
def switch_language(request):
|
|
language = request.GET.get("language", "en")
|
|
referer = request.META.get("HTTP_REFERER", "/")
|
|
parsed_url = urlparse(referer)
|
|
path_parts = parsed_url.path.split("/")
|
|
|
|
if path_parts[1] in dict(settings.LANGUAGES):
|
|
path_parts.pop(1)
|
|
|
|
new_path = "/".join(path_parts)
|
|
new_url = urlunparse(
|
|
(
|
|
parsed_url.scheme,
|
|
parsed_url.netloc,
|
|
new_path,
|
|
parsed_url.params,
|
|
parsed_url.query,
|
|
parsed_url.fragment,
|
|
)
|
|
)
|
|
|
|
if language in dict(settings.LANGUAGES):
|
|
logger.debug(f"Switching language to: {language}")
|
|
response = redirect(new_url)
|
|
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, language)
|
|
translation.activate(language)
|
|
request.session[settings.LANGUAGE_COOKIE_NAME] = language
|
|
logger.debug(
|
|
f"Language switched to: {language}, Session: {request.session[settings.LANGUAGE_COOKIE_NAME]}"
|
|
)
|
|
return response
|
|
else:
|
|
logger.warning(f"Invalid language code: {language}")
|
|
return redirect("/")
|
|
|
|
|
|
def dealer_signup(request, *args, **kwargs):
|
|
if request.method == "POST":
|
|
data = json.loads(request.body)
|
|
wf1 = data.get("wizardValidationForm1")
|
|
wf2 = data.get("wizardValidationForm2")
|
|
wf3 = data.get("wizardValidationForm3")
|
|
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(email=email, password=password)
|
|
|
|
models.Dealer.objects.create(user=user,
|
|
name=name,
|
|
arabic_name=arabic_name,
|
|
crn=crn,
|
|
vrn=vrn,
|
|
phone_number=phone,
|
|
address=address,)
|
|
return JsonResponse({"message": "User created successfully."}, status=200)
|
|
except Exception as e:
|
|
return JsonResponse({"error": str(e)}, status=400)
|
|
|
|
form1 = forms.WizardForm1()
|
|
form2 = forms.WizardForm2()
|
|
form3 = forms.WizardForm3()
|
|
return render(request, "account/signup-wizard.html",{"form1": form1, "form2": form2, "form3": form3})
|
|
|
|
|
|
class Login(views.LoginView):
|
|
template_name = "account/login.html"
|
|
redirect_authenticated_user = True
|
|
|
|
|
|
class HomeView(TemplateView):
|
|
template_name = "index.html"
|
|
|
|
|
|
class AccountingDashboard(LoginRequiredMixin, TemplateView):
|
|
template_name = "dashboards/accounting.html"
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
if (
|
|
# not any(hasattr(request.user, attr) for attr in ["dealer", "subdealer"])
|
|
not request.user.is_authenticated
|
|
):
|
|
# messages.error(request, _("You are not associated with any dealer."))
|
|
return redirect("welcome")
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
|
|
total_cars = models.Car.objects.filter(dealer=self.request.user.dealer).count()
|
|
total_reservations = models.CarReservation.objects.filter(
|
|
reserved_until__gte=timezone.now()
|
|
).count()
|
|
stats = models.CarFinance.objects.aggregate(
|
|
total_cost_price=Sum("cost_price"),
|
|
total_selling_price=Sum("selling_price"),
|
|
)
|
|
total_cost_price = stats["total_cost_price"] or 0
|
|
total_selling_price = stats["total_selling_price"] or 0
|
|
total_profit = total_selling_price - total_cost_price
|
|
|
|
context['dealer'] = self.request.user.dealer
|
|
context["total_cars"] = total_cars
|
|
context["total_reservations"] = total_reservations
|
|
context["total_cost_price"] = total_cost_price
|
|
context["total_selling_price"] = total_selling_price
|
|
context["total_profit"] = total_profit
|
|
return context
|
|
|
|
|
|
class WelcomeView(TemplateView):
|
|
template_name = "welcome.html"
|
|
|
|
|
|
class CarCreateView(LoginRequiredMixin, CreateView):
|
|
model = models.Car
|
|
form_class = forms.CarForm
|
|
template_name = "inventory/car_form.html"
|
|
# success_url = reverse_lazy('inventory_stats')
|
|
|
|
def get_success_url(self):
|
|
"""Determine the redirect URL based on user choice."""
|
|
if self.request.POST.get("add_another"):
|
|
return reverse("car_add")
|
|
return reverse("inventory_stats")
|
|
|
|
def form_valid(self, form):
|
|
form.instance.dealer = self.request.user.dealer
|
|
form.save()
|
|
messages.success(self.request, "Car saved successfully.")
|
|
return super().form_valid(form)
|
|
|
|
|
|
class AjaxHandlerView(LoginRequiredMixin, View):
|
|
def get(self, request, *args, **kwargs):
|
|
action = request.GET.get("action")
|
|
handlers = {
|
|
"decode_vin": self.decode_vin,
|
|
"get_models": self.get_models,
|
|
"get_series": self.get_series,
|
|
"get_trims": self.get_trims,
|
|
"get_specifications": self.get_specifications,
|
|
}
|
|
handler = handlers.get(action)
|
|
if handler:
|
|
return handler(request)
|
|
else:
|
|
return JsonResponse({"error": "Invalid action"}, status=400)
|
|
|
|
def decode_vin(self, request):
|
|
vin_no = request.GET.get("vin_no")
|
|
if not vin_no or len(vin_no.strip()) != 17:
|
|
return JsonResponse(
|
|
{"success": False, "error": "Invalid VIN number provided."}, status=400
|
|
)
|
|
|
|
vin_no = vin_no.strip()
|
|
vin_data = {}
|
|
decoding_method = ""
|
|
|
|
# manufacturer_name = model_name = year_model = None
|
|
if not (result := decodevin(vin_no)):
|
|
return JsonResponse(
|
|
{"success": False, "error": "VIN not found in all sources."}, status=404
|
|
)
|
|
|
|
manufacturer_name, model_name, year_model = result.values()
|
|
make = get_make(manufacturer_name)
|
|
model = get_model(model_name, make)
|
|
|
|
logger.info(
|
|
f"VIN decoded using {decoding_method}: Make={manufacturer_name}, Model={model_name}, Year={year_model}"
|
|
)
|
|
car_model = model
|
|
car_make = make
|
|
|
|
if not car_make:
|
|
return JsonResponse(
|
|
{"success": False, "error": "Manufacturer not found in the database."},
|
|
status=404,
|
|
)
|
|
vin_data["make_id"] = car_make.id_car_make
|
|
vin_data["name"] = car_make.name
|
|
vin_data["arabic_name"] = car_make.arabic_name
|
|
|
|
if not car_model:
|
|
vin_data["model_id"] = ""
|
|
else:
|
|
vin_data["model_id"] = car_model.id_car_model
|
|
vin_data["year"] = year_model
|
|
return JsonResponse({"success": True, "data": vin_data})
|
|
|
|
def get_models(self, request):
|
|
make_id = request.GET.get("make_id")
|
|
car_models = (
|
|
models.CarModel.objects.filter(id_car_make=make_id)
|
|
.values("id_car_model", "name", "arabic_name")
|
|
.order_by("name")
|
|
)
|
|
return JsonResponse(list(car_models), safe=False)
|
|
|
|
def get_series(self, request):
|
|
model_id = request.GET.get("model_id")
|
|
year = request.GET.get("year")
|
|
|
|
# Validate inputs
|
|
if not model_id or not year:
|
|
return JsonResponse(
|
|
{"error": "Missing required parameters: model_id or year"}, status=400
|
|
)
|
|
|
|
try:
|
|
year = int(year)
|
|
except ValueError:
|
|
return JsonResponse({"error": "Invalid year format"}, status=400)
|
|
|
|
series = models.CarSerie.objects.filter(id_car_model=model_id).values(
|
|
"id_car_serie", "name", "arabic_name"
|
|
)
|
|
|
|
return JsonResponse(list(series), safe=False)
|
|
|
|
def get_trims(self, request):
|
|
serie_id = request.GET.get("serie_id")
|
|
# model_id = request.GET.get('model_id')
|
|
trims = models.CarTrim.objects.filter(id_car_serie=serie_id).values(
|
|
"id_car_trim", "name", "arabic_name"
|
|
)
|
|
return JsonResponse(list(trims), safe=False)
|
|
|
|
def get_specifications(self, request):
|
|
trim_id = request.GET.get("trim_id")
|
|
car_spec_values = models.CarSpecificationValue.objects.filter(
|
|
id_car_trim=trim_id
|
|
)
|
|
lang = translation.get_language()
|
|
specs_by_parent = {}
|
|
for value in car_spec_values:
|
|
specification = value.id_car_specification
|
|
parent = specification.id_parent
|
|
parent_id = parent.id_car_specification if parent else 0
|
|
if lang == "ar":
|
|
parent_name = parent.arabic_name if parent else "Root"
|
|
else:
|
|
parent_name = parent.name if parent else "Root"
|
|
if parent_id not in specs_by_parent:
|
|
specs_by_parent[parent_id] = {
|
|
"parent_name": parent_name,
|
|
"specifications": [],
|
|
}
|
|
spec_data = {
|
|
"specification_id": specification.id_car_specification,
|
|
"s_name": specification.arabic_name
|
|
if lang == "ar"
|
|
else specification.name,
|
|
"s_value": value.value,
|
|
"s_unit": value.unit if value.unit else "",
|
|
"trim_name": value.id_car_trim.name,
|
|
}
|
|
specs_by_parent[parent_id]["specifications"].append(spec_data)
|
|
serialized_specs = [
|
|
{"parent_name": v["parent_name"], "specifications": v["specifications"]}
|
|
for v in specs_by_parent.values()
|
|
]
|
|
return JsonResponse(serialized_specs, safe=False)
|
|
|
|
|
|
class CarInventory(LoginRequiredMixin, ListView):
|
|
model = models.Car
|
|
home_label = _("inventory")
|
|
template_name = "inventory/car_inventory.html"
|
|
context_object_name = "cars"
|
|
paginate_by = 10
|
|
ordering = ["receiving_date"]
|
|
|
|
def get_queryset(self, *args, **kwargs):
|
|
query = self.request.GET.get('q')
|
|
make_id = self.kwargs['make_id']
|
|
model_id = self.kwargs['model_id']
|
|
trim_id = self.kwargs['trim_id']
|
|
|
|
cars = models.Car.objects.filter(
|
|
dealer=self.request.user.dealer,
|
|
id_car_make=make_id,
|
|
id_car_model=model_id,
|
|
id_car_trim=trim_id,
|
|
).order_by("receiving_date")
|
|
|
|
if query:
|
|
cars = cars.filter(Q(vin__icontains=query))
|
|
return cars
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context["query"] = self.request.GET.get("q", "")
|
|
context["make_id"] = self.kwargs["make_id"]
|
|
context["model_id"] = self.kwargs["model_id"]
|
|
context["trim_id"] = self.kwargs["trim_id"]
|
|
return context
|
|
|
|
|
|
class CarColorCreate(LoginRequiredMixin, CreateView):
|
|
model = models.CarColors
|
|
form_class = forms.CarColorsForm
|
|
template_name = "inventory/add_colors.html"
|
|
|
|
def form_valid(self, form):
|
|
car = get_object_or_404(models.Car, pk=self.kwargs["car_pk"])
|
|
form.instance.car = car
|
|
return super().form_valid(form)
|
|
|
|
def get_success_url(self):
|
|
return reverse_lazy("car_detail", kwargs={"pk": self.kwargs["car_pk"]})
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context["car"] = get_object_or_404(models.Car, pk=self.kwargs["car_pk"])
|
|
return context
|
|
|
|
|
|
@login_required
|
|
def inventory_stats_view(request):
|
|
dealer = request.user.dealer
|
|
|
|
# Annotate total cars by make, model, and trim
|
|
cars = (
|
|
models.Car.objects.filter(dealer=dealer)
|
|
.select_related("id_car_make", "id_car_model", "id_car_trim")
|
|
.annotate(
|
|
make_total=Count("id_car_make"),
|
|
model_total=Count("id_car_model"),
|
|
trim_total=Count("id_car_trim"),
|
|
)
|
|
)
|
|
|
|
inventory = {}
|
|
for car in cars:
|
|
make = car.id_car_make
|
|
if make.id_car_make not in inventory:
|
|
inventory[make.id_car_make] = {
|
|
'make_id': make.id_car_make,
|
|
'make_name': make.get_local_name(),
|
|
'total_cars': 0,
|
|
'models': {}
|
|
}
|
|
inventory[make.id_car_make]["total_cars"] += 1
|
|
|
|
model = car.id_car_model
|
|
if model and model.id_car_model not in inventory[make.id_car_make]['models']:
|
|
inventory[make.id_car_make]['models'][model.id_car_model] = {
|
|
'model_id': model.id_car_model,
|
|
'model_name': model.get_local_name(),
|
|
'total_cars': 0,
|
|
'trims': {}
|
|
}
|
|
inventory[make.id_car_make]["models"][model.id_car_model]["total_cars"] += 1
|
|
|
|
trim = car.id_car_trim
|
|
if (
|
|
trim
|
|
and trim.id_car_trim
|
|
not in inventory[make.id_car_make]["models"][model.id_car_model]["trims"]
|
|
):
|
|
inventory[make.id_car_make]["models"][model.id_car_model]["trims"][
|
|
trim.id_car_trim
|
|
] = {"trim_id": trim.id_car_trim, "trim_name": trim.name, "total_cars": 0}
|
|
inventory[make.id_car_make]["models"][model.id_car_model]["trims"][
|
|
trim.id_car_trim
|
|
]["total_cars"] += 1
|
|
|
|
result = {
|
|
"total_cars": cars.count(),
|
|
"makes": [
|
|
{
|
|
'make_id': make_data['make_id'],
|
|
'make_name': make_data['make_name'],
|
|
'total_cars': make_data['total_cars'],
|
|
'models': [
|
|
{
|
|
"model_id": model_data["model_id"],
|
|
"model_name": model_data["model_name"],
|
|
"total_cars": model_data["total_cars"],
|
|
"trims": list(model_data["trims"].values()),
|
|
}
|
|
for model_data in make_data["models"].values()
|
|
],
|
|
}
|
|
for make_data in inventory.values()
|
|
],
|
|
}
|
|
|
|
return render(request, "inventory/inventory_stats.html", {"inventory": result})
|
|
|
|
|
|
class CarDetailView(LoginRequiredMixin, DetailView):
|
|
model = models.Car
|
|
template_name = "inventory/car_detail.html"
|
|
context_object_name = "car"
|
|
|
|
|
|
class CarFinanceCreateView(LoginRequiredMixin, CreateView):
|
|
model = models.CarFinance
|
|
form_class = forms.CarFinanceForm
|
|
template_name = "inventory/car_finance_form.html"
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
self.car = get_object_or_404(models.Car, pk=self.kwargs["car_pk"])
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
def form_valid(self, form):
|
|
form.instance.car = self.car
|
|
messages.success(self.request, _("Car finance details saved successfully."))
|
|
return super().form_valid(form)
|
|
|
|
def get_success_url(self):
|
|
return reverse("car_detail", kwargs={"pk": self.car.pk})
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context["car"] = self.car
|
|
return context
|
|
|
|
|
|
class CarFinanceUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
|
|
model = models.CarFinance
|
|
form_class = forms.CarFinanceForm
|
|
template_name = "inventory/car_finance_form.html"
|
|
success_message = _("Car finance details updated successfully.")
|
|
|
|
def get_success_url(self):
|
|
return reverse("car_detail", kwargs={"pk": self.object.car.pk})
|
|
|
|
def get_form_kwargs(self):
|
|
kwargs = super().get_form_kwargs()
|
|
kwargs["instance"] = self.get_object()
|
|
return kwargs
|
|
|
|
|
|
class CarUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
|
|
model = models.Car
|
|
form_class = forms.CarUpdateForm
|
|
template_name = "inventory/car_edit.html"
|
|
success_message = _("Car updated successfully.")
|
|
|
|
def get_success_url(self):
|
|
return reverse("car_detail", kwargs={"pk": self.object.pk})
|
|
|
|
|
|
class CarDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteView):
|
|
model = models.Car
|
|
template_name = "inventory/car_confirm_delete.html"
|
|
success_url = reverse_lazy("inventory_stats")
|
|
|
|
def delete(self, request, *args, **kwargs):
|
|
messages.success(request, _("Car deleted successfully."))
|
|
return super().delete(request, *args, **kwargs)
|
|
|
|
|
|
class CarLocationCreateView(CreateView):
|
|
model = models.CarLocation
|
|
form_class = forms.CarLocationForm
|
|
template_name = "inventory/car_location_form.html"
|
|
|
|
def get_success_url(self):
|
|
return reverse_lazy("car_detail", kwargs={"pk": self.object.car.pk})
|
|
|
|
def form_valid(self, form):
|
|
form.instance.car = get_object_or_404(models.Car, pk=self.kwargs["car_pk"])
|
|
form.instance.OWNER = self.request.user.dealer
|
|
form.save()
|
|
messages.success(self.request, "Car saved successfully.")
|
|
return super().form_valid(form)
|
|
|
|
|
|
class CarLocationUpdateView(UpdateView):
|
|
model = models.CarLocation
|
|
form_class = forms.CarLocationForm
|
|
template_name = "inventory/car_location_form.html"
|
|
|
|
def get_success_url(self):
|
|
return reverse_lazy("car_detail", kwargs={"pk": self.object.car.pk})
|
|
|
|
|
|
class CustomCardCreateView(LoginRequiredMixin, CreateView):
|
|
model = models.CustomCard
|
|
form_class = forms.CustomCardForm
|
|
template_name = "inventory/add_custom_card.html"
|
|
|
|
def form_valid(self, form):
|
|
car = get_object_or_404(models.Car, pk=self.kwargs["car_pk"])
|
|
form.instance.car = car
|
|
return super().form_valid(form)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context["car"] = get_object_or_404(models.Car, pk=self.kwargs["car_pk"])
|
|
return context
|
|
|
|
def get_success_url(self):
|
|
messages.success(self.request, _("Custom Card added successfully."))
|
|
return reverse_lazy("car_detail", kwargs={"pk": self.kwargs["car_pk"]})
|
|
|
|
|
|
@login_required()
|
|
def reserve_car_view(request, car_id):
|
|
if request.method == "POST":
|
|
car = get_object_or_404(models.Car, pk=car_id)
|
|
if car.is_reserved():
|
|
messages.error(request, _("This car is already reserved."))
|
|
return redirect("car_detail", pk=car.pk)
|
|
|
|
try:
|
|
reserved_until = timezone.now() + timezone.timedelta(hours=24)
|
|
models.CarReservation.objects.create(
|
|
car=car, reserved_by=request.user, reserved_until=reserved_until
|
|
)
|
|
messages.success(request, _("Car reserved successfully."))
|
|
except Exception as e:
|
|
messages.error(request, f"Error reserving car: {e}")
|
|
|
|
return redirect("car_detail", pk=car.pk)
|
|
return JsonResponse(
|
|
{"success": False, "message": "Invalid request method."}, status=400
|
|
)
|
|
|
|
|
|
@login_required
|
|
def manage_reservation(request, reservation_id):
|
|
reservation = get_object_or_404(
|
|
models.CarReservation, pk=reservation_id, reserved_by=request.user
|
|
)
|
|
|
|
if request.method == "POST":
|
|
action = request.POST.get("action")
|
|
if action == "renew":
|
|
reservation.reserved_until = timezone.now() + timezone.timedelta(hours=24)
|
|
reservation.save()
|
|
messages.success(request, _("Reservation renewed successfully."))
|
|
return redirect("car_detail", pk=reservation.car.pk)
|
|
|
|
elif action == "cancel":
|
|
reservation.delete()
|
|
messages.success(request, _("Reservation canceled successfully."))
|
|
return redirect("car_detail", pk=reservation.car.pk)
|
|
|
|
else:
|
|
return JsonResponse(
|
|
{"success": False, "message": _("Invalid action.")}, status=400
|
|
)
|
|
|
|
return JsonResponse(
|
|
{"success": False, "message": _("Invalid request method.")}, status=400
|
|
)
|
|
|
|
|
|
class DealerDetailView(LoginRequiredMixin, DetailView):
|
|
model = models.Dealer
|
|
template_name = "dealers/dealer_detail.html"
|
|
context_object_name = "dealer"
|
|
|
|
|
|
class DealerUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
|
|
model = models.Dealer
|
|
form_class = forms.DealerForm
|
|
template_name = "dealers/dealer_form.html"
|
|
success_url = reverse_lazy("dealer_detail")
|
|
success_message = _("Dealer updated successfully.")
|
|
|
|
def get_success_url(self):
|
|
return reverse("dealer_detail", kwargs={"pk": self.object.pk})
|
|
|
|
# def get_form(self, form_class=None):
|
|
# form = super().get_form(form_class)
|
|
# if hasattr(form.fields, "dealer_type"):
|
|
# form.fields.pop("dealer_type")
|
|
# return form
|
|
#
|
|
# def get_form_class(self):
|
|
# if self.request.user.dealer.dealer_type == "OWNER":
|
|
# return forms.DealerForm
|
|
# else:
|
|
# return forms.UserForm
|
|
|
|
|
|
class CustomerListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
|
|
model = models.Customer
|
|
home_label = _("customers")
|
|
context_object_name = "customers"
|
|
paginate_by = 10
|
|
template_name = "customers/customer_list.html"
|
|
permission_required = ("inventory.view_customer",)
|
|
|
|
def get_queryset(self):
|
|
query = self.request.GET.get("q")
|
|
customers = models.Customer.objects.filter(
|
|
dealer=self.request.user.dealer
|
|
)
|
|
|
|
if query:
|
|
customers = customers.filter(
|
|
Q(national_id__icontains=query)
|
|
| Q(first_name__icontains=query)
|
|
| Q(last_name__icontains=query)
|
|
)
|
|
return customers
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context["query"] = self.request.GET.get("q", "")
|
|
return context
|
|
|
|
|
|
class CustomerDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
|
|
model = models.Customer
|
|
template_name = "customers/view_customer.html"
|
|
context_object_name = "customer"
|
|
permission_required = ("inventory.view_customer",)
|
|
|
|
|
|
class CustomerCreateView(
|
|
LoginRequiredMixin,
|
|
PermissionRequiredMixin,
|
|
SuccessMessageMixin,
|
|
CreateView,
|
|
):
|
|
model = models.Customer
|
|
form_class = forms.CustomerForm
|
|
template_name = "customers/customer_form.html"
|
|
success_url = reverse_lazy("customer_list")
|
|
permission_required = ("inventory.add_customer",)
|
|
success_message = _("Customer created successfully.")
|
|
|
|
def form_valid(self, form):
|
|
form.instance.dealer = self.request.user.dealer
|
|
return super().form_valid(form)
|
|
|
|
|
|
|
|
class CustomerUpdateView(
|
|
LoginRequiredMixin,
|
|
PermissionRequiredMixin,
|
|
SuccessMessageMixin,
|
|
UpdateView,
|
|
):
|
|
model = models.Customer
|
|
form_class = forms.CustomerForm
|
|
template_name = "customers/customer_form.html"
|
|
success_url = reverse_lazy("customer_list")
|
|
permission_required = ("inventory.change_customer",)
|
|
success_message = _("Customer updated successfully.")
|
|
|
|
|
|
@login_required
|
|
def delete_customer(request, pk):
|
|
customer = get_object_or_404(models.Customer, pk=pk)
|
|
customer.delete()
|
|
messages.success(request, _("Customer deleted successfully."))
|
|
return redirect("customer_list")
|
|
|
|
|
|
class VendorListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
|
|
model = models.Vendor
|
|
context_object_name = "vendors"
|
|
paginate_by = 10
|
|
template_name = "vendors/vendors_list.html"
|
|
permission_required = ("inventory.view_vendor",)
|
|
|
|
|
|
class VendorDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
|
|
model = models.Vendor
|
|
template_name = "vendors/view_vendor.html"
|
|
permission_required = ("inventory.view_vendor",)
|
|
|
|
|
|
class VendorCreateView(
|
|
LoginRequiredMixin,
|
|
PermissionRequiredMixin,
|
|
SuccessMessageMixin,
|
|
CreateView,
|
|
):
|
|
model = models.Vendor
|
|
form_class = forms.VendorForm
|
|
template_name = "vendors/vendor_form.html"
|
|
success_url = reverse_lazy("vendor_list")
|
|
permission_required = ("inventory.add_vendor",)
|
|
success_message = _("Vendor created successfully.")
|
|
|
|
def form_valid(self, form):
|
|
form.instance.dealer = self.request.user.dealer
|
|
return super().form_valid(form)
|
|
|
|
|
|
|
|
|
|
class VendorUpdateView(
|
|
LoginRequiredMixin,
|
|
PermissionRequiredMixin,
|
|
SuccessMessageMixin,
|
|
UpdateView,
|
|
):
|
|
model = models.Vendor
|
|
form_class = forms.VendorForm
|
|
template_name = "vendors/vendor_form.html"
|
|
success_url = reverse_lazy("vendor_list")
|
|
permission_required = ("inventory.change_vendor",)
|
|
success_message = _("Vendor updated successfully.")
|
|
|
|
|
|
@login_required
|
|
def delete_vendor(request, pk):
|
|
vendor = get_object_or_404(models.Vendor, pk=pk)
|
|
vendor.delete()
|
|
messages.success(request, _("Vendor deleted successfully."))
|
|
return redirect("vendor_list")
|
|
|
|
|
|
class QuotationCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
|
|
model = models.SaleQuotation
|
|
form_class = forms.QuotationForm
|
|
template_name = "sales/quotation_form.html"
|
|
permission_required = ("inventory.add_salequotation",)
|
|
|
|
def form_valid(self, form):
|
|
dealer = self.request.user.dealer
|
|
form.instance.dealer = dealer
|
|
quotation = form.save()
|
|
selected_cars = form.cleaned_data.get("cars")
|
|
for car in selected_cars:
|
|
car_finance = car.finances
|
|
if car_finance:
|
|
models.SaleQuotationCar.objects.create(
|
|
quotation=quotation,
|
|
car=car,
|
|
)
|
|
|
|
messages.success(self.request, _("Quotation created successfully."))
|
|
return redirect("quotation_list")
|
|
|
|
|
|
class QuotationListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
|
|
model = models.SaleQuotation
|
|
template_name = "sales/quotation_list.html"
|
|
context_object_name = "quotations"
|
|
paginate_by = 10
|
|
permission_required = ("inventory.view_salequotation",)
|
|
|
|
def get_queryset(self):
|
|
status = self.request.GET.get("status")
|
|
queryset = self.request.user.dealer.sales.all()
|
|
if status:
|
|
queryset = queryset.filter(status=status)
|
|
return queryset
|
|
|
|
|
|
class QuotationDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
|
|
model = models.SaleQuotation
|
|
template_name = "sales/quotation_detail.html"
|
|
context_object_name = "quotation"
|
|
permission_required = ("inventory.view_salequotation",)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
quotation = self.object
|
|
|
|
context_result = get_calculations(quotation)
|
|
context.update(context_result)
|
|
|
|
return context
|
|
|
|
|
|
@login_required
|
|
def generate_invoice(request, pk):
|
|
quotation = get_object_or_404(models.SaleQuotation, pk=pk)
|
|
dealer = request.user.dealer
|
|
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, PermissionRequiredMixin, DetailView):
|
|
model = models.SalesOrder
|
|
template_name = "sales/sales_order_detail.html"
|
|
context_object_name = "sales_order"
|
|
permission_required = ("inventory.view_salequotation",)
|
|
slug_field = "order_id"
|
|
slug_url_kwarg = "order_id"
|
|
|
|
|
|
# Users
|
|
# class UserListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
|
|
# model = models.Staff
|
|
# context_object_name = "users"
|
|
# paginate_by = 10
|
|
# template_name = "users/user_list.html"
|
|
# permission_required = ("inventory.view_dealer",)
|
|
#
|
|
# def get_queryset(self):
|
|
# query = self.request.GET.get("q")
|
|
# users = self.request.user.dealer.staff.all()
|
|
#
|
|
# if query:
|
|
# users = users.filter(
|
|
# Q(name__icontains=query)
|
|
# | Q(arabic_name__icontains=query)
|
|
# | Q(phone_number__icontains=query)
|
|
# | Q(address__icontains=query)
|
|
# )
|
|
# return users.all()
|
|
#
|
|
#
|
|
# class UserDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
|
|
# model = models.Staff
|
|
# template_name = "users/user_detail.html"
|
|
# context_object_name = "user_"
|
|
# permission_required = ("inventory.view_dealer",)
|
|
|
|
|
|
# class UserCreateView(
|
|
# LoginRequiredMixin,
|
|
# PermissionRequiredMixin,
|
|
# SuccessMessageMixin,
|
|
# CreateView,
|
|
# ):
|
|
# model = models.Staff
|
|
# form_class = forms.UserForm
|
|
# template_name = "users/user_form.html"
|
|
# success_url = reverse_lazy("user_list")
|
|
# permission_required = ("inventory.add_dealer",)
|
|
# success_message = _("User created successfully.")
|
|
#
|
|
# def get_form(self, form_class=None):
|
|
# form = super().get_form(form_class)
|
|
# form.fields["staff_type"].choices = [
|
|
# t for t in form.fields["staff_type"].choices if t[0] != "OWNER"
|
|
# ]
|
|
# return form
|
|
#
|
|
# def form_valid(self, form):
|
|
# dealer = self.request.user.dealer
|
|
# if dealer.sub_dealers.count() >= dealer.get_active_plan.max_users:
|
|
# messages.error(
|
|
# self.request, _("You have reached the maximum number of users.")
|
|
# )
|
|
# return redirect("user_list")
|
|
#
|
|
# user = User.objects.create_user(username=form.cleaned_data["name"])
|
|
# user.set_password("Tenhal@123")
|
|
# user.save()
|
|
# form.instance.user = user
|
|
# form.instance.parent_dealer = dealer
|
|
# for group in user.groups.all():
|
|
# group.user_set.remove(user)
|
|
# Group.objects.get(name=form.cleaned_data["dealer_type"].lower()).user_set.add(
|
|
# user
|
|
# )
|
|
# form.save()
|
|
# return super().form_valid(form)
|
|
|
|
|
|
# class UserUpdateView(
|
|
# LoginRequiredMixin,
|
|
# PermissionRequiredMixin,
|
|
# SuccessMessageMixin,
|
|
# UpdateView,
|
|
# ):
|
|
# model = models.Dealer
|
|
# form_class = forms.UserForm
|
|
# template_name = "users/user_form.html"
|
|
# success_url = reverse_lazy("user_list")
|
|
# permission_required = ("inventory.change_dealer",)
|
|
# success_message = _("User updated successfully.")
|
|
#
|
|
# def get_form(self, form_class=None):
|
|
# form = super().get_form(form_class)
|
|
# if not self.request.user.has_perms(["inventory.change_dealer_type"]):
|
|
# field = form.fields["dealer_type"]
|
|
# field.widget = field.hidden_widget()
|
|
# form.fields["dealer_type"].choices = [
|
|
# t for t in form.fields["dealer_type"].choices if t[0] != "Owner"
|
|
# ]
|
|
# return form
|
|
#
|
|
# def form_valid(self, form):
|
|
# user = form.instance.user
|
|
# for group in user.groups.all():
|
|
# group.user_set.remove(user)
|
|
# Group.objects.get(name=form.cleaned_data["dealer_type"].lower()).user_set.add(
|
|
# user
|
|
# )
|
|
# form.save()
|
|
# return super().form_valid(form)
|
|
|
|
|
|
# def UserDeleteview(request, pk):
|
|
# user = get_object_or_404(models.Dealer, pk=pk)
|
|
# user.delete()
|
|
# messages.success(request, _("User deleted successfully."))
|
|
# return redirect("user_list")
|
|
|
|
|
|
# errors
|
|
def custom_page_not_found_view(request, exception):
|
|
return render(request, "errors/404.html", {})
|
|
|
|
|
|
def custom_error_view(request, exception=None):
|
|
return render(request, "errors/500.html", {})
|
|
|
|
|
|
def custom_permission_denied_view(request, exception=None):
|
|
return render(request, "errors/403.html", {})
|
|
|
|
|
|
def custom_bad_request_view(request, exception=None):
|
|
return render(request, "errors/400.html", {})
|
|
|
|
|
|
class OrganizationListView(LoginRequiredMixin, ListView):
|
|
model = models.Organization
|
|
template_name = "organizations/organization_list.html"
|
|
context_object_name = "organizations"
|
|
|
|
|
|
class OrganizationDetailView(DetailView):
|
|
model = models.Organization
|
|
template_name = "organizations/organization_detail.html"
|
|
context_object_name = "organization"
|
|
|
|
|
|
class OrganizationCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
|
|
model = models.Organization
|
|
form_class = forms.OrganizationForm
|
|
template_name = "organizations/organization_form.html"
|
|
success_url = reverse_lazy("organization_list")
|
|
success_message = "Organization created successfully."
|
|
|
|
def form_valid(self, form):
|
|
if form.is_valid():
|
|
form.instance.dealer = self.request.user.dealer
|
|
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 = request.user.dealer
|
|
if request.method == "POST":
|
|
form = forms.PaymentForm(request.POST)
|
|
if form.is_valid():
|
|
form.instance.quotation = quotation
|
|
insatnce = form.save()
|
|
|
|
dealer = request.user.dealer
|
|
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"
|
|
|
|
def get_queryset(self):
|
|
entity = self.request.user.dealer.entity
|
|
qs = entity.get_all_accounts()
|
|
return qs
|
|
|
|
|
|
class AccountCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
|
|
model = AccountModel
|
|
form_class = AccountModelCreateForm
|
|
template_name = "ledger/coa_accounts/account_form.html"
|
|
success_url = reverse_lazy("account_list")
|
|
success_message = "Account created successfully."
|
|
|
|
def form_valid(self, form):
|
|
form.instance.entity_model = self.request.user.dealer.entity
|
|
form.instance.depth = 0
|
|
return super().form_valid(form)
|
|
|
|
|
|
def get_form_kwargs(self):
|
|
"""
|
|
Override this method to pass additional keyword arguments to the form.
|
|
"""
|
|
entity = self.request.user.dealer.entity
|
|
kwargs = super().get_form_kwargs()
|
|
|
|
kwargs['coa_model'] = entity.get_default_coa()
|
|
return kwargs
|
|
|
|
class AccountDetailView(LoginRequiredMixin, DetailView):
|
|
model = AccountModel
|
|
template_name = "ledger/coa_accounts/account_detail.html"
|
|
context_object_name = "account"
|
|
|
|
class AccountUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
|
|
model = AccountModel
|
|
form_class = AccountModelUpdateForm
|
|
template_name = "ledger/coa_accounts/account_form.html"
|
|
success_url = reverse_lazy("account_list")
|
|
success_message = "Account updated successfully."
|
|
|
|
@login_required
|
|
def account_delete(request, pk):
|
|
account = get_object_or_404(AccountModel, pk=pk)
|
|
if request.method == "POST":
|
|
account.delete()
|
|
messages.success(request, "Account deleted successfully.")
|
|
return redirect("account_list")
|
|
return render(request, "ledger/coa_accounts/account_delete.html", {"account": account})
|
|
|
|
#Estimates
|
|
|
|
class EstimateListView(LoginRequiredMixin, ListView):
|
|
model = EstimateModel
|
|
template_name = "sales/estimates/estimate_list.html"
|
|
context_object_name = "estimates"
|
|
|
|
class EstimateCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
|
|
model = EstimateModel
|
|
form_class = EstimateModelCreateForm
|
|
template_name = "sales/estimates/estimate_form.html"
|
|
success_url = reverse_lazy("estimate_list")
|
|
success_message = "Estimate created successfully."
|
|
|
|
def get_form_kwargs(self):
|
|
"""
|
|
Override this method to pass additional keyword arguments to the form.
|
|
"""
|
|
entity = self.request.user.dealer.entity
|
|
kwargs = super().get_form_kwargs()
|
|
kwargs['entity_slug'] = entity.slug
|
|
kwargs['user_model'] = entity.admin
|
|
return kwargs
|
|
|
|
def get_context_data(self, **kwargs):
|
|
entity = self.request.user.dealer.entity
|
|
kwargs['items'] = entity.get_items_all()
|
|
return super().get_context_data(**kwargs)
|
|
def get_customer_queryset(self):
|
|
entity = self.request.user.dealer.entity
|
|
return entity.get_customer_queryset()
|
|
|
|
def form_valid(self, form):
|
|
form.instance.entity = self.request.user.dealer.entity
|
|
return super().form_valid(form)
|
|
|
|
@csrf_exempt
|
|
def create_estimate(request):
|
|
entity = request.user.dealer.entity
|
|
|
|
if request.method == 'POST':
|
|
try:
|
|
data = json.loads(request.body)
|
|
title = data['title']
|
|
customer_id = data['customer']
|
|
terms = data['terms']
|
|
customer = entity.get_customers().filter(pk=customer_id).first()
|
|
|
|
estimate = entity.create_estimate(
|
|
estimate_title=title,
|
|
customer_model=customer,
|
|
contract_terms=terms)
|
|
|
|
items = data.get('item[]', [])
|
|
items = [entity.get_items_all().filter(pk=item).first() for item in items]
|
|
quantities = data.get('quantity[]', [])
|
|
unit_costs = data.get('unitCost[]', [])
|
|
unit_sales_prices = data.get('unitSalesPrice[]', [])
|
|
total_costs = data.get('totalCost[]', [])
|
|
total_revenues = data.get('totalRevenue[]', [])
|
|
|
|
estimate_itemtxs = {
|
|
item.item_number: {
|
|
'unit_cost': float(unit_costs[i]),
|
|
'unit_revenue': float(unit_sales_prices[i]),
|
|
'quantity': float(quantities[i]),
|
|
'total_amount': float(total_costs[i])
|
|
}
|
|
for i, item in enumerate(items)
|
|
}
|
|
estimate.migrate_itemtxs(
|
|
itemtxs=estimate_itemtxs,
|
|
commit=True,
|
|
operation=EstimateModel.ITEMIZE_APPEND
|
|
)
|
|
url = reverse_lazy('estimate_detail', kwargs={'pk': estimate.pk})
|
|
return JsonResponse({'status': 'success', 'message': 'Estimate created successfully!','url':url})
|
|
except Exception as e:
|
|
return JsonResponse({'status': 'error', 'message': f'An error occurred while processing the request.{e}'}, status=400)
|
|
|
|
form = EstimateModelCreateForm(entity_slug=entity.slug,user_model=entity.admin)
|
|
context = {'form': form,"items":entity.get_items_all()}
|
|
return render(request, 'sales/estimates/estimate_form.html', context)
|
|
|
|
class EstimateDetailView(LoginRequiredMixin, DetailView):
|
|
model = EstimateModel
|
|
template_name = "sales/estimates/estimate_detail.html"
|
|
context_object_name = "estimate"
|
|
|
|
def get_context_data(self, **kwargs):
|
|
kwargs["total_unit_price"] = self.object.get_itemtxs_data()[0].first().ce_unit_cost_estimate * self.object.get_itemtxs_data()[0].first().ce_quantity
|
|
return super().get_context_data(**kwargs) |