haikal/inventory/views.py

1448 lines
52 KiB
Python

from django_ledger.models import EntityModel, InvoiceModel
import logging
import json
import datetime
from decimal import Decimal
from django_ledger.models import TransactionModel, AccountModel,JournalEntryModel
from .pdf_generator import generate_quotation_pdf
from django.shortcuts import HttpResponse
from django.template.loader import render_to_string
# from weasyprint import HTML
# from weasyprint.fonts import FontConfiguration
from django.views.decorators.csrf import csrf_exempt
from vin import VIN
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.decorators import login_required
from django.http import JsonResponse
from django.shortcuts import render, get_object_or_404, redirect
from django.utils.translation import gettext_lazy as _
from django.db.models import Q
from django.views.generic import (
View,
ListView,
DetailView,
CreateView,
UpdateView,
DeleteView,
TemplateView,
)
from django.utils import timezone, translation
from django.conf import settings
from urllib.parse import urlparse, urlunparse
from django.forms import ChoiceField, ModelForm, RadioSelect
from django.urls import reverse, reverse_lazy
from django.contrib import messages
from django.db.models import Sum, F, Count
from inventory.mixins import AddDealerInstanceMixin
from .services import elm, decodevin, get_make, get_model, normalize_name
from .services import (
elm,
decodevin,
get_make,
get_model,
normalize_name,
get_ledger_data,
)
from . import models, forms
from django_tables2.export.views import ExportMixin
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.contrib.auth.decorators import user_passes_test
from django.contrib.messages.views import SuccessMessageMixin
from django.contrib.auth.models import Group
from django.contrib.auth import get_user_model
from .utils import get_calculations
User = get_user_model()
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO)
def switch_language(request):
language = request.GET.get("language", "en")
referer = request.META.get("HTTP_REFERER", "/")
parsed_url = urlparse(referer)
path_parts = parsed_url.path.split("/")
if path_parts[1] in dict(settings.LANGUAGES):
path_parts.pop(1)
new_path = "/".join(path_parts)
new_url = urlunparse(
(
parsed_url.scheme,
parsed_url.netloc,
new_path,
parsed_url.params,
parsed_url.query,
parsed_url.fragment,
)
)
if language in dict(settings.LANGUAGES):
logger.debug(f"Switching language to: {language}")
response = redirect(new_url)
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, language)
translation.activate(language)
request.session[settings.LANGUAGE_COOKIE_NAME] = language
logger.debug(
f"Language switched to: {language}, Session: {request.session[settings.LANGUAGE_COOKIE_NAME]}"
)
return response
else:
logger.warning(f"Invalid language code: {language}")
return redirect("/")
class HomeView(LoginRequiredMixin, TemplateView):
template_name = "crm.html"
# def dispatch(self, request, *args, **kwargs):
# if (
# not any(hasattr(request.user, attr) for attr in ["dealer", "subdealer"])
# or not request.user.is_authenticated
# ):
# messages.error(request, _("You are not associated with any dealer."))
# return redirect("welcome")
# return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
total_cars = models.Car.objects.filter(dealer=self.request.user.dealer).count()
total_reservations = models.CarReservation.objects.filter(
reserved_until__gte=timezone.now()
).count()
stats = models.CarFinance.objects.aggregate(
total_cost_price=Sum("cost_price"),
total_selling_price=Sum("selling_price"),
)
total_cost_price = stats["total_cost_price"] or 0
total_selling_price = stats["total_selling_price"] or 0
total_profit = total_selling_price - total_cost_price
context["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 = "default.html"
class CarCreateView(LoginRequiredMixin, CreateView):
model = models.Car
form_class = forms.CarForm
template_name = "inventory/car_form.html"
# success_url = reverse_lazy('inventory_stats')
def get_success_url(self):
"""Determine the redirect URL based on user choice."""
if self.request.POST.get("add_another"):
return reverse("car_add")
return reverse("inventory_stats")
def form_valid(self, form):
form.instance.dealer = self.request.user.dealer.get_root_dealer
form.save()
messages.success(self.request, "Car saved successfully.")
return super().form_valid(form)
class AjaxHandlerView(LoginRequiredMixin, View):
def get(self, request, *args, **kwargs):
action = request.GET.get("action")
handlers = {
"decode_vin": self.decode_vin,
"get_models": self.get_models,
"get_series": self.get_series,
"get_trims": self.get_trims,
"get_specifications": self.get_specifications,
}
handler = handlers.get(action)
if handler:
return handler(request)
else:
return JsonResponse({"error": "Invalid action"}, status=400)
def decode_vin(self, request):
vin_no = request.GET.get("vin_no")
if not vin_no or len(vin_no.strip()) != 17:
return JsonResponse(
{"success": False, "error": "Invalid VIN number provided."}, status=400
)
vin_no = vin_no.strip()
vin_data = {}
decoding_method = ""
# manufacturer_name = model_name = year_model = None
if not (result := decodevin(vin_no)):
return JsonResponse(
{"success": False, "error": "VIN not found in all sources."}, status=404
)
manufacturer_name, model_name, year_model = result.values()
make = get_make(manufacturer_name)
model = get_model(model_name, make)
logger.info(
f"VIN decoded using {decoding_method}: Make={manufacturer_name}, Model={model_name}, Year={year_model}"
)
car_model = model
car_make = make
if not car_make:
return JsonResponse(
{"success": False, "error": "Manufacturer not found in the database."},
status=404,
)
vin_data["make_id"] = car_make.id_car_make
vin_data["name"] = car_make.name
vin_data["arabic_name"] = car_make.arabic_name
if not car_model:
vin_data["model_id"] = ""
else:
vin_data["model_id"] = car_model.id_car_model
vin_data["year"] = year_model
return JsonResponse({"success": True, "data": vin_data})
def get_models(self, request):
make_id = request.GET.get("make_id")
car_models = (
models.CarModel.objects.filter(id_car_make=make_id)
.values("id_car_model", "name", "arabic_name")
.order_by("name")
)
return JsonResponse(list(car_models), safe=False)
def get_series(self, request):
model_id = request.GET.get("model_id")
year = request.GET.get("year")
# Validate inputs
if not model_id or not year:
return JsonResponse(
{"error": "Missing required parameters: model_id or year"}, status=400
)
try:
year = int(year)
except ValueError:
return JsonResponse({"error": "Invalid year format"}, status=400)
series = models.CarSerie.objects.filter(id_car_model=model_id).values(
"id_car_serie", "name", "arabic_name"
)
return JsonResponse(list(series), safe=False)
def get_trims(self, request):
serie_id = request.GET.get("serie_id")
# model_id = request.GET.get('model_id')
trims = models.CarTrim.objects.filter(id_car_serie=serie_id).values(
"id_car_trim", "name", "arabic_name"
)
return JsonResponse(list(trims), safe=False)
def get_specifications(self, request):
trim_id = request.GET.get("trim_id")
car_spec_values = models.CarSpecificationValue.objects.filter(
id_car_trim=trim_id
)
lang = translation.get_language()
specs_by_parent = {}
for value in car_spec_values:
specification = value.id_car_specification
parent = specification.id_parent
parent_id = parent.id_car_specification if parent else 0
if lang == "ar":
parent_name = parent.arabic_name if parent else "Root"
else:
parent_name = parent.name if parent else "Root"
if parent_id not in specs_by_parent:
specs_by_parent[parent_id] = {
"parent_name": parent_name,
"specifications": [],
}
spec_data = {
"specification_id": specification.id_car_specification,
"s_name": specification.arabic_name
if lang == "ar"
else specification.name,
"s_value": value.value,
"s_unit": value.unit if value.unit else "",
"trim_name": value.id_car_trim.name,
}
specs_by_parent[parent_id]["specifications"].append(spec_data)
serialized_specs = [
{"parent_name": v["parent_name"], "specifications": v["specifications"]}
for v in specs_by_parent.values()
]
return JsonResponse(serialized_specs, safe=False)
class CarInventory(LoginRequiredMixin, ListView):
model = models.Car
home_label = _("inventory")
template_name = "inventory/car_inventory.html"
context_object_name = "cars"
paginate_by = 10
ordering = ["receiving_date"]
def get_queryset(self, *args, **kwargs):
query = self.request.GET.get('q')
make_id = self.kwargs['make_id']
model_id = self.kwargs['model_id']
trim_id = self.kwargs['trim_id']
cars = models.Car.objects.filter(
dealer=self.request.user.dealer.get_root_dealer,
id_car_make=make_id,
id_car_model=model_id,
id_car_trim=trim_id,
).order_by("receiving_date")
if query:
cars = cars.filter(Q(vin__icontains=query))
return cars
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["query"] = self.request.GET.get("q", "")
context["make_id"] = self.kwargs["make_id"]
context["model_id"] = self.kwargs["model_id"]
context["trim_id"] = self.kwargs["trim_id"]
return context
class CarColorCreate(LoginRequiredMixin, CreateView):
model = models.CarColors
form_class = forms.CarColorsForm
template_name = "inventory/add_colors.html"
def form_valid(self, form):
car = get_object_or_404(models.Car, pk=self.kwargs["car_pk"])
form.instance.car = car
return super().form_valid(form)
def get_success_url(self):
return reverse_lazy("car_detail", kwargs={"pk": self.kwargs["car_pk"]})
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["car"] = get_object_or_404(models.Car, pk=self.kwargs["car_pk"])
return context
@login_required
def inventory_stats_view(request):
dealer = request.user.dealer
# Annotate total cars by make, model, and trim
cars = (
models.Car.objects.filter(dealer=dealer.get_root_dealer)
.select_related("id_car_make", "id_car_model", "id_car_trim")
.annotate(
make_total=Count("id_car_make"),
model_total=Count("id_car_model"),
trim_total=Count("id_car_trim"),
)
)
inventory = {}
for car in cars:
make = car.id_car_make
if make.id_car_make not in inventory:
inventory[make.id_car_make] = {
'make_id': make.id_car_make,
'make_name': make.get_local_name(),
'total_cars': 0,
'models': {}
}
inventory[make.id_car_make]["total_cars"] += 1
model = car.id_car_model
if model and model.id_car_model not in inventory[make.id_car_make]['models']:
inventory[make.id_car_make]['models'][model.id_car_model] = {
'model_id': model.id_car_model,
'model_name': model.get_local_name(),
'total_cars': 0,
'trims': {}
}
inventory[make.id_car_make]["models"][model.id_car_model]["total_cars"] += 1
trim = car.id_car_trim
if (
trim
and trim.id_car_trim
not in inventory[make.id_car_make]["models"][model.id_car_model]["trims"]
):
inventory[make.id_car_make]["models"][model.id_car_model]["trims"][
trim.id_car_trim
] = {"trim_id": trim.id_car_trim, "trim_name": trim.name, "total_cars": 0}
inventory[make.id_car_make]["models"][model.id_car_model]["trims"][
trim.id_car_trim
]["total_cars"] += 1
result = {
"total_cars": cars.count(),
"makes": [
{
'make_id': make_data['make_id'],
'make_name': make_data['make_name'],
'total_cars': make_data['total_cars'],
'models': [
{
"model_id": model_data["model_id"],
"model_name": model_data["model_name"],
"total_cars": model_data["total_cars"],
"trims": list(model_data["trims"].values()),
}
for model_data in make_data["models"].values()
],
}
for make_data in inventory.values()
],
}
return render(request, "inventory/inventory_stats.html", {"inventory": result})
class CarDetailView(LoginRequiredMixin, DetailView):
model = models.Car
template_name = "inventory/car_detail.html"
context_object_name = "car"
class CarFinanceCreateView(LoginRequiredMixin, CreateView):
model = models.CarFinance
form_class = forms.CarFinanceForm
template_name = "inventory/car_finance_form.html"
def dispatch(self, request, *args, **kwargs):
self.car = get_object_or_404(models.Car, pk=self.kwargs["car_pk"])
return super().dispatch(request, *args, **kwargs)
def form_valid(self, form):
form.instance.car = self.car
messages.success(self.request, _("Car finance details saved successfully."))
return super().form_valid(form)
def get_success_url(self):
return reverse("car_detail", kwargs={"pk": self.car.pk})
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["car"] = self.car
return context
class CarFinanceUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
model = models.CarFinance
form_class = forms.CarFinanceForm
template_name = "inventory/car_finance_form.html"
success_message = _("Car finance details updated successfully.")
def get_success_url(self):
return reverse("car_detail", kwargs={"pk": self.object.car.pk})
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs["instance"] = self.get_object()
return kwargs
class CarUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
model = models.Car
form_class = forms.CarUpdateForm
template_name = "inventory/car_edit.html"
success_message = _("Car updated successfully.")
def get_success_url(self):
return reverse("car_detail", kwargs={"pk": self.object.pk})
class CarDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteView):
model = models.Car
template_name = "inventory/car_confirm_delete.html"
success_url = reverse_lazy("inventory_stats")
def delete(self, request, *args, **kwargs):
messages.success(request, _("Car deleted successfully."))
return super().delete(request, *args, **kwargs)
class CarLocationCreateView(CreateView):
model = models.CarLocation
form_class = forms.CarLocationForm
template_name = "inventory/car_location_form.html"
def get_success_url(self):
return reverse_lazy("car_detail", kwargs={"pk": self.object.car.pk})
def form_valid(self, form):
form.instance.car = get_object_or_404(models.Car, pk=self.kwargs["car_pk"])
form.instance.owner = self.request.user.dealer
form.save()
messages.success(self.request, "Car saved successfully.")
return super().form_valid(form)
class CarLocationUpdateView(UpdateView):
model = models.CarLocation
form_class = forms.CarLocationForm
template_name = "inventory/car_location_form.html"
def get_success_url(self):
return reverse_lazy("car_detail", kwargs={"pk": self.object.car.pk})
class CustomCardCreateView(LoginRequiredMixin, CreateView):
model = models.CustomCard
form_class = forms.CustomCardForm
template_name = "inventory/add_custom_card.html"
def form_valid(self, form):
car = get_object_or_404(models.Car, pk=self.kwargs["car_pk"])
form.instance.car = car
return super().form_valid(form)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["car"] = get_object_or_404(models.Car, pk=self.kwargs["car_pk"])
return context
def get_success_url(self):
messages.success(self.request, _("Custom Card added successfully."))
return reverse_lazy("car_detail", kwargs={"pk": self.kwargs["car_pk"]})
@login_required()
def reserve_car_view(request, car_id):
if request.method == "POST":
car = get_object_or_404(models.Car, pk=car_id)
if car.is_reserved():
messages.error(request, _("This car is already reserved."))
return redirect("car_detail", pk=car.pk)
try:
reserved_until = timezone.now() + timezone.timedelta(hours=24)
models.CarReservation.objects.create(
car=car, reserved_by=request.user, reserved_until=reserved_until
)
messages.success(request, _("Car reserved successfully."))
except Exception as e:
messages.error(request, f"Error reserving car: {e}")
return redirect("car_detail", pk=car.pk)
return JsonResponse(
{"success": False, "message": "Invalid request method."}, status=400
)
@login_required
def manage_reservation(request, reservation_id):
reservation = get_object_or_404(
models.CarReservation, pk=reservation_id, reserved_by=request.user
)
if request.method == "POST":
action = request.POST.get("action")
if action == "renew":
reservation.reserved_until = timezone.now() + timezone.timedelta(hours=24)
reservation.save()
messages.success(request, _("Reservation renewed successfully."))
return redirect("car_detail", pk=reservation.car.pk)
elif action == "cancel":
reservation.delete()
messages.success(request, _("Reservation canceled successfully."))
return redirect("car_detail", pk=reservation.car.pk)
else:
return JsonResponse(
{"success": False, "message": _("Invalid action.")}, status=400
)
return JsonResponse(
{"success": False, "message": _("Invalid request method.")}, status=400
)
class DealerListView(LoginRequiredMixin, ListView):
model = models.Dealer
template_name = "dealer_list.html"
context_object_name = "dealers"
class DealerDetailView(LoginRequiredMixin, DetailView):
model = models.Dealer
template_name = "dealers/dealer_detail.html"
context_object_name = "dealer"
class DealerCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
model = models.Dealer
form_class = forms.DealerForm
template_name = "dealer_form.html"
success_url = reverse_lazy("dealer_list")
success_message = _("Dealer created successfully.")
class DealerUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
model = models.Dealer
form_class = forms.DealerForm
template_name = "dealers/dealer_form.html"
success_url = reverse_lazy("dealer_detail")
success_message = _("Dealer updated successfully.")
def get_success_url(self):
return reverse("dealer_detail", kwargs={"pk": self.object.pk})
def get_form(self, form_class=None):
form = super().get_form(form_class)
if hasattr(form.fields, "dealer_type"):
form.fields.pop("dealer_type")
return form
def get_form_class(self):
if self.request.user.dealer.dealer_type == "Owner":
return forms.DealerForm
else:
return forms.UserForm
class DealerDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteView):
model = models.Dealer
template_name = "dealer_confirm_delete.html"
success_url = reverse_lazy("dealer_list")
success_message = _("Dealer deleted successfully.")
class CustomerListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
model = models.Customer
home_label = _("customers")
context_object_name = "customers"
paginate_by = 10
template_name = "customers/customer_list.html"
permission_required = ("inventory.view_customer",)
def get_queryset(self):
query = self.request.GET.get("q")
customers = models.Customer.objects.filter(
dealer=self.request.user.dealer.get_root_dealer
)
if query:
customers = customers.filter(
Q(national_id__icontains=query)
| Q(first_name__icontains=query)
| Q(last_name__icontains=query)
)
return customers
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["query"] = self.request.GET.get("q", "")
return context
class CustomerDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
model = models.Customer
template_name = "customers/view_customer.html"
context_object_name = "customer"
permission_required = ("inventory.view_customer",)
class CustomerCreateView(
LoginRequiredMixin,
PermissionRequiredMixin,
SuccessMessageMixin,
AddDealerInstanceMixin,
CreateView,
):
model = models.Customer
form_class = forms.CustomerForm
template_name = "customers/customer_form.html"
success_url = reverse_lazy("customer_list")
permission_required = ("inventory.add_customer",)
success_message = _("Customer created successfully.")
class CustomerUpdateView(
LoginRequiredMixin,
PermissionRequiredMixin,
SuccessMessageMixin,
AddDealerInstanceMixin,
UpdateView,
):
model = models.Customer
form_class = forms.CustomerForm
template_name = "customers/customer_form.html"
success_url = reverse_lazy("customer_list")
permission_required = ("inventory.change_customer",)
success_message = _("Customer updated successfully.")
@login_required
def delete_customer(request, pk):
customer = get_object_or_404(models.Customer, pk=pk)
customer.delete()
messages.success(request, _("Customer deleted successfully."))
return redirect("customer_list")
class VendorListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
model = models.Vendor
context_object_name = "vendors"
paginate_by = 10
template_name = "vendors/vendors_list.html"
permission_required = ("inventory.view_vendor",)
class VendorDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
model = models.Vendor
template_name = "vendors/view_vendor.html"
permission_required = ("inventory.view_vendor",)
class VendorCreateView(
LoginRequiredMixin,
PermissionRequiredMixin,
SuccessMessageMixin,
AddDealerInstanceMixin,
CreateView,
):
model = models.Vendor
form_class = forms.VendorForm
template_name = "vendors/vendor_form.html"
success_url = reverse_lazy("vendor_list")
permission_required = ("inventory.add_vendor",)
success_message = _("Vendor created successfully.")
class VendorUpdateView(
LoginRequiredMixin,
PermissionRequiredMixin,
SuccessMessageMixin,
AddDealerInstanceMixin,
UpdateView,
):
model = models.Vendor
form_class = forms.VendorForm
template_name = "vendors/vendor_form.html"
success_url = reverse_lazy("vendor_list")
permission_required = ("inventory.change_vendor",)
success_message = _("Vendor updated successfully.")
@login_required
def delete_vendor(request, pk):
vendor = get_object_or_404(models.Vendor, pk=pk)
vendor.delete()
messages.success(request, _("Vendor deleted successfully."))
return redirect("vendor_list")
class QuotationCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
model = models.SaleQuotation
form_class = forms.QuotationForm
template_name = "sales/quotation_form.html"
permission_required = ("inventory.add_salequotation",)
def form_valid(self, form):
dealer = self.request.user.dealer.get_root_dealer
entity = EntityModel.objects.get(name=dealer.get_root_dealer.name)
form.instance.dealer = dealer
form.instance.entity = entity
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 = models.SaleQuotation.objects.all()
print(self.request.user.dealer.get_root_dealer.sales.all())
queryset = self.request.user.dealer.get_root_dealer.sales.all()
if status:
queryset = queryset.filter(status=status)
return queryset
class QuotationDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
model = models.SaleQuotation
template_name = "sales/quotation_detail.html"
context_object_name = "quotation"
permission_required = ("inventory.view_salequotation",)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
quotation = self.object
context_result = get_calculations(quotation)
context.update(context_result)
return context
@login_required
def generate_invoice(request, pk):
quotation = get_object_or_404(models.SaleQuotation, pk=pk)
if not quotation.is_approved:
messages.error(
request, "Quotation must be approved before converting to an invoice."
)
else:
entity = quotation.entity
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)
if qoutation.posted:
messages.error(request, "Quotation is already posted")
return redirect("quotation_detail", pk=pk)
entity = qoutation.entity
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")
entity = qoutation.entity
date = datetime.datetime.now()
customer = entity.get_customers().filter(customer_name=qoutation.customer.get_full_name).first()
invoice_model = entity.get_invoices().filter(customer=customer)
if status == "approved":
if qoutation.status == "Approved":
messages.error(request, "Quotation is already approved")
return redirect("quotation_detail", pk=pk)
invoice_model = invoice_model.filter(date_in_review=qoutation.date_in_review).first()
if not invoice_model.can_approve():
messages.error(request, "Quotation is not ready for approval")
return redirect("quotation_detail", pk=pk)
invoice_model.mark_as_approved(entity_slug=entity.slug, user_model=request.user.dealer.get_root_dealer.user)
invoice_model.date_approved = date
qoutation.date_approved = date
invoice_model.save()
qoutation.status = "Approved"
qoutation.save()
for car in qoutation.quotation_cars.all():
car.car.status = "reserved"
car.car.save()
messages.success(request, _("Quotation Approved"))
elif status == "paid":
if qoutation.status == "Paid":
messages.error(request, "Quotation is already paid")
return redirect("quotation_detail", pk=pk)
invoice_model = invoice_model.filter(date_approved=qoutation.date_approved).first()
if not invoice_model.can_pay():
messages.error(request, "Quotation is not ready for payment")
return redirect("quotation_detail", pk=pk)
invoice_model.mark_as_paid(entity_slug=entity.slug, user_model=request.user.dealer.get_root_dealer.user)
invoice_model.date_paid = date
qoutation.date_paid = date
invoice_model.save()
qoutation.status = "Paid"
qoutation.save()
messages.success(request, _("Quotation Paid"))
return redirect("quotation_detail", pk=pk)
@login_required
def confirm_quotation(request, pk):
quotation = get_object_or_404(models.SaleQuotation, pk=pk)
if quotation.is_approved:
messages.error(request, _("Quotation already approved."))
return redirect("quotation_detail", pk=pk)
try:
# quotation.confirm()
# quotation_cars = quotation.quotation_cars.annotate(total_price=F('car__total') * F('quantity'))
# total = quotation.quotation_cars.aggregate(total_price=Sum(F('car__finances__selling_price') * F('quantity')))
models.SalesOrder.objects.create(
quotation=quotation,
total_amount=quotation.total_vat,
# total_amount=quotation.quotation_cars.aggregate(Sum("total_amount"))["total_amount__sum"],
)
quotation.is_approved = True
quotation.save()
messages.success(request, _("Quotation confirmed and sales order created."))
except ValueError as e:
messages.error(request, str(e))
return redirect("quotation_detail", pk=pk)
class SalesOrderDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
model = models.SalesOrder
template_name = "sales/sales_order_detail.html"
context_object_name = "sales_order"
permission_required = ("inventory.view_salequotation",)
slug_field = "order_id"
slug_url_kwarg = "order_id"
# Users
class UserListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
model = models.Dealer
context_object_name = "users"
paginate_by = 10
template_name = "users/user_list.html"
permission_required = ("inventory.view_dealer",)
def get_queryset(self):
query = self.request.GET.get("q")
users = self.request.user.dealer.sub_dealers
if query:
users = users.filter(
Q(name__icontains=query)
| Q(arabic_name__icontains=query)
| Q(phone_number__icontains=query)
| Q(address__icontains=query)
)
return users.all()
class UserDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
model = models.Dealer
template_name = "users/user_detail.html"
context_object_name = "user_"
permission_required = ("inventory.view_dealer",)
class UserCreateView(
LoginRequiredMixin,
PermissionRequiredMixin,
SuccessMessageMixin,
AddDealerInstanceMixin,
CreateView,
):
model = models.Dealer
form_class = forms.UserForm
template_name = "users/user_form.html"
success_url = reverse_lazy("user_list")
permission_required = ("inventory.add_dealer",)
success_message = _("User created successfully.")
def get_form(self, form_class=None):
form = super().get_form(form_class)
form.fields["dealer_type"].choices = [
t for t in form.fields["dealer_type"].choices if t[0] != "Owner"
]
return form
def form_valid(self, form):
dealer = self.request.user.dealer.get_root_dealer
if dealer.sub_dealers.count() >= dealer.get_active_plan.max_users:
messages.error(
self.request, _("You have reached the maximum number of users.")
)
return redirect("user_list")
user = User.objects.create_user(username=form.cleaned_data["name"])
user.set_password("Tenhal@123")
user.save()
form.instance.user = user
form.instance.parent_dealer = dealer
for group in user.groups.all():
group.user_set.remove(user)
Group.objects.get(name=form.cleaned_data["dealer_type"].lower()).user_set.add(
user
)
form.save()
return super().form_valid(form)
class UserUpdateView(
LoginRequiredMixin,
PermissionRequiredMixin,
SuccessMessageMixin,
AddDealerInstanceMixin,
UpdateView,
):
model = models.Dealer
form_class = forms.UserForm
template_name = "users/user_form.html"
success_url = reverse_lazy("user_list")
permission_required = ("inventory.change_dealer",)
success_message = _("User updated successfully.")
def get_form(self, form_class=None):
form = super().get_form(form_class)
if not self.request.user.has_perms(["inventory.change_dealer_type"]):
field = form.fields["dealer_type"]
field.widget = field.hidden_widget()
form.fields["dealer_type"].choices = [
t for t in form.fields["dealer_type"].choices if t[0] != "Owner"
]
return form
def form_valid(self, form):
user = form.instance.user
for group in user.groups.all():
group.user_set.remove(user)
Group.objects.get(name=form.cleaned_data["dealer_type"].lower()).user_set.add(
user
)
form.save()
return super().form_valid(form)
def UserDeleteview(request, pk):
user = get_object_or_404(models.Dealer, pk=pk)
user.delete()
messages.success(request, _("User deleted successfully."))
return redirect("user_list")
# errors
def custom_page_not_found_view(request, exception):
return render(request, "errors/404.html", {})
def custom_error_view(request, exception=None):
return render(request, "errors/500.html", {})
def custom_permission_denied_view(request, exception=None):
return render(request, "errors/403.html", {})
def custom_bad_request_view(request, exception=None):
return render(request, "errors/400.html", {})
class OrganizationListView(LoginRequiredMixin, ListView):
model = models.Organization
template_name = "organizations/organization_list.html"
context_object_name = "organizations"
class OrganizationDetailView(DetailView):
model = models.Organization
template_name = "organizations/organization_detail.html"
context_object_name = "organization"
class OrganizationCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
model = models.Organization
form_class = forms.OrganizationForm
template_name = "organizations/organization_form.html"
success_url = reverse_lazy("organization_list")
success_message = "Organization created successfully."
def form_valid(self, form):
if form.is_valid():
form.instance.dealer = self.request.user.dealer.get_root_dealer
form.save()
return super().form_valid(form)
else:
return form.errors
class OrganizationUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
model = models.Organization
form_class = forms.OrganizationForm
template_name = "organizations/organization_form.html"
success_url = reverse_lazy("organization_list")
success_message = "Organization updated successfully."
class OrganizationDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteView):
model = models.Organization
template_name = "organizations/organization_confirm_delete.html"
success_url = reverse_lazy("organization_list")
success_message = "Organization deleted successfully."
class RepresentativeListView(LoginRequiredMixin, ListView):
model = models.Representative
template_name = "representatives/representative_list.html"
context_object_name = "representatives"
class RepresentativeDetailView(DetailView):
model = models.Representative
template_name = "representatives/representative_detail.html"
context_object_name = "representative"
class RepresentativeCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
model = models.Representative
form_class = forms.RepresentativeForm
template_name = "representatives/representative_form.html"
success_url = reverse_lazy("representative_list")
success_message = "Representative created successfully."
def form_valid(self, form):
if form.is_valid():
form.instance.dealer = self.request.user.dealer.get_root_dealer
form.save()
return super().form_valid(form)
else:
return form.errors
class RepresentativeUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
model = models.Representative
form_class = forms.RepresentativeForm
template_name = "representatives/representative_form.html"
success_url = reverse_lazy("representative_list")
success_message = "Representative updated successfully."
class RepresentativeDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteView):
model = models.Representative
template_name = "representatives/representative_confirm_delete.html"
success_url = reverse_lazy("representative_list")
success_message = "Representative deleted successfully."
# def quotation_pdf_view(request, pk):
# # Get the quotation object
# quotation = models.SaleQuotation.objects.get(pk=pk)
#
# # Render the HTML template for the quotation page
# context = {
# "quotation": quotation,
# }
# context_result = get_calculations(quotation)
# context = context.update(context_result)
#
# html_content = render_to_string("sales/quotation_pdf.html", context)
#
# # Create a PDF file
#
# pdf_file = HTML(string=html_content).render()
#
# # Save the PDF file to a file
# with open("quotation.pdf", "wb") as f:
# f.write(pdf_file.write_pdf())
#
# # Return the PDF file as a response
# return HttpResponse(pdf_file, content_type="application/pdf")
@login_required
def download_quotation_pdf(request, quotation_id):
try:
# Retrieve the quotation object
quotation = models.SaleQuotation.objects.get(id=quotation_id)
cars = models.SaleQuotationCar.objects.get(id=quotation_id)
print(cars)
services = cars.finance.additional_services.all()
print(services)
# Create a response object
response = HttpResponse(content_type='application/pdf')
response['Content-Disposition'] = f'attachment; filename="quotation_{quotation.id}.pdf"'
# Call the PDF generation function
generate_quotation_pdf(response, quotation, services)
return response
except models.SaleQuotation.DoesNotExist:
return HttpResponse("Quotation not found", status=404)
@login_required
def invoice_detail(request,pk):
quotation = get_object_or_404(models.SaleQuotation, pk=pk)
entity = quotation.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)
entity = quotation.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)
if request.method == "POST":
form = forms.PaymentForm(request.POST)
if form.is_valid():
form.instance.quotation = quotation
insatnce = form.save()
entity = quotation.entity
customer = entity.get_customers().filter(customer_name=quotation.customer.get_full_name).first()
coa_qs, coa_map = entity.get_all_coa_accounts()
cash_account = coa_qs.first().get_coa_accounts().filter(name="Cash")
recivable_account = coa_qs.first().get_coa_accounts().filter(name="Accounts Receivable")
journal_entry = JournalEntryModel.objects.filter(pk=quotation.payment_id).first()
TransactionModel.objects.create(
journal_entry=journal_entry,
account=cash_account.first(), # Debit Cash
amount=insatnce.amount, # Payment amount
tx_type='debit',
description="Payment Received",
)
TransactionModel.objects.create(
journal_entry=journal_entry,
account=recivable_account.first(), # Credit Accounts Receivable
amount=insatnce.amount, # Payment amount
tx_type='credit',
description="Payment Received",
)
journal_entry.posted = True
quotation.posted = True
quotation.save()
journal_entry.save()
invoice_model = entity.get_invoices().filter(date_approved=quotation.date_approved).first()
invoice_model.mark_as_paid(entity_slug=entity.slug, user_model=request.user.dealer.get_root_dealer.user)
date = timezone.now()
invoice_model.date_paid = date
quotation.date_paid = date
invoice_model.save()
quotation.status = "Paid"
quotation.save()
messages.success(request, "Payment created successfully.")
return redirect("quotation_detail", pk=pk)
else:
form = forms.PaymentForm()
return render(request, "sales/payments/payment_create.html", {"quotation": quotation,"form": form})