3624 lines
126 KiB
Python
3624 lines
126 KiB
Python
from rich import print
|
|
from decimal import Decimal
|
|
from django.core.paginator import Paginator
|
|
from django.forms import DateField, DateInput, HiddenInput, TextInput
|
|
from django_ledger.forms.bill import (
|
|
ApprovedBillModelUpdateForm,
|
|
InReviewBillModelUpdateForm,
|
|
)
|
|
from django.utils.decorators import method_decorator
|
|
from django.views.decorators.csrf import csrf_exempt
|
|
from django_ledger.models import (
|
|
EntityModel,
|
|
InvoiceModel,
|
|
BankAccountModel,
|
|
AccountModel,
|
|
JournalEntryModel,
|
|
TransactionModel,
|
|
EstimateModel,
|
|
CustomerModel,
|
|
LedgerModel,
|
|
ItemModel,
|
|
BillModel,
|
|
VendorModel,
|
|
)
|
|
from django_ledger.forms.bank_account import (
|
|
BankAccountCreateForm,
|
|
BankAccountUpdateForm,
|
|
)
|
|
|
|
from django_ledger.forms.customer import CustomerModelForm
|
|
from django_ledger.forms.bill import BillModelCreateForm
|
|
from django_ledger.forms.invoice import (
|
|
DraftInvoiceModelUpdateForm,
|
|
ApprovedInvoiceModelUpdateForm,
|
|
PaidInvoiceModelUpdateForm,
|
|
)
|
|
from django_ledger.forms.account import AccountModelCreateForm, AccountModelUpdateForm
|
|
# from django_ledger.forms.estimate import EstimateModelCreateForm
|
|
|
|
from django_ledger.forms.invoice import InvoiceModelCreateForm
|
|
from django_ledger.forms.item import (
|
|
ServiceCreateForm,
|
|
ExpenseItemCreateForm,
|
|
ExpenseItemUpdateForm,
|
|
)
|
|
from django_ledger.forms.journal_entry import JournalEntryModelCreateForm
|
|
from django_ledger.io import roles
|
|
from django.contrib.admin.models import LogEntry
|
|
import logging
|
|
import json
|
|
import datetime
|
|
from django.db.models.functions import Coalesce
|
|
from django.shortcuts import HttpResponse
|
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
|
from django.contrib.auth.decorators import login_required
|
|
from django.http import JsonResponse
|
|
from django.shortcuts import render, get_object_or_404, redirect
|
|
from django.utils.translation import gettext_lazy as _
|
|
from django.db.models import Q
|
|
from django.views.generic import (
|
|
View,
|
|
ListView,
|
|
DetailView,
|
|
CreateView,
|
|
UpdateView,
|
|
DeleteView,
|
|
TemplateView,
|
|
)
|
|
from django.utils import timezone, translation
|
|
from django.conf import settings
|
|
from urllib.parse import urlparse, urlunparse
|
|
from django.urls import reverse, reverse_lazy
|
|
from django.contrib import messages
|
|
from django.db.models import Sum, F, Count
|
|
from django.db import transaction
|
|
# from two_factor.utils import default_device
|
|
|
|
# from two_factor.views import OTPRequiredMixin
|
|
|
|
from .forms import VendorForm
|
|
from .services import (
|
|
decodevin,
|
|
get_make,
|
|
get_model,
|
|
)
|
|
from . import models, forms
|
|
from django.contrib.auth.mixins import PermissionRequiredMixin
|
|
from django.contrib.messages.views import SuccessMessageMixin
|
|
from django.contrib.auth.models import Group
|
|
from .utils import (
|
|
CarFinanceCalculator,
|
|
calculate_vat_amount,
|
|
get_calculations,
|
|
get_car_finance_data,
|
|
get_financial_values,
|
|
reserve_car,
|
|
send_email,
|
|
get_user_type,
|
|
set_bill_payment,
|
|
set_invoice_payment,
|
|
to_dict,
|
|
transfer_car,
|
|
)
|
|
from django.contrib.auth.models import User
|
|
from django.db.models import Count, F, Value
|
|
from django.contrib.auth import authenticate
|
|
import cv2
|
|
import numpy as np
|
|
from pyzbar.pyzbar import decode
|
|
from django.core.files.storage import default_storage
|
|
from plans.models import Plan,PlanPricing
|
|
|
|
logger = logging.getLogger(__name__)
|
|
logging.basicConfig(level=logging.INFO)
|
|
|
|
|
|
def switch_language(request):
|
|
language = request.GET.get("language", "en")
|
|
referer = request.META.get("HTTP_REFERER", "/")
|
|
parsed_url = urlparse(referer)
|
|
path_parts = parsed_url.path.split("/")
|
|
|
|
if path_parts[1] in dict(settings.LANGUAGES):
|
|
path_parts.pop(1)
|
|
|
|
new_path = "/".join(path_parts)
|
|
new_url = urlunparse(
|
|
(
|
|
parsed_url.scheme,
|
|
parsed_url.netloc,
|
|
new_path,
|
|
parsed_url.params,
|
|
parsed_url.query,
|
|
parsed_url.fragment,
|
|
)
|
|
)
|
|
|
|
if language in dict(settings.LANGUAGES):
|
|
logger.debug(f"Switching language to: {language}")
|
|
response = redirect(new_url)
|
|
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, language)
|
|
translation.activate(language)
|
|
request.session[settings.LANGUAGE_COOKIE_NAME] = language
|
|
logger.debug(
|
|
f"Language switched to: {language}, Session: {request.session[settings.LANGUAGE_COOKIE_NAME]}"
|
|
)
|
|
return response
|
|
else:
|
|
logger.warning(f"Invalid language code: {language}")
|
|
return redirect("/")
|
|
|
|
|
|
|
|
def dealer_signup(request, *args, **kwargs):
|
|
if request.method == "POST":
|
|
data = json.loads(request.body)
|
|
wf1 = data.get("wizardValidationForm1")
|
|
wf2 = data.get("wizardValidationForm2")
|
|
wf3 = data.get("wizardValidationForm3")
|
|
# username = wf1.get("username")
|
|
email = wf1.get("email")
|
|
password = wf1.get("password")
|
|
password_confirm = wf1.get("confirm_password")
|
|
name = wf2.get("name")
|
|
arabic_name = wf2.get("arabic_name")
|
|
phone = wf2.get("phone_number")
|
|
crn = wf3.get("crn")
|
|
vrn = wf3.get("vrn")
|
|
address = wf3.get("address")
|
|
|
|
if password != password_confirm:
|
|
return JsonResponse({"error": "Passwords do not match."}, status=400)
|
|
|
|
try:
|
|
with transaction.atomic():
|
|
# user = User.objects.create(username=username, email=email)
|
|
user = User.objects.create(username=email, email=email)
|
|
user.set_password(password)
|
|
user.save()
|
|
|
|
models.Dealer.objects.create(
|
|
user=user,
|
|
name=name,
|
|
arabic_name=arabic_name,
|
|
crn=crn,
|
|
vrn=vrn,
|
|
phone_number=phone,
|
|
address=address,
|
|
)
|
|
# user = authenticate(request, email=email, password=password)
|
|
# if user is not None:
|
|
# return JsonResponse(
|
|
# {"message": "User created successfully."}, status=200
|
|
# )
|
|
# else:
|
|
# return JsonResponse({"error": "User creation failed."}, status=400)
|
|
# return redirect("account_login")
|
|
|
|
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(allauth_views.LoginView):
|
|
# template_name = "account/login.html"
|
|
# redirect_authenticated_user = True
|
|
# class OTPView(View, LoginRequiredMixin):
|
|
# template_name = "account/otp_verification.html"
|
|
#
|
|
# def get(self, request, *args, **kwargs):
|
|
# # device = default_device(request.user)
|
|
# EmailDevice.generate_challenge(self)
|
|
# return render(request, self.template_name)
|
|
#
|
|
# def post(self, request, *args, **kwargs):
|
|
# otp_code = request.POST.get("otp_code")
|
|
#
|
|
# if self.verify_otp(otp_code, request.user):
|
|
# messages.success(request, _("OTP verified successfully!"))
|
|
# return redirect("home")
|
|
#
|
|
# messages.error(request, _("Invalid OTP. Please try again."))
|
|
# return render(request, self.template_name)
|
|
#
|
|
# def verify_otp(self, otp_code, user):
|
|
# device = default_device(user)
|
|
# if device and device.verify_token(otp_code):
|
|
# return True
|
|
# return False
|
|
|
|
|
|
class HomeView(TemplateView):
|
|
template_name = "index.html"
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
if not request.user.is_authenticated:
|
|
return redirect("welcome")
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
dealer = get_user_type(self.request)
|
|
total_cars = models.Car.objects.filter(dealer=dealer).count()
|
|
total_reservations = models.CarReservation.objects.filter(
|
|
reserved_until__gte=timezone.now()
|
|
).count()
|
|
cars_in_house = models.CarLocation.objects.filter(
|
|
owner=dealer,
|
|
).count()
|
|
cars_outside = total_cars - cars_in_house
|
|
stats = models.CarFinance.objects.aggregate(
|
|
total_cost_price=Sum("cost_price"),
|
|
total_selling_price=Sum("selling_price"),
|
|
)
|
|
total_cost_price = stats["total_cost_price"] or 0
|
|
total_selling_price = stats["total_selling_price"] or 0
|
|
total_profit = total_selling_price - total_cost_price
|
|
|
|
context["dealer"] = dealer
|
|
context["total_cars"] = total_cars
|
|
context["cars_in_house"] = cars_in_house
|
|
context["cars_outside"] = cars_outside
|
|
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 TestView(TemplateView):
|
|
template_name = "test.html"
|
|
|
|
|
|
class AccountingDashboard(LoginRequiredMixin, TemplateView):
|
|
template_name = "dashboards/accounting.html"
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
if (
|
|
# not any(hasattr(request.user, attr) for attr in ["dealer", "subdealer"])
|
|
not request.user.is_authenticated
|
|
):
|
|
# messages.error(request, _("You are not associated with any dealer."))
|
|
return redirect("welcome")
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
dealer = get_user_type(self.request)
|
|
total_cars = models.Car.objects.filter(dealer=dealer).count()
|
|
total_reservations = models.CarReservation.objects.filter(
|
|
reserved_until__gte=timezone.now()
|
|
).count()
|
|
stats = models.CarFinance.objects.aggregate(
|
|
total_cost_price=Sum("cost_price"),
|
|
total_selling_price=Sum("selling_price"),
|
|
)
|
|
total_cost_price = stats["total_cost_price"] or 0
|
|
total_selling_price = stats["total_selling_price"] or 0
|
|
total_profit = total_selling_price - total_cost_price
|
|
|
|
context["dealer"] = dealer
|
|
context["total_cars"] = total_cars
|
|
context["total_reservations"] = total_reservations
|
|
context["total_cost_price"] = total_cost_price
|
|
context["total_selling_price"] = total_selling_price
|
|
context["total_profit"] = total_profit
|
|
return context
|
|
|
|
|
|
class WelcomeView(TemplateView):
|
|
template_name = "welcome.html"
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
dealer = get_user_type(self.request)
|
|
plans = Plan.objects.all()
|
|
# pricing = PlanPricing.objects.filter(plan=plan).
|
|
context["plans"] = plans
|
|
return context
|
|
|
|
|
|
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_form(self, form_class=None):
|
|
form = super().get_form(form_class)
|
|
dealer = get_user_type(self.request)
|
|
form.fields["vendor"].queryset = dealer.entity.get_vendors().filter(active=True)
|
|
return form
|
|
|
|
def get_success_url(self):
|
|
"""Determine the redirect URL based on user choice."""
|
|
if self.request.POST.get("add_another"):
|
|
return reverse("car_add")
|
|
return reverse("inventory_stats")
|
|
|
|
def form_valid(self, form):
|
|
dealer = get_user_type(self.request)
|
|
form.instance.dealer = dealer
|
|
form.save()
|
|
messages.success(self.request, "Car saved successfully.")
|
|
return super().form_valid(form)
|
|
|
|
|
|
class AjaxHandlerView(LoginRequiredMixin, View):
|
|
def get(self, request, *args, **kwargs):
|
|
action = request.GET.get("action")
|
|
handlers = {
|
|
"decode_vin": self.decode_vin,
|
|
"get_models": self.get_models,
|
|
"get_series": self.get_series,
|
|
"get_trims": self.get_trims,
|
|
"get_specifications": self.get_specifications,
|
|
"get_equipments": self.get_equipments,
|
|
"get_options": self.get_options,
|
|
}
|
|
handler = handlers.get(action)
|
|
if handler:
|
|
return handler(request)
|
|
else:
|
|
return JsonResponse({"error": "Invalid action"}, status=400)
|
|
|
|
def decode_vin(self, request):
|
|
vin_no = request.GET.get("vin_no")
|
|
car_existed = models.Car.objects.filter(vin=vin_no).exists()
|
|
|
|
if car_existed:
|
|
return JsonResponse({"error": _("VIN number exists")}, status=400)
|
|
|
|
if not vin_no or len(vin_no.strip()) != 17:
|
|
return JsonResponse(
|
|
{"success": False, "error": "Invalid VIN number provided."}, status=400
|
|
)
|
|
|
|
vin_no = vin_no.strip()
|
|
vin_data = {}
|
|
decoding_method = ""
|
|
|
|
# manufacturer_name = model_name = year_model = None
|
|
if not (result := decodevin(vin_no)):
|
|
return JsonResponse(
|
|
{"success": False, "error": "VIN not found in all sources."}, status=404
|
|
)
|
|
|
|
manufacturer_name, model_name, year_model = result.values()
|
|
make = get_make(manufacturer_name)
|
|
model = get_model(model_name, make)
|
|
|
|
logger.info(
|
|
f"VIN decoded using {decoding_method}: Make={manufacturer_name}, Model={model_name}, Year={year_model}"
|
|
)
|
|
car_model = model
|
|
car_make = make
|
|
|
|
if not car_make:
|
|
return JsonResponse(
|
|
{"success": False, "error": "Manufacturer not found in the database."},
|
|
status=404,
|
|
)
|
|
vin_data["make_id"] = car_make.id_car_make
|
|
vin_data["name"] = car_make.name
|
|
vin_data["arabic_name"] = car_make.arabic_name
|
|
|
|
if not car_model:
|
|
vin_data["model_id"] = ""
|
|
else:
|
|
vin_data["model_id"] = car_model.id_car_model
|
|
vin_data["year"] = year_model
|
|
return JsonResponse({"success": True, "data": vin_data})
|
|
|
|
def get_models(self, request):
|
|
make_id = request.GET.get("make_id")
|
|
car_models = (
|
|
models.CarModel.objects.filter(id_car_make=make_id)
|
|
.values("id_car_model", "name", "arabic_name")
|
|
.order_by("name")
|
|
)
|
|
return JsonResponse(list(car_models), safe=False)
|
|
|
|
def get_series(self, request):
|
|
model_id = request.GET.get("model_id")
|
|
year = request.GET.get("year")
|
|
|
|
# Validate inputs
|
|
if not model_id or not year:
|
|
return JsonResponse(
|
|
{"error": "Missing required parameters: model_id or year"}, status=400
|
|
)
|
|
try:
|
|
year = int(year)
|
|
except ValueError:
|
|
return JsonResponse({"error": "Invalid year format"}, status=400)
|
|
|
|
series = models.CarSerie.objects.filter(
|
|
id_car_model=model_id, year_begin__lte=year, year_end__gte=year
|
|
).values("id_car_serie", "name", "arabic_name", "generation_name")
|
|
|
|
return JsonResponse(list(series), safe=False)
|
|
|
|
def get_trims(self, request):
|
|
serie_id = request.GET.get("serie_id")
|
|
# model_id = request.GET.get('model_id')
|
|
trims = models.CarTrim.objects.filter(id_car_serie=serie_id).values(
|
|
"id_car_trim", "name", "arabic_name"
|
|
)
|
|
return JsonResponse(list(trims), safe=False)
|
|
|
|
def get_specifications(self, request):
|
|
trim_id = request.GET.get("trim_id")
|
|
car_spec_values = models.CarSpecificationValue.objects.filter(
|
|
id_car_trim=trim_id
|
|
)
|
|
lang = translation.get_language()
|
|
specs_by_parent = {}
|
|
for value in car_spec_values:
|
|
specification = value.id_car_specification
|
|
parent = specification.id_parent
|
|
parent_id = parent.id_car_specification if parent else 0
|
|
if lang == "ar":
|
|
parent_name = parent.arabic_name if parent else "Root"
|
|
else:
|
|
parent_name = parent.name if parent else "Root"
|
|
if parent_id not in specs_by_parent:
|
|
specs_by_parent[parent_id] = {
|
|
"parent_name": parent_name,
|
|
"specifications": [],
|
|
}
|
|
spec_data = {
|
|
"specification_id": specification.id_car_specification,
|
|
"s_name": specification.arabic_name
|
|
if lang == "ar"
|
|
else specification.name,
|
|
"s_value": value.value,
|
|
"s_unit": value.unit if value.unit else "",
|
|
"trim_name": value.id_car_trim.name,
|
|
}
|
|
specs_by_parent[parent_id]["specifications"].append(spec_data)
|
|
serialized_specs = [
|
|
{"parent_name": v["parent_name"], "specifications": v["specifications"]}
|
|
for v in specs_by_parent.values()
|
|
]
|
|
return JsonResponse(serialized_specs, safe=False)
|
|
|
|
def get_equipments(self, request):
|
|
trim_id = request.GET.get("trim_id")
|
|
equipments = (
|
|
models.CarEquipment.objects.filter(id_car_trim=trim_id)
|
|
.values("id_car_equipment", "name")
|
|
.order_by("name")
|
|
)
|
|
return JsonResponse(list(equipments), safe=False)
|
|
|
|
def get_options(self, request):
|
|
equipment_id = request.GET.get("equipment_id")
|
|
car_option_values = models.CarOptionValue.objects.filter(
|
|
id_car_equipment=equipment_id
|
|
)
|
|
|
|
options_by_parent = {}
|
|
for value in car_option_values:
|
|
option = value.id_car_option
|
|
parent = option.id_parent
|
|
parent_id = parent.id_car_option if parent else 0
|
|
parent_name = parent.name if parent else "Root"
|
|
if parent_id not in options_by_parent:
|
|
options_by_parent[parent_id] = {
|
|
"parent_name": parent_name,
|
|
"options": [],
|
|
}
|
|
option_data = {
|
|
"option_id": option.id_car_option,
|
|
"option_name": option.name,
|
|
"is_base": value.is_base,
|
|
"equipment_name": value.id_car_equipment.name,
|
|
}
|
|
options_by_parent[parent_id]["options"].append(option_data)
|
|
serialized_options = [
|
|
{"parent_name": v["parent_name"], "options": v["options"]}
|
|
for v in options_by_parent.values()
|
|
]
|
|
return JsonResponse(serialized_options, safe=False)
|
|
|
|
|
|
@method_decorator(csrf_exempt, name="dispatch")
|
|
class SearchCodeView(View):
|
|
template_name = "inventory/scan_vin.html"
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
"""Render the form page."""
|
|
return render(request, self.template_name)
|
|
|
|
def post(self, request, *args, **kwargs):
|
|
image_file = request.FILES.get("image")
|
|
|
|
if image_file:
|
|
print("image received!")
|
|
image = cv2.imdecode(
|
|
np.frombuffer(image_file.read(), np.uint8), cv2.IMREAD_COLOR
|
|
)
|
|
decoded_objects = decode(image)
|
|
if decoded_objects:
|
|
print("image decoded!")
|
|
print(decoded_objects[0])
|
|
code = decoded_objects[0].data.decode("utf-8")
|
|
print("code received!")
|
|
print(code)
|
|
car = get_object_or_404(models.Car, vin=code)
|
|
name = car.id_car_make.get_local_name
|
|
print(name)
|
|
return redirect("car_detail", pk=car.pk)
|
|
else:
|
|
print("back to else statement")
|
|
return JsonResponse({"success": False, "error": "No code detected"})
|
|
else:
|
|
return JsonResponse({"success": False, "error": "No image provided"})
|
|
|
|
|
|
class CarInventory(LoginRequiredMixin, ListView):
|
|
model = models.Car
|
|
home_label = _("inventory")
|
|
template_name = "inventory/car_inventory.html"
|
|
context_object_name = "cars"
|
|
paginate_by = 10
|
|
ordering = ["receiving_date"]
|
|
|
|
def get_queryset(self, *args, **kwargs):
|
|
query = self.request.GET.get("q")
|
|
make_id = self.kwargs["make_id"]
|
|
model_id = self.kwargs["model_id"]
|
|
trim_id = self.kwargs["trim_id"]
|
|
|
|
dealer = get_user_type(self.request)
|
|
cars = models.Car.objects.filter(
|
|
dealer=dealer,
|
|
id_car_make=make_id,
|
|
id_car_model=model_id,
|
|
id_car_trim=trim_id,
|
|
).order_by("receiving_date")
|
|
|
|
if query:
|
|
cars = cars.filter(Q(vin__icontains=query))
|
|
return cars
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context["query"] = self.request.GET.get("q", "")
|
|
context["make_id"] = self.kwargs["make_id"]
|
|
context["model_id"] = self.kwargs["model_id"]
|
|
context["trim_id"] = self.kwargs["trim_id"]
|
|
return context
|
|
|
|
|
|
class CarColorCreate(LoginRequiredMixin, CreateView):
|
|
model = models.CarColors
|
|
form_class = forms.CarColorsForm
|
|
template_name = "inventory/add_colors.html"
|
|
|
|
def form_valid(self, form):
|
|
car = get_object_or_404(models.Car, pk=self.kwargs["car_pk"])
|
|
form.instance.car = car
|
|
return super().form_valid(form)
|
|
|
|
def get_success_url(self):
|
|
return reverse_lazy("car_detail", kwargs={"pk": self.kwargs["car_pk"]})
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context["car"] = get_object_or_404(models.Car, pk=self.kwargs["car_pk"])
|
|
return context
|
|
|
|
|
|
@login_required
|
|
def inventory_stats_view(request):
|
|
dealer = get_user_type(request)
|
|
|
|
# Base queryset for cars belonging to the dealer
|
|
cars = models.Car.objects.filter(dealer=dealer)
|
|
|
|
# Count for total, reserved, showroom, and unreserved cars
|
|
total_cars = cars.count()
|
|
reserved_cars = models.CarReservation.objects.count()
|
|
# showroom_cars = cars.filter(location='showroom').count()
|
|
# unreserved_cars = total_cars - reserved_cars
|
|
|
|
# Annotate total cars by make, model, and trim
|
|
cars = cars.select_related("id_car_make", "id_car_model", "id_car_trim").annotate(
|
|
make_total=Count("id_car_make"),
|
|
model_total=Count("id_car_model"),
|
|
trim_total=Count("id_car_trim"),
|
|
)
|
|
|
|
inventory = {}
|
|
for car in cars:
|
|
make = car.id_car_make
|
|
if make.id_car_make not in inventory:
|
|
inventory[make.id_car_make] = {
|
|
"make_id": make.id_car_make,
|
|
"make_name": make.get_local_name(),
|
|
"total_cars": 0,
|
|
"models": {},
|
|
}
|
|
inventory[make.id_car_make]["total_cars"] += 1
|
|
|
|
model = car.id_car_model
|
|
if model and model.id_car_model not in inventory[make.id_car_make]["models"]:
|
|
inventory[make.id_car_make]["models"][model.id_car_model] = {
|
|
"model_id": model.id_car_model,
|
|
"model_name": model.get_local_name(),
|
|
"total_cars": 0,
|
|
"trims": {},
|
|
}
|
|
try:
|
|
inventory[make.id_car_make]["models"][model.id_car_model]["total_cars"] += 1
|
|
|
|
trim = car.id_car_trim
|
|
if (
|
|
trim
|
|
and trim.id_car_trim
|
|
not in inventory[make.id_car_make]["models"][model.id_car_model][
|
|
"trims"
|
|
]
|
|
):
|
|
inventory[make.id_car_make]["models"][model.id_car_model]["trims"][
|
|
trim.id_car_trim
|
|
] = {
|
|
"trim_id": trim.id_car_trim,
|
|
"trim_name": trim.name,
|
|
"total_cars": 0,
|
|
}
|
|
inventory[make.id_car_make]["models"][model.id_car_model]["trims"][
|
|
trim.id_car_trim
|
|
]["total_cars"] += 1
|
|
except Exception as e:
|
|
print(e)
|
|
result = {
|
|
"total_cars": total_cars,
|
|
"reserved_cars": reserved_cars,
|
|
"makes": [
|
|
{
|
|
"make_id": make_data["make_id"],
|
|
"make_name": make_data["make_name"],
|
|
"total_cars": make_data["total_cars"],
|
|
"models": [
|
|
{
|
|
"model_id": model_data["model_id"],
|
|
"model_name": model_data["model_name"],
|
|
"total_cars": model_data["total_cars"],
|
|
"trims": list(model_data["trims"].values()),
|
|
}
|
|
for model_data in make_data["models"].values()
|
|
],
|
|
}
|
|
for make_data in inventory.values()
|
|
],
|
|
}
|
|
|
|
return render(request, "inventory/inventory_stats.html", {"inventory": result})
|
|
|
|
|
|
class CarDetailView(LoginRequiredMixin, DetailView):
|
|
model = models.Car
|
|
template_name = "inventory/car_detail.html"
|
|
context_object_name = "car"
|
|
|
|
|
|
class CarFinanceCreateView(LoginRequiredMixin, CreateView):
|
|
model = models.CarFinance
|
|
form_class = forms.CarFinanceForm
|
|
template_name = "inventory/car_finance_form.html"
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
self.car = get_object_or_404(models.Car, pk=self.kwargs["car_pk"])
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
def form_valid(self, form):
|
|
form.instance.car = self.car
|
|
messages.success(self.request, _("Car finance details saved successfully."))
|
|
return super().form_valid(form)
|
|
|
|
def get_success_url(self):
|
|
return reverse("car_detail", kwargs={"pk": self.car.pk})
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context["car"] = self.car
|
|
return context
|
|
|
|
def get_form(self, form_class=None):
|
|
form = super().get_form(form_class)
|
|
dealer = get_user_type(self.request)
|
|
form.fields[
|
|
"additional_finances"
|
|
].queryset = models.AdditionalServices.objects.filter(dealer=dealer)
|
|
return form
|
|
|
|
# def get_initial(self):
|
|
# initial = super().get_initial()
|
|
# instance = self.get_object()
|
|
# dealer = get_user_type(self.request.user.dealer)
|
|
# selected_items = instance.additional_services.filter(dealer=dealer)
|
|
# initial["additional_finances"] = selected_items
|
|
# return initial
|
|
|
|
|
|
class CarFinanceUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
|
|
model = models.CarFinance
|
|
form_class = forms.CarFinanceForm
|
|
template_name = "inventory/car_finance_form.html"
|
|
success_message = _("Car finance details updated successfully.")
|
|
|
|
def get_success_url(self):
|
|
return reverse("car_detail", kwargs={"pk": self.object.car.pk})
|
|
|
|
def get_form_kwargs(self):
|
|
kwargs = super().get_form_kwargs()
|
|
kwargs["instance"] = self.get_object()
|
|
return kwargs
|
|
|
|
def get_initial(self):
|
|
initial = super().get_initial()
|
|
instance = self.get_object()
|
|
dealer = get_user_type(self.request)
|
|
selected_items = instance.additional_services.filter(dealer=dealer)
|
|
initial["additional_finances"] = selected_items
|
|
return initial
|
|
|
|
def get_form(self, form_class=None):
|
|
form = super().get_form(form_class)
|
|
dealer = get_user_type(self.request)
|
|
form.fields[
|
|
"additional_finances"
|
|
].queryset = models.AdditionalServices.objects.filter(dealer=dealer)
|
|
return form
|
|
|
|
|
|
class CarUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
|
|
model = models.Car
|
|
form_class = forms.CarUpdateForm
|
|
template_name = "inventory/car_edit.html"
|
|
success_message = _("Car updated successfully.")
|
|
|
|
def get_success_url(self):
|
|
return reverse("car_detail", kwargs={"pk": self.object.pk})
|
|
|
|
|
|
class CarDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteView):
|
|
model = models.Car
|
|
template_name = "inventory/car_confirm_delete.html"
|
|
success_url = reverse_lazy("inventory_stats")
|
|
|
|
def delete(self, request, *args, **kwargs):
|
|
messages.success(request, _("Car deleted successfully."))
|
|
return super().delete(request, *args, **kwargs)
|
|
|
|
|
|
class CarLocationCreateView(CreateView):
|
|
model = models.CarLocation
|
|
form_class = forms.CarLocationForm
|
|
template_name = "inventory/car_location_form.html"
|
|
|
|
def get_success_url(self):
|
|
return reverse_lazy("car_detail", kwargs={"pk": self.object.car.pk})
|
|
|
|
def form_valid(self, form):
|
|
form.instance.car = get_object_or_404(models.Car, pk=self.kwargs["car_pk"])
|
|
dealer = get_user_type(self.request)
|
|
form.instance.owner = dealer
|
|
form.save()
|
|
messages.success(self.request, "Car saved successfully.")
|
|
return super().form_valid(form)
|
|
|
|
|
|
class CarLocationUpdateView(UpdateView):
|
|
model = models.CarLocation
|
|
form_class = forms.CarLocationForm
|
|
template_name = "inventory/car_location_form.html"
|
|
|
|
def get_success_url(self):
|
|
return reverse_lazy("car_detail", kwargs={"pk": self.object.car.pk})
|
|
|
|
|
|
class CarTransferCreateView(CreateView):
|
|
model = models.CarTransfer
|
|
form_class = forms.CarTransferForm
|
|
template_name = "inventory/car_location_form.html"
|
|
|
|
def get_form(self, form_class=None):
|
|
form = super().get_form(form_class)
|
|
form.fields["to_dealer"].queryset = models.Dealer.objects.exclude(
|
|
pk=get_user_type(self.request).pk
|
|
).all()
|
|
form.fields["car"].queryset = models.Car.objects.filter(pk=self.kwargs["pk"])
|
|
return form
|
|
|
|
def get_initial(self):
|
|
initial = super().get_initial()
|
|
initial["car"] = get_object_or_404(models.Car, pk=self.kwargs["pk"])
|
|
return initial
|
|
|
|
def form_valid(self, form):
|
|
form.instance.from_dealer = get_user_type(self.request)
|
|
form.instance.car.status = "transfer"
|
|
form.instance.car.save()
|
|
return super().form_valid(form)
|
|
|
|
def get_success_url(self):
|
|
return reverse_lazy("car_detail", kwargs={"pk": self.object.car.pk})
|
|
|
|
|
|
def CarTransferDetailView(request, pk):
|
|
transfer = get_object_or_404(models.CarTransfer, pk=pk)
|
|
context = {"transfer": transfer}
|
|
return render(request, "inventory/transfer_details.html", context)
|
|
|
|
|
|
def car_transfer_approve(request, car_pk, transfer_pk):
|
|
car = get_object_or_404(models.Car, pk=car_pk)
|
|
transfer = get_object_or_404(models.CarTransfer, pk=transfer_pk)
|
|
action = request.GET.get("action")
|
|
if action == "cancel":
|
|
transfer.status = "cancel"
|
|
transfer.active = False
|
|
transfer.save()
|
|
transfer.car.status = "available"
|
|
transfer.car.save()
|
|
messages.success(request, _("Car transfer canceled successfully."))
|
|
models.Notification.objects.create(
|
|
user=transfer.from_dealer.user,
|
|
message=f"Car transfer request from {transfer.to_dealer} is canceled.",
|
|
)
|
|
return redirect("car_detail", pk=car.pk)
|
|
transfer.status = "approved"
|
|
transfer.save()
|
|
url = request.build_absolute_uri(
|
|
reverse(
|
|
"transfer_preview", kwargs={"car_pk": car.pk, "transfer_pk": transfer.pk}
|
|
)
|
|
)
|
|
models.Notification.objects.create(
|
|
user=transfer.to_dealer.user,
|
|
message=f"Car transfer request from {transfer.from_dealer} is waiting for your acceptance. <a href='{url}'> Accept</a>",
|
|
)
|
|
messages.success(request, _("Car transfer approved successfully."))
|
|
return redirect("car_detail", pk=car.pk)
|
|
|
|
|
|
def car_transfer_accept_reject(request, car_pk, transfer_pk):
|
|
car = get_object_or_404(models.Car, pk=car_pk)
|
|
transfer = get_object_or_404(models.CarTransfer, pk=transfer_pk)
|
|
status = request.GET.get("status")
|
|
if status == "rejected":
|
|
transfer.status = "reject"
|
|
transfer.active = False
|
|
messages.success(request, _("Car transfer rejected successfully."))
|
|
models.Notification.objects.create(
|
|
user=transfer.from_dealer.user,
|
|
message=f"Car transfer request from {transfer.to_dealer} is rejected.",
|
|
)
|
|
transfer.save()
|
|
elif status == "accepted":
|
|
transfer.status = "accept"
|
|
transfer.save()
|
|
success = transfer_car(car, transfer)
|
|
if success:
|
|
messages.success(request, _("Car Transfer Completed successfully."))
|
|
models.Notification.objects.create(
|
|
user=transfer.from_dealer.user,
|
|
message=f"Car transfer request from {transfer.to_dealer} is completed.",
|
|
)
|
|
return redirect("inventory_stats")
|
|
|
|
|
|
def CarTransferPreviewView(request, car_pk, transfer_pk):
|
|
transfer = get_object_or_404(models.CarTransfer, pk=transfer_pk)
|
|
if transfer.to_dealer != get_user_type(request):
|
|
return redirect("car_detail", pk=car_pk)
|
|
return render(request, "inventory/transfer_preview.html", {"transfer": transfer})
|
|
# def get_context_data(self, **kwargs):
|
|
# estimate = kwargs.get("object")
|
|
# if estimate.get_itemtxs_data():
|
|
# data = get_financial_values(estimate)
|
|
|
|
# kwargs["vat_amount"] = data["vat_amount"]
|
|
# kwargs["total"] = data["grand_total"]
|
|
# kwargs["discount_amount"] = data["discount_amount"]
|
|
# kwargs["vat"] = data["vat"]
|
|
# kwargs["car_and_item_info"] = data["car_and_item_info"]
|
|
# kwargs["additional_services"] = data["additional_services"]
|
|
# return super().get_context_data(**kwargs)
|
|
|
|
|
|
# class CarTransferView(View):
|
|
# template_name = "inventory/car_location_form.html"
|
|
|
|
# def get(self, request, *args, **kwargs):
|
|
# form = forms.CarTransferForm()
|
|
# car = models.Car.objects.filter(pk=self.kwargs["pk"])
|
|
# form.fields['to_dealer'].queryset = form.fields['to_dealer'].queryset.exclude(pk=get_user_type(request).pk)
|
|
# form.fields['car'].queryset = car
|
|
# form.initial['car'] = car.first()
|
|
# context = {"form": form}
|
|
# return render(request, self.template_name,context)
|
|
|
|
# def post(self, request, *args, **kwargs):
|
|
# form = forms.CarTransferForm(request.POST)
|
|
# if form.is_valid():
|
|
# from_dealer = get_user_type(request)
|
|
# car = form.cleaned_data['car']
|
|
# to_dealer = form.cleaned_data['to_dealer']
|
|
# remarks = form.cleaned_data['remarks']
|
|
# models.CarTransferLog.objects.create(car=car, from_dealer=from_dealer, to_dealer=to_dealer, remarks=remarks)
|
|
# # car = models.Car.objects.filter(pk=self.kwargs["pk"])
|
|
# # form.instance.car = car.first()
|
|
# # form.instance.to_dealer = get_user_type(request)
|
|
# # form.save()
|
|
# # messages.success(request, "Car transfered successfully.")
|
|
# return redirect("car_detail", pk=self.kwargs["pk"])
|
|
|
|
|
|
class CustomCardCreateView(LoginRequiredMixin, CreateView):
|
|
model = models.CustomCard
|
|
form_class = forms.CustomCardForm
|
|
template_name = "inventory/add_custom_card.html"
|
|
|
|
def form_valid(self, form):
|
|
car = get_object_or_404(models.Car, pk=self.kwargs["car_pk"])
|
|
form.instance.car = car
|
|
return super().form_valid(form)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context["car"] = get_object_or_404(models.Car, pk=self.kwargs["car_pk"])
|
|
return context
|
|
|
|
def get_success_url(self):
|
|
messages.success(self.request, _("Custom Card added successfully."))
|
|
return reverse_lazy("car_detail", kwargs={"pk": self.kwargs["car_pk"]})
|
|
|
|
|
|
@login_required()
|
|
def reserve_car_view(request, car_id):
|
|
if request.method == "POST":
|
|
car = get_object_or_404(models.Car, pk=car_id)
|
|
if car.is_reserved():
|
|
messages.error(request, _("This car is already reserved."))
|
|
return redirect("car_detail", pk=car.pk)
|
|
response = reserve_car(car, request)
|
|
return response
|
|
return JsonResponse(
|
|
{"success": False, "message": "Invalid request method."}, status=400
|
|
)
|
|
|
|
|
|
@login_required
|
|
def manage_reservation(request, reservation_id):
|
|
reservation = get_object_or_404(
|
|
models.CarReservation, pk=reservation_id, reserved_by=request.user
|
|
)
|
|
|
|
if request.method == "POST":
|
|
action = request.POST.get("action")
|
|
if action == "renew":
|
|
reservation.reserved_until = timezone.now() + timezone.timedelta(hours=24)
|
|
reservation.save()
|
|
messages.success(request, _("Reservation renewed successfully."))
|
|
return redirect("car_detail", pk=reservation.car.pk)
|
|
|
|
elif action == "cancel":
|
|
car = reservation.car
|
|
reservation.delete()
|
|
car.status = models.CarStatusChoices.AVAILABLE
|
|
car.save()
|
|
messages.success(request, _("Reservation canceled successfully."))
|
|
return redirect("car_detail", pk=reservation.car.pk)
|
|
|
|
else:
|
|
return JsonResponse(
|
|
{"success": False, "message": _("Invalid action.")}, status=400
|
|
)
|
|
|
|
return JsonResponse(
|
|
{"success": False, "message": _("Invalid request method.")}, status=400
|
|
)
|
|
|
|
|
|
class DealerDetailView(LoginRequiredMixin, DetailView):
|
|
model = models.Dealer
|
|
template_name = "dealers/dealer_detail.html"
|
|
context_object_name = "dealer"
|
|
|
|
def get_queryset(self):
|
|
total_count = models.Dealer.objects.annotate(
|
|
staff_count=Coalesce(Count("staff"), Value(0)),
|
|
total_count=F("staff_count") + Value(1),
|
|
)
|
|
return total_count
|
|
|
|
|
|
class DealerUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
|
|
model = models.Dealer
|
|
form_class = forms.DealerForm
|
|
template_name = "dealers/dealer_form.html"
|
|
success_url = reverse_lazy("dealer_detail")
|
|
success_message = _("Dealer updated successfully.")
|
|
|
|
def get_success_url(self):
|
|
return reverse("dealer_detail", kwargs={"pk": self.object.pk})
|
|
|
|
# def get_form(self, form_class=None):
|
|
# form = super().get_form(form_class)
|
|
# if hasattr(form.fields, "dealer_type"):
|
|
# form.fields.pop("dealer_type")
|
|
# return form
|
|
#
|
|
# def get_form_class(self):
|
|
# if self.request.user.dealer.dealer_type == "OWNER":
|
|
# return forms.DealerForm
|
|
# else:
|
|
# return forms.UserForm
|
|
|
|
|
|
class CustomerListView(LoginRequiredMixin, ListView):
|
|
model = CustomerModel
|
|
home_label = _("customers")
|
|
context_object_name = "customers"
|
|
paginate_by = 10
|
|
template_name = "customers/customer_list.html"
|
|
ordering = ["-created"]
|
|
|
|
def get_queryset(self):
|
|
query = self.request.GET.get("q")
|
|
dealer = get_user_type(self.request)
|
|
|
|
customers = dealer.entity.get_customers().filter(active=True, additional_info__type="customer")
|
|
|
|
if query:
|
|
customers = customers.filter(
|
|
Q(first_name__icontains=query)
|
|
| Q(last_name__icontains=query)
|
|
| Q(additional_info__info__icontains=query)
|
|
)
|
|
return customers
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context["query"] = self.request.GET.get("q", "")
|
|
return context
|
|
|
|
|
|
class CustomerDetailView(LoginRequiredMixin, DetailView):
|
|
model = CustomerModel
|
|
template_name = "customers/view_customer.html"
|
|
context_object_name = "customer"
|
|
|
|
def get_context_data(self, **kwargs):
|
|
dealer = get_user_type(self.request)
|
|
entity = dealer.entity
|
|
context = super().get_context_data(**kwargs)
|
|
# customer = f"{context['customer'].first_name} {context['customer'].middle_name} {context['customer'].last_name}"
|
|
# context["estimates"] = entity.get_estimates().filter(
|
|
# customer__customer_name=name
|
|
# )
|
|
context["estimates"] = context["customer"].estimatemodel_set.all()
|
|
# context["notes"] = models.Notes.objects.filter(
|
|
# content_type__model="customer", object_id=self.object.id
|
|
# )
|
|
# context["activities"] = models.Activity.objects.filter(
|
|
# content_type__model="customer", object_id=self.object.id
|
|
# )
|
|
return context
|
|
|
|
|
|
def add_note_to_customer(request, pk):
|
|
customer = get_object_or_404(CustomerModel, pk=pk)
|
|
if request.method == "POST":
|
|
form = forms.NoteForm(request.POST)
|
|
if form.is_valid():
|
|
note = form.save(commit=False)
|
|
note.content_object = customer
|
|
|
|
note.created_by = request.user
|
|
note.save()
|
|
return redirect("customer_detail", pk=pk)
|
|
else:
|
|
form = forms.NoteForm()
|
|
return render(request, "crm/add_note.html", {"form": form, "customer": customer})
|
|
|
|
|
|
def add_activity_to_customer(request, pk):
|
|
customer = get_object_or_404(CustomerModel, pk=pk)
|
|
if request.method == "POST":
|
|
form = forms.ActivityForm(request.POST)
|
|
if form.is_valid():
|
|
activity = form.save(commit=False)
|
|
activity.content_object = customer
|
|
activity.created_by = request.user
|
|
activity.save()
|
|
return redirect("customer_detail", pk=pk)
|
|
else:
|
|
form = forms.ActivityForm()
|
|
return render(
|
|
request, "crm/add_activity.html", {"form": form, "customer": customer}
|
|
)
|
|
|
|
|
|
def CustomerCreateView(request):
|
|
if request.method == "POST":
|
|
customer_dict = {
|
|
x: request.POST[x] for x in request.POST if x != "csrfmiddlewaretoken"
|
|
}
|
|
dealer = get_user_type(request)
|
|
customer_name = (
|
|
customer_dict["first_name"]
|
|
+ " "
|
|
+ customer_dict["middle_name"]
|
|
+ " "
|
|
+ customer_dict["last_name"]
|
|
)
|
|
|
|
instance = dealer.entity.create_customer(
|
|
customer_model_kwargs={
|
|
"customer_name": customer_name,
|
|
"address_1": customer_dict["address"],
|
|
"phone": customer_dict["phone_number"],
|
|
"email": customer_dict["email"],
|
|
}
|
|
)
|
|
customer_dict["pk"] = str(instance.pk)
|
|
instance.additional_info["customer_info"] = customer_dict
|
|
instance.additional_info["type"] = "customer"
|
|
instance.save()
|
|
messages.success(request, _("Customer created successfully."))
|
|
return redirect("customer_list")
|
|
|
|
form = forms.CustomerForm()
|
|
return render(request, "customers/customer_form.html", {"form": form})
|
|
|
|
|
|
def CustomerUpdateView(request, pk):
|
|
customer = get_object_or_404(CustomerModel, pk=pk)
|
|
if request.method == "POST":
|
|
# form = forms.CustomerForm(request.POST, instance=customer)
|
|
customer_dict = {
|
|
x: request.POST[x] for x in request.POST if x != "csrfmiddlewaretoken"
|
|
}
|
|
dealer = get_user_type(request)
|
|
customer_name = (
|
|
customer_dict["first_name"]
|
|
+ " "
|
|
+ customer_dict["middle_name"]
|
|
+ " "
|
|
+ customer_dict["last_name"]
|
|
)
|
|
|
|
instance = dealer.entity.get_customers().get(pk=pk)
|
|
instance.customer_name = customer_name
|
|
instance.address_1 = customer_dict["address"]
|
|
instance.phone = customer_dict["phone_number"]
|
|
instance.email = customer_dict["email"]
|
|
|
|
customer_dict["pk"] = str(instance.pk)
|
|
instance.additional_info["customer_info"] = customer_dict
|
|
instance.save()
|
|
messages.success(request, _("Customer updated successfully."))
|
|
return redirect("customer_list")
|
|
else:
|
|
form = forms.CustomerForm(
|
|
initial=customer.additional_info["customer_info"] if "customer_info" in customer.additional_info else {})
|
|
return render(request, "customers/customer_form.html", {"form": form})
|
|
|
|
|
|
@login_required
|
|
def delete_customer(request, pk):
|
|
customer = get_object_or_404(models.Customer, pk=pk)
|
|
customer.delete()
|
|
messages.success(request, _("Customer deleted successfully."))
|
|
return redirect("customer_list")
|
|
|
|
|
|
class VendorListView(LoginRequiredMixin, ListView):
|
|
model = VendorModel
|
|
context_object_name = "vendors"
|
|
paginate_by = 10
|
|
template_name = "vendors/vendors_list.html"
|
|
ordering = ["-created"]
|
|
|
|
def get_queryset(self):
|
|
dealer = get_user_type(self.request)
|
|
# vendors = models.Vendor.objects.filter(dealer=dealer)
|
|
return dealer.entity.get_vendors().filter(active=True)
|
|
# return vendors
|
|
|
|
|
|
# class VendorDetailView(LoginRequiredMixin, DetailView):
|
|
# model = models.Vendor
|
|
# template_name = "vendors/view_vendor.html"
|
|
def vendorDetailView(request, pk):
|
|
vendor = get_object_or_404(models.Vendor, pk=pk)
|
|
return render(request, template_name="vendors/view_vendor.html", context={"vendor": vendor})
|
|
|
|
|
|
class VendorCreateView(
|
|
LoginRequiredMixin,
|
|
SuccessMessageMixin,
|
|
CreateView,
|
|
):
|
|
model = models.Vendor
|
|
form_class = forms.VendorForm
|
|
template_name = "vendors/vendor_form.html"
|
|
success_url = reverse_lazy("vendor_list")
|
|
success_message = _("Vendor created successfully.")
|
|
|
|
def form_valid(self, form):
|
|
dealer = get_user_type(self.request)
|
|
form.instance.dealer = dealer
|
|
# instance = form.save(commit=False)
|
|
# instance.entity_model = dealer.entity
|
|
form.instance.save()
|
|
return super().form_valid(form)
|
|
|
|
|
|
class VendorUpdateView(
|
|
LoginRequiredMixin,
|
|
SuccessMessageMixin,
|
|
UpdateView,
|
|
):
|
|
model = models.Vendor
|
|
form_class = forms.VendorForm
|
|
template_name = "vendors/vendor_form.html"
|
|
success_url = reverse_lazy("vendor_list")
|
|
success_message = _("Vendor updated successfully.")
|
|
|
|
|
|
@login_required
|
|
def delete_vendor(request, pk):
|
|
vendor = get_object_or_404(models.Vendor, pk=pk)
|
|
# vendor.active = False
|
|
vendor.delete()
|
|
messages.success(request, _("Vendor deleted successfully."))
|
|
return redirect("vendor_list")
|
|
|
|
|
|
# class QuotationCreateView(LoginRequiredMixin, CreateView):
|
|
# model = models.SaleQuotation
|
|
# form_class = forms.QuotationForm
|
|
# template_name = "sales/quotation_form.html"
|
|
#
|
|
# def form_valid(self, form):
|
|
# form.instance.dealer = get_user_type(self.request)
|
|
# quotation = form.save()
|
|
# selected_cars = form.cleaned_data.get("cars")
|
|
# for car in selected_cars:
|
|
# car_finance = car.finances
|
|
# if car_finance:
|
|
# models.SaleQuotationCar.objects.create(
|
|
# quotation=quotation,
|
|
# car=car,
|
|
# )
|
|
#
|
|
# messages.success(self.request, _("Quotation created successfully."))
|
|
# return redirect("quotation_list")
|
|
|
|
|
|
# class QuotationListView(LoginRequiredMixin, ListView):
|
|
# model = models.SaleQuotation
|
|
# template_name = "sales/quotation_list.html"
|
|
# context_object_name = "quotations"
|
|
# paginate_by = 10
|
|
#
|
|
# def get_queryset(self):
|
|
# status = self.request.GET.get("status")
|
|
# dealer = get_user_type(self.request)
|
|
# queryset = dealer.sales.all()
|
|
# if status:
|
|
# queryset = queryset.filter(status=status)
|
|
# return queryset
|
|
|
|
|
|
# class QuotationDetailView(LoginRequiredMixin, DetailView):
|
|
# model = models.SaleQuotation
|
|
# template_name = "sales/quotation_detail.html"
|
|
# context_object_name = "quotation"
|
|
#
|
|
# def get_context_data(self, **kwargs):
|
|
# context = super().get_context_data(**kwargs)
|
|
# quotation = self.object
|
|
#
|
|
# context_result = get_calculations(quotation)
|
|
# context.update(context_result)
|
|
#
|
|
# return context
|
|
|
|
|
|
# @login_required
|
|
# def generate_invoice(request, pk):
|
|
# quotation = get_object_or_404(models.SaleQuotation, pk=pk)
|
|
# dealer = get_user_type(request)
|
|
# entity = dealer.entity
|
|
# if not quotation.is_approved:
|
|
# messages.error(
|
|
# request, "Quotation must be approved before converting to an invoice."
|
|
# )
|
|
# else:
|
|
# coa_qs, coa_map = entity.get_all_coa_accounts()
|
|
# cash_account = coa_qs.first().get_coa_accounts().filter(name="Cash")
|
|
# recivable_account = (
|
|
# coa_qs.first().get_coa_accounts().filter(name="Accounts Receivable")
|
|
# )
|
|
# customer = (
|
|
# entity.get_customers()
|
|
# .filter(customer_name=quotation.customer.get_full_name)
|
|
# .first()
|
|
# )
|
|
#
|
|
# invoice_model = entity.create_invoice(
|
|
# customer_model=customer,
|
|
# terms=InvoiceModel.TERMS_ON_RECEIPT,
|
|
# cash_account=cash_account.first(),
|
|
# prepaid_account=recivable_account.first(),
|
|
# coa_model=coa_qs.first(),
|
|
# )
|
|
#
|
|
# name_list = [
|
|
# f"{instance.car.year} {instance.car.id_car_make} {instance.car.id_car_model} {instance.car.id_car_trim}"
|
|
# for instance in quotation.quotation_cars.all()
|
|
# ]
|
|
#
|
|
# invoices_item_models = invoice_model.get_item_model_qs().filter(
|
|
# name__in=name_list
|
|
# )
|
|
#
|
|
# invoice_itemtxs = {
|
|
# im.item_number: {
|
|
# "unit_cost": im.default_amount,
|
|
# "quantity": 1,
|
|
# "total_amount": im.default_amount,
|
|
# }
|
|
# for im in invoices_item_models
|
|
# }
|
|
#
|
|
# invoice_itemtxs = invoice_model.migrate_itemtxs(
|
|
# itemtxs=invoice_itemtxs, commit=True, operation=InvoiceModel.ITEMIZE_APPEND
|
|
# )
|
|
# ledger = (
|
|
# entity.get_ledgers()
|
|
# .filter(name=f"Payment Ledger for Invoice {invoice_model}")
|
|
# .first()
|
|
# )
|
|
# if not ledger:
|
|
# ledger = entity.create_ledger(
|
|
# name=f"Payment Ledger for Invoice {invoice_model}", posted=True
|
|
# )
|
|
# journal_entry = JournalEntryModel.objects.create(
|
|
# posted=False,
|
|
# description=f"Payment for Invoice {invoice_model}",
|
|
# ledger=ledger,
|
|
# locked=False,
|
|
# origin="Payment",
|
|
# )
|
|
#
|
|
# quotation.payment_id = journal_entry.pk
|
|
# quotation.is_approved = True
|
|
# date = datetime.datetime.now()
|
|
# quotation.date_draft = date
|
|
# invoice_model.date_draft = date
|
|
# invoice_model.save()
|
|
# quotation.save()
|
|
#
|
|
# if not invoice_model.can_review():
|
|
# messages.error(request, "Quotation is not ready for review")
|
|
# return redirect("quotation_detail", pk=pk)
|
|
#
|
|
# invoice_model.mark_as_review()
|
|
# invoice_model.date_in_review = date
|
|
# quotation.date_in_review = date
|
|
# quotation.status = "In Review"
|
|
# invoice_model.save()
|
|
# quotation.save()
|
|
|
|
# elif status == "approved":
|
|
# if qoutation.status == "Approved":
|
|
# messages.error(request, "Quotation is already approved")
|
|
# return redirect("quotation_detail", pk=pk)
|
|
|
|
# invoice_model = invoice_model.filter(date_in_review=qoutation.date_in_review).first()
|
|
# if not invoice_model.can_approve():
|
|
# messages.error(request, "Quotation is not ready for approval")
|
|
# return redirect("quotation_detail", pk=pk)
|
|
|
|
# invoice_model.mark_as_approved(entity_slug=entity.slug, user_model=request.user.dealer.get_root_dealer.user)
|
|
# invoice_model.date_approved = date
|
|
# qoutation.date_approved = date
|
|
# invoice_model.save()
|
|
# qoutation.status = "Approved"
|
|
# qoutation.save()
|
|
# messages.success(request, _("Quotation Approved"))
|
|
# ledger = entity.create_ledger(
|
|
# name=f"Payment Ledger for Invoice {invoice_model}",
|
|
# posted=True
|
|
# )
|
|
|
|
# entity_unit,created = EntityUnitModel.objects.get_or_create(
|
|
# name="Sales Department",
|
|
# entity=entity,
|
|
# document_prefix="SD"
|
|
# )
|
|
|
|
# journal_entry = JournalEntryModel.objects.create(
|
|
# entity_unit=entity_unit,
|
|
# posted=False,
|
|
# description=f"Payment for Invoice {invoice_model}",
|
|
# ledger=ledger,
|
|
# locked=False,
|
|
# origin="Payment",
|
|
# )
|
|
|
|
# accounts_receivable = coa_qs.first().get_coa_accounts().filter(name="Accounts Receivable").first()
|
|
# if not accounts_receivable:
|
|
# accounts_receivable = entity.create_account(
|
|
# code="AR",
|
|
# role="asset",
|
|
# name="Accounts Receivable",
|
|
# coa_model=coa_qs.first(),
|
|
# balance_type="credit"
|
|
# )
|
|
|
|
# TransactionModel.objects.create(
|
|
# journal_entry=journal_entry,
|
|
# account=cash_account.first(), # Debit Cash
|
|
# amount=invoice_model.amount_due, # Payment amount
|
|
# tx_type='debit',
|
|
# description="Payment Received",
|
|
# )
|
|
|
|
# TransactionModel.objects.create(
|
|
# journal_entry=journal_entry,
|
|
# account=accounts_receivable, # Credit Accounts Receivable
|
|
# amount=invoice_model.amount_due, # Payment amount
|
|
# tx_type='credit',
|
|
# description="Payment Received",
|
|
# )
|
|
|
|
# invoice_model.mark_as_review()
|
|
# print("reviewed")
|
|
# invoice_model.mark_as_approved(entity_slug=entity.slug, user_model=request.user.dealer.get_root_dealer.user)
|
|
# print("approved")
|
|
# invoice_model.mark_as_paid(entity_slug=entity.slug, user_model=request.user.dealer.get_root_dealer.user)
|
|
# print("paid")
|
|
# invoice_model.save()
|
|
# messages.success(request, "Invoice created")
|
|
# return redirect("quotation_detail", pk=pk)
|
|
|
|
# return redirect('django_ledger:invoice-detail', entity_slug=quotation.entity.slug, invoice_pk=invoice.uuid)
|
|
|
|
|
|
# @login_required
|
|
# def post_quotation(request, pk):
|
|
# qoutation = get_object_or_404(models.SaleQuotation, pk=pk)
|
|
# dealer = get_user_type(request)
|
|
# entity = dealer.entity
|
|
# if qoutation.posted:
|
|
# messages.error(request, "Quotation is already posted")
|
|
# return redirect("quotation_detail", pk=pk)
|
|
# coa_qs, coa_map = entity.get_all_coa_accounts()
|
|
# cash_account = coa_qs.first().get_coa_accounts().filter(name="Cash")
|
|
# recivable_account = (
|
|
# coa_qs.first().get_coa_accounts().filter(name="Accounts Receivable")
|
|
# )
|
|
# customer = (
|
|
# entity.get_customers()
|
|
# .filter(customer_name=qoutation.customer.get_full_name)
|
|
# .first()
|
|
# )
|
|
# invoice_model = (
|
|
# entity.get_invoices()
|
|
# .filter(customer=customer, date_paid=qoutation.date_paid)
|
|
# .first()
|
|
# )
|
|
# ledger = (
|
|
# entity.get_ledgers()
|
|
# .filter(name=f"Payment Ledger for Invoice {invoice_model}")
|
|
# .first()
|
|
# )
|
|
# return
|
|
# if not ledger:
|
|
# ledger = entity.create_ledger(name=f"Payment Ledger for Invoice {invoice_model}",posted=True)
|
|
|
|
# entity_unit,created = EntityUnitModel.objects.get_or_create(
|
|
# name="Sales Department",
|
|
# entity=entity,
|
|
# document_prefix="SD"
|
|
# )
|
|
|
|
# journal_entry = JournalEntryModel.objects.create(
|
|
# entity_unit=entity_unit,
|
|
# posted=False,
|
|
# description=f"Payment for Invoice {invoice_model}",
|
|
# ledger=ledger,
|
|
# locked=False,
|
|
# origin="Payment",
|
|
# )
|
|
|
|
# TransactionModel.objects.create(
|
|
# journal_entry=journal_entry,
|
|
# account=cash_account.first(), # Debit Cash
|
|
# amount=invoice_model.amount_due, # Payment amount
|
|
# tx_type='debit',
|
|
# description="Payment Received",
|
|
# )
|
|
|
|
# TransactionModel.objects.create(
|
|
# journal_entry=journal_entry,
|
|
# account=recivable_account.first(), # Credit Accounts Receivable
|
|
# amount=invoice_model.amount_due, # Payment amount
|
|
# tx_type='credit',
|
|
# description="Payment Received",
|
|
# )
|
|
# journal_entry.posted = True
|
|
# qoutation.posted = True
|
|
# qoutation.save()
|
|
# journal_entry.save()
|
|
# messages.success(request, "Invoice posted")
|
|
# return redirect("quotation_detail", pk=pk)
|
|
|
|
|
|
# @login_required
|
|
# def mark_quotation(request, pk):
|
|
# qoutation = get_object_or_404(models.SaleQuotation, pk=pk)
|
|
# status = request.GET.get("status")
|
|
# dealer = request.user.dealer
|
|
# entity = dealer.entity
|
|
# date = datetime.datetime.now()
|
|
# customer = (
|
|
# entity.get_customers()
|
|
# .filter(customer_name=qoutation.customer.get_full_name)
|
|
# .first()
|
|
# )
|
|
# invoice_model = entity.get_invoices().filter(customer=customer)
|
|
# if status == "approved":
|
|
# if qoutation.status == "Approved":
|
|
# messages.error(request, "Quotation is already approved")
|
|
# return redirect("quotation_detail", pk=pk)
|
|
#
|
|
# invoice_model = invoice_model.filter(
|
|
# date_in_review=qoutation.date_in_review
|
|
# ).first()
|
|
# if not invoice_model.can_approve():
|
|
# messages.error(request, "Quotation is not ready for approval")
|
|
# return redirect("quotation_detail", pk=pk)
|
|
#
|
|
# invoice_model.mark_as_approved(
|
|
# entity_slug=entity.slug, user_model=request.user.dealer
|
|
# )
|
|
# invoice_model.date_approved = date
|
|
# qoutation.date_approved = date
|
|
# invoice_model.save()
|
|
# qoutation.status = "Approved"
|
|
# qoutation.save()
|
|
# for car in qoutation.quotation_cars.all():
|
|
# car.car.status = "reserved"
|
|
# car.car.save()
|
|
# messages.success(request, _("Quotation Approved"))
|
|
# elif status == "paid":
|
|
# if qoutation.status == "Paid":
|
|
# messages.error(request, "Quotation is already paid")
|
|
# return redirect("quotation_detail", pk=pk)
|
|
#
|
|
# invoice_model = invoice_model.filter(
|
|
# date_approved=qoutation.date_approved
|
|
# ).first()
|
|
# if not invoice_model.can_pay():
|
|
# messages.error(request, "Quotation is not ready for payment")
|
|
# return redirect("quotation_detail", pk=pk)
|
|
#
|
|
# invoice_model.mark_as_paid(
|
|
# entity_slug=entity.slug, user_model=request.user.dealer
|
|
# )
|
|
# invoice_model.date_paid = date
|
|
# qoutation.date_paid = date
|
|
# invoice_model.save()
|
|
# qoutation.status = "Paid"
|
|
# qoutation.save()
|
|
# messages.success(request, _("Quotation Paid"))
|
|
# return redirect("quotation_detail", pk=pk)
|
|
|
|
|
|
# @login_required
|
|
# def confirm_quotation(request, pk):
|
|
# quotation = get_object_or_404(models.SaleQuotation, pk=pk)
|
|
# if quotation.is_approved:
|
|
# messages.error(request, _("Quotation already approved."))
|
|
# return redirect("quotation_detail", pk=pk)
|
|
#
|
|
# try:
|
|
# # quotation.confirm()
|
|
# # quotation_cars = quotation.quotation_cars.annotate(total_price=F('car__total') * F('quantity'))
|
|
# # total = quotation.quotation_cars.aggregate(total_price=Sum(F('car__finances__selling_price') * F('quantity')))
|
|
#
|
|
# models.SalesOrder.objects.create(
|
|
# quotation=quotation,
|
|
# total_amount=quotation.total_vat,
|
|
# # total_amount=quotation.quotation_cars.aggregate(Sum("total_amount"))["total_amount__sum"],
|
|
# )
|
|
# quotation.is_approved = True
|
|
# quotation.save()
|
|
# messages.success(request, _("Quotation confirmed and sales order created."))
|
|
# except ValueError as e:
|
|
# messages.error(request, str(e))
|
|
# return redirect("quotation_detail", pk=pk)
|
|
|
|
#
|
|
# class SalesOrderDetailView(LoginRequiredMixin, DetailView):
|
|
# model = models.SalesOrder
|
|
# template_name = "sales/sales_order_detail.html"
|
|
# context_object_name = "sales_order"
|
|
# slug_field = "order_id"
|
|
# slug_url_kwarg = "order_id"
|
|
|
|
|
|
# Users
|
|
class UserListView(LoginRequiredMixin, ListView):
|
|
model = models.Staff
|
|
context_object_name = "users"
|
|
paginate_by = 10
|
|
template_name = "users/user_list.html"
|
|
|
|
def get_queryset(self):
|
|
dealer = get_user_type(self.request)
|
|
return models.Staff.objects.filter(dealer=dealer).all()
|
|
|
|
|
|
class UserDetailView(LoginRequiredMixin, DetailView):
|
|
model = models.Staff
|
|
template_name = "users/user_detail.html"
|
|
context_object_name = "user_"
|
|
|
|
|
|
class UserCreateView(
|
|
LoginRequiredMixin,
|
|
SuccessMessageMixin,
|
|
CreateView,
|
|
):
|
|
model = models.Staff
|
|
form_class = forms.StaffForm
|
|
template_name = "users/user_form.html"
|
|
success_url = reverse_lazy("user_list")
|
|
success_message = _("User created successfully.")
|
|
|
|
def form_valid(self, form):
|
|
dealer = get_user_type(self.request)
|
|
form.instance.dealer = dealer
|
|
email = form.cleaned_data["email"]
|
|
password = "Tenhal@123"
|
|
user = User.objects.create_user(username=email, email=email, password=password)
|
|
|
|
staff = form.save(commit=False)
|
|
staff.user = user
|
|
staff.save()
|
|
|
|
return super().form_valid(form)
|
|
|
|
|
|
class UserUpdateView(
|
|
LoginRequiredMixin,
|
|
SuccessMessageMixin,
|
|
UpdateView,
|
|
):
|
|
model = models.Staff
|
|
form_class = forms.StaffForm
|
|
template_name = "users/user_form.html"
|
|
success_url = reverse_lazy("user_list")
|
|
success_message = _("User updated successfully.")
|
|
|
|
def get_form_kwargs(self):
|
|
kwargs = super().get_form_kwargs()
|
|
kwargs["instance"] = self.get_object() # Pass the Staff instance to the form
|
|
return kwargs
|
|
|
|
|
|
def UserDeleteview(request, pk):
|
|
user = get_object_or_404(models.Staff, pk=pk)
|
|
user.delete()
|
|
messages.success(request, _("User deleted successfully."))
|
|
return redirect("user_list")
|
|
|
|
|
|
# errors
|
|
def custom_page_not_found_view(request, exception):
|
|
return render(request, "errors/404.html", {})
|
|
|
|
|
|
def custom_error_view(request, exception=None):
|
|
return render(request, "errors/500.html", {})
|
|
|
|
|
|
def custom_permission_denied_view(request, exception=None):
|
|
return render(request, "errors/403.html", {})
|
|
|
|
|
|
def custom_bad_request_view(request, exception=None):
|
|
return render(request, "errors/400.html", {})
|
|
|
|
|
|
class OrganizationListView(LoginRequiredMixin, ListView):
|
|
model = CustomerModel
|
|
template_name = "organizations/organization_list.html"
|
|
context_object_name = "organizations"
|
|
paginate_by = 10
|
|
|
|
def get_queryset(self):
|
|
dealer = get_user_type(self.request)
|
|
return dealer.entity.get_customers().filter(additional_info__type="organization", active=True).all()
|
|
|
|
|
|
class OrganizationDetailView(DetailView):
|
|
model = models.Organization
|
|
template_name = "organizations/organization_detail.html"
|
|
context_object_name = "organization"
|
|
|
|
|
|
def OrganizationCreateView(request):
|
|
if request.method == "POST":
|
|
form = forms.OrganizationForm(request.POST)
|
|
|
|
#upload logo
|
|
image = request.FILES.get('logo')
|
|
file_name = default_storage.save('images/{}'.format(image.name), image)
|
|
file_url = default_storage.url(file_name)
|
|
|
|
organization_dict = {
|
|
x: request.POST[x] for x in request.POST if x != "csrfmiddlewaretoken"
|
|
}
|
|
dealer = get_user_type(request)
|
|
|
|
instance = dealer.entity.create_customer(
|
|
customer_model_kwargs={
|
|
"customer_name": organization_dict["name"],
|
|
"address_1": organization_dict["address"],
|
|
"phone": organization_dict["phone_number"],
|
|
"email": organization_dict["email"],
|
|
}
|
|
)
|
|
organization_dict["logo"] = file_url
|
|
organization_dict["pk"] = str(instance.pk)
|
|
instance.additional_info["organization_info"] = organization_dict
|
|
instance.additional_info["type"] = "organization"
|
|
instance.save()
|
|
messages.success(request, _("Organization created successfully."))
|
|
return redirect("organization_list")
|
|
else:
|
|
form = forms.OrganizationForm()
|
|
return render(request, "organizations/organization_form.html", {"form": form})
|
|
|
|
|
|
def OrganizationUpdateView(request, pk):
|
|
organization = get_object_or_404(CustomerModel, pk=pk)
|
|
if request.method == "POST":
|
|
form = forms.OrganizationForm(request.POST)
|
|
|
|
organization_dict = {
|
|
x: request.POST[x] for x in request.POST if x != "csrfmiddlewaretoken"
|
|
}
|
|
dealer = get_user_type(request)
|
|
|
|
instance = dealer.entity.get_customers().get(pk=organization.additional_info['organization_info']['pk'])
|
|
instance.customer_name = organization_dict["name"]
|
|
instance.address_1 = organization_dict["address"]
|
|
instance.phone = organization_dict["phone_number"]
|
|
instance.email = organization_dict["email"]
|
|
|
|
organization_dict["logo"] = organization.additional_info['organization_info']['logo']
|
|
organization_dict["pk"] = str(instance.pk)
|
|
instance.additional_info["organization_info"] = organization_dict
|
|
instance.additional_info["type"] = "organization"
|
|
instance.save()
|
|
messages.success(request, _("Organization created successfully."))
|
|
return redirect("organization_list")
|
|
else:
|
|
form = forms.OrganizationForm(initial=organization.additional_info["organization_info"] or {})
|
|
form.fields.pop('logo', None)
|
|
return render(request, "organizations/organization_form.html", {"form": form})
|
|
|
|
|
|
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"
|
|
paginate_by = 10
|
|
|
|
def get_queryset(self):
|
|
dealer = get_user_type(self.request)
|
|
return models.Representative.objects.filter(dealer=dealer).all()
|
|
|
|
|
|
class RepresentativeDetailView(DetailView):
|
|
model = models.Representative
|
|
template_name = "representatives/representative_detail.html"
|
|
context_object_name = "representative"
|
|
|
|
|
|
class RepresentativeCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
|
|
model = models.Representative
|
|
form_class = forms.RepresentativeForm
|
|
template_name = "representatives/representative_form.html"
|
|
success_url = reverse_lazy("representative_list")
|
|
success_message = "Representative created successfully."
|
|
|
|
def form_valid(self, form):
|
|
if form.is_valid():
|
|
form.instance.dealer = self.request.user.dealer
|
|
form.save()
|
|
return super().form_valid(form)
|
|
else:
|
|
return form.errors
|
|
|
|
|
|
class RepresentativeUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
|
|
model = models.Representative
|
|
form_class = forms.RepresentativeForm
|
|
template_name = "representatives/representative_form.html"
|
|
success_url = reverse_lazy("representative_list")
|
|
success_message = "Representative updated successfully."
|
|
|
|
|
|
class RepresentativeDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteView):
|
|
model = models.Representative
|
|
template_name = "representatives/representative_confirm_delete.html"
|
|
success_url = reverse_lazy("representative_list")
|
|
success_message = "Representative deleted successfully."
|
|
|
|
|
|
# def quotation_pdf_view(request, pk):
|
|
# # Get the quotation object
|
|
# quotation = models.SaleQuotation.objects.get(pk=pk)
|
|
#
|
|
# # Render the HTML template for the quotation page
|
|
# context = {
|
|
# "quotation": quotation,
|
|
# }
|
|
# context_result = get_calculations(quotation)
|
|
# context = context.update(context_result)
|
|
#
|
|
# html_content = render_to_string("sales/quotation_pdf.html", context)
|
|
#
|
|
# # Create a PDF file
|
|
#
|
|
# pdf_file = HTML(string=html_content).render()
|
|
#
|
|
# # Save the PDF file to a file
|
|
# with open("quotation.pdf", "wb") as f:
|
|
# f.write(pdf_file.write_pdf())
|
|
#
|
|
# # Return the PDF file as a response
|
|
# return HttpResponse(pdf_file, content_type="application/pdf")
|
|
|
|
|
|
# @login_required
|
|
# def download_quotation_pdf(request, quotation_id):
|
|
# try:
|
|
# # Retrieve the quotation object
|
|
# quotation = models.SaleQuotation.objects.get(id=quotation_id)
|
|
# cars = models.SaleQuotationCar.objects.get(id=quotation_id)
|
|
# print(cars)
|
|
# services = cars.finance.additional_services.all()
|
|
# print(services)
|
|
#
|
|
# # Create a response object
|
|
# response = HttpResponse(content_type="application/pdf")
|
|
# response["Content-Disposition"] = (
|
|
# f'attachment; filename="quotation_{quotation.id}.pdf"'
|
|
# )
|
|
#
|
|
# # Call the PDF generation function
|
|
# # generate_quotation_pdf(response, quotation, services)
|
|
#
|
|
# return response
|
|
# except models.SaleQuotation.DoesNotExist:
|
|
# return HttpResponse("Quotation not found", status=404)
|
|
|
|
|
|
# @login_required
|
|
# def invoice_detail(request, pk):
|
|
# quotation = get_object_or_404(models.SaleQuotation, pk=pk)
|
|
# dealer = request.user.dealer
|
|
# entity = dealer.entity
|
|
# customer = (
|
|
# entity.get_customers()
|
|
# .filter(customer_name=quotation.customer.get_full_name)
|
|
# .first()
|
|
# )
|
|
# invoice_model = entity.get_invoices()
|
|
#
|
|
# invoice = invoice_model.filter(
|
|
# customer=customer, date_draft=quotation.date_draft
|
|
# ).first()
|
|
# return redirect("quotation_detail", pk=pk)
|
|
|
|
|
|
# @login_required
|
|
# def payment_invoice(request, pk):
|
|
# quotation = get_object_or_404(models.SaleQuotation, pk=pk)
|
|
# dealer = request.user.dealer
|
|
# entity = dealer.entity
|
|
# customer = (
|
|
# entity.get_customers()
|
|
# .filter(customer_name=quotation.customer.get_full_name)
|
|
# .first()
|
|
# )
|
|
# invoice_model = entity.get_invoices()
|
|
# invoice = invoice_model.filter(
|
|
# customer=customer, date_draft=quotation.date_draft
|
|
# ).first()
|
|
#
|
|
# return redirect("quotation_detail", pk=pk)
|
|
|
|
|
|
# class PaymentCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
|
|
# model = models.Payment
|
|
# form_class = forms.PaymentForm
|
|
# template_name = "sales/payments/payment_form.html"
|
|
# success_url = reverse_lazy("quotation_list")
|
|
# success_message = "Payment created successfully."
|
|
|
|
# def form_valid(self, form):
|
|
# quotation = get_object_or_404(models.SaleQuotation, pk=self.kwargs["pk"])
|
|
# form.instance.quotation = quotation
|
|
# form.save()
|
|
# return super().form_valid(form)
|
|
# def get_context_data(self, **kwargs):
|
|
# context = super().get_context_data(**kwargs)
|
|
# context["quotation"] = get_object_or_404(models.SaleQuotation, pk=self.kwargs["pk"])
|
|
# return context
|
|
|
|
|
|
# def payment_create(request, pk):
|
|
# quotation = get_object_or_404(models.SaleQuotation, pk=pk)
|
|
# dealer = get_user_type(request)
|
|
# if request.method == "POST":
|
|
# form = forms.PaymentForm(request.POST)
|
|
# if form.is_valid():
|
|
# form.instance.quotation = quotation
|
|
# insatnce = form.save()
|
|
#
|
|
# dealer = dealer
|
|
# entity = dealer.entity
|
|
# customer = (
|
|
# entity.get_customers()
|
|
# .filter(customer_name=quotation.customer.get_full_name)
|
|
# .first()
|
|
# )
|
|
# coa_qs, coa_map = entity.get_all_coa_accounts()
|
|
# cash_account = coa_qs.first().get_coa_accounts().filter(name="Cash")
|
|
# recivable_account = (
|
|
# coa_qs.first().get_coa_accounts().filter(name="Accounts Receivable")
|
|
# )
|
|
# journal_entry = JournalEntryModel.objects.filter(
|
|
# pk=quotation.payment_id
|
|
# ).first()
|
|
# TransactionModel.objects.create(
|
|
# journal_entry=journal_entry,
|
|
# account=cash_account.first(), # Debit Cash
|
|
# amount=insatnce.amount, # Payment amount
|
|
# tx_type="debit",
|
|
# description="Payment Received",
|
|
# )
|
|
#
|
|
# TransactionModel.objects.create(
|
|
# journal_entry=journal_entry,
|
|
# account=recivable_account.first(), # Credit Accounts Receivable
|
|
# amount=insatnce.amount, # Payment amount
|
|
# tx_type="credit",
|
|
# description="Payment Received",
|
|
# )
|
|
# journal_entry.posted = True
|
|
# quotation.posted = True
|
|
# quotation.save()
|
|
# journal_entry.save()
|
|
#
|
|
# invoice_model = (
|
|
# entity.get_invoices()
|
|
# .filter(date_approved=quotation.date_approved)
|
|
# .first()
|
|
# )
|
|
#
|
|
# invoice_model.mark_as_paid(
|
|
# entity_slug=entity.slug, user_model=request.user.dealer
|
|
# )
|
|
# date = timezone.now()
|
|
# invoice_model.date_paid = date
|
|
# quotation.date_paid = date
|
|
# invoice_model.save()
|
|
# quotation.status = "Paid"
|
|
# quotation.save()
|
|
#
|
|
# messages.success(request, "Payment created successfully.")
|
|
# return redirect("quotation_detail", pk=pk)
|
|
# else:
|
|
# form = forms.PaymentForm()
|
|
# return render(
|
|
# request,
|
|
# "sales/payments/payment_create.html",
|
|
# {"quotation": quotation, "form": form},
|
|
# )
|
|
|
|
|
|
# Ledger
|
|
|
|
|
|
# BANK ACCOUNT
|
|
class BankAccountListView(LoginRequiredMixin, ListView):
|
|
model = BankAccountModel
|
|
template_name = "ledger/bank_accounts/bank_account_list.html"
|
|
context_object_name = "bank_accounts"
|
|
paginate_by = 10
|
|
|
|
def get_queryset(self):
|
|
dealer = get_user_type(self.request)
|
|
return BankAccountModel.objects.filter(entity_model=dealer.entity)
|
|
|
|
|
|
class BankAccountCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
|
|
model = BankAccountModel
|
|
form_class = BankAccountCreateForm
|
|
template_name = "ledger/bank_accounts/bank_account_form.html"
|
|
success_url = reverse_lazy("bank_account_list")
|
|
success_message = "Bank account created successfully."
|
|
|
|
def form_valid(self, form):
|
|
dealer = get_user_type(self.request)
|
|
form.instance.entity_model = dealer.entity
|
|
return super().form_valid(form)
|
|
|
|
def get_form_kwargs(self):
|
|
dealer = get_user_type(self.request)
|
|
entity = dealer.entity
|
|
kwargs = super().get_form_kwargs()
|
|
kwargs["entity_slug"] = entity.slug
|
|
kwargs["user_model"] = entity.admin
|
|
return kwargs
|
|
|
|
|
|
class BankAccountDetailView(LoginRequiredMixin, DetailView):
|
|
model = BankAccountModel
|
|
template_name = "ledger/bank_accounts/bank_account_detail.html"
|
|
context_object_name = "bank_account"
|
|
|
|
|
|
class BankAccountUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
|
|
model = BankAccountModel
|
|
form_class = BankAccountUpdateForm
|
|
template_name = "ledger/bank_accounts/bank_account_form.html"
|
|
success_url = reverse_lazy("bank_account_list")
|
|
success_message = "Bank account updated successfully."
|
|
|
|
def get_form_kwargs(self):
|
|
dealer = get_user_type(self.request)
|
|
entity = dealer.entity
|
|
kwargs = super().get_form_kwargs()
|
|
kwargs["entity_slug"] = entity.slug # Get entity_slug from URL
|
|
kwargs["user_model"] = entity.admin # Get user_model from the request
|
|
return kwargs
|
|
|
|
|
|
@login_required
|
|
def bank_account_delete(request, pk):
|
|
bank_account = get_object_or_404(BankAccountModel, pk=pk)
|
|
if request.method == "POST":
|
|
bank_account.delete()
|
|
messages.success(request, "Bank account deleted successfully.")
|
|
return redirect("bank_account_list")
|
|
return render(
|
|
request,
|
|
"ledger/bank_accounts/bank_account_delete.html",
|
|
{"bank_account": bank_account},
|
|
)
|
|
|
|
|
|
# Accounts
|
|
|
|
|
|
class AccountListView(LoginRequiredMixin, ListView):
|
|
model = AccountModel
|
|
template_name = "ledger/coa_accounts/account_list.html"
|
|
context_object_name = "accounts"
|
|
paginate_by = 10
|
|
|
|
def get_queryset(self):
|
|
dealer = get_user_type(self.request)
|
|
entity = dealer.entity
|
|
return entity.get_all_accounts()
|
|
|
|
|
|
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):
|
|
dealer = get_user_type(self.request)
|
|
form.instance.entity_model = dealer.entity
|
|
form.instance.depth = 0
|
|
return super().form_valid(form)
|
|
|
|
def get_form_kwargs(self):
|
|
dealer = get_user_type(self.request)
|
|
entity = dealer.entity
|
|
kwargs = super().get_form_kwargs()
|
|
|
|
kwargs["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"
|
|
slug_field = "uuid"
|
|
DEFAULT_TXS_DAYS = 30
|
|
extra_context = {
|
|
"DEFAULT_TXS_DAYS": DEFAULT_TXS_DAYS,
|
|
"header_subtitle_icon": "ic:round-account-tree",
|
|
}
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
account_model: AccountModel = context["object"]
|
|
context["header_title"] = f"Account {account_model.code} - {account_model.name}"
|
|
context["page_title"] = f"Account {account_model.code} - {account_model.name}"
|
|
context["total_debits"] = sum(
|
|
x.amount for x in account_model.transactionmodel_set.filter(tx_type="debit")
|
|
)
|
|
context["total_credits"] = sum(
|
|
x.amount
|
|
for x in account_model.transactionmodel_set.filter(tx_type="credit")
|
|
)
|
|
txs_qs = (
|
|
account_model.transactionmodel_set.all()
|
|
.posted()
|
|
.order_by("journal_entry__timestamp")
|
|
.select_related(
|
|
"journal_entry",
|
|
"journal_entry__entity_unit",
|
|
"journal_entry__ledger__billmodel",
|
|
"journal_entry__ledger__invoicemodel",
|
|
)
|
|
)
|
|
|
|
return context
|
|
|
|
|
|
class AccountUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
|
|
model = AccountModel
|
|
form_class = AccountModelUpdateForm
|
|
template_name = "ledger/coa_accounts/account_form.html"
|
|
success_url = reverse_lazy("account_list")
|
|
success_message = "Account updated successfully."
|
|
|
|
def get_form(self, form_class=None):
|
|
form = super().get_form(form_class)
|
|
form.fields["_ref_node_id"].widget = HiddenInput()
|
|
form.fields["_position"].widget = HiddenInput()
|
|
return form
|
|
|
|
|
|
@login_required
|
|
def account_delete(request, pk):
|
|
account = get_object_or_404(AccountModel, pk=pk)
|
|
if request.method == "POST":
|
|
account.delete()
|
|
messages.success(request, "Account deleted successfully.")
|
|
return redirect("account_list")
|
|
return render(
|
|
request, "ledger/coa_accounts/account_delete.html", {"account": account}
|
|
)
|
|
|
|
|
|
# Estimates
|
|
class EstimateListView(LoginRequiredMixin, ListView):
|
|
model = EstimateModel
|
|
template_name = "sales/estimates/estimate_list.html"
|
|
context_object_name = "estimates"
|
|
paginate_by = 10
|
|
|
|
def get_queryset(self):
|
|
dealer = get_user_type(self.request)
|
|
entity = dealer.entity
|
|
return entity.get_estimates()
|
|
|
|
|
|
# class EstimateCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
|
|
# model = EstimateModel
|
|
# form_class = EstimateModelCreateForm
|
|
# template_name = "sales/estimates/estimate_form.html"
|
|
# success_url = reverse_lazy("estimate_list")
|
|
# success_message = "Estimate created successfully."
|
|
|
|
# def get_form_kwargs(self):
|
|
# """
|
|
# Override this method to pass additional keyword arguments to the form.
|
|
# """
|
|
# entity = self.request.user.dealer.entity
|
|
# kwargs = super().get_form_kwargs()
|
|
# kwargs['entity_slug'] = entity.slug
|
|
# kwargs['user_model'] = entity.admin
|
|
# return kwargs
|
|
|
|
# def get_context_data(self, **kwargs):
|
|
# entity = self.request.user.dealer.entity
|
|
# kwargs['items'] = entity.get_items_all()
|
|
# return super().get_context_data(**kwargs)
|
|
# def get_customer_queryset(self):
|
|
# entity = self.request.user.dealer.entity
|
|
# return entity.get_customer_queryset()
|
|
|
|
# def form_valid(self, form):
|
|
# form.instance.entity = self.request.user.dealer.entity
|
|
# return super().form_valid(form)
|
|
|
|
|
|
# @csrf_exempt
|
|
@login_required
|
|
def create_estimate(request):
|
|
dealer = get_user_type(request)
|
|
entity = dealer.entity
|
|
|
|
if request.method == "POST":
|
|
# try:
|
|
data = json.loads(request.body)
|
|
title = data.get("title")
|
|
customer_id = data.get("customer")
|
|
terms = data.get("terms")
|
|
customer = entity.get_customers().filter(pk=customer_id).first()
|
|
|
|
items = data.get("item", [])
|
|
quantities = data.get("quantity", [])
|
|
|
|
if not all([items, quantities]):
|
|
return JsonResponse(
|
|
{"status": "error", "message": "Items and Quantities are required"},
|
|
status=400,
|
|
)
|
|
if isinstance(quantities, list):
|
|
if "0" in quantities:
|
|
return JsonResponse(
|
|
{"status": "error", "message": "Quantity must be greater than zero"}
|
|
)
|
|
else:
|
|
if int(quantities) <= 0:
|
|
return JsonResponse(
|
|
{"status": "error", "message": "Quantity must be greater than zero"}
|
|
)
|
|
|
|
estimate = entity.create_estimate(
|
|
estimate_title=title, customer_model=customer, contract_terms=terms
|
|
)
|
|
if isinstance(items, list):
|
|
item_quantity_map = {}
|
|
for item, quantity in zip(items, quantities):
|
|
if item in item_quantity_map:
|
|
item_quantity_map[item] += int(quantity)
|
|
else:
|
|
item_quantity_map[item] = int(quantity)
|
|
item_list = list(item_quantity_map.keys())
|
|
quantity_list = list(item_quantity_map.values())
|
|
|
|
items_list = [
|
|
{"item_id": item_list[i], "quantity": quantity_list[i]}
|
|
for i in range(len(item_list))
|
|
]
|
|
items_txs = []
|
|
for item in items_list:
|
|
item_instance = ItemModel.objects.get(pk=item.get("item_id"))
|
|
car_instance = models.Car.objects.get(vin=item_instance.name)
|
|
items_txs.append(
|
|
{
|
|
"item_number": item_instance.item_number,
|
|
"quantity": Decimal(item.get("quantity")),
|
|
"unit_cost": car_instance.finances.selling_price,
|
|
"unit_revenue": car_instance.finances.selling_price,
|
|
"total_amount": (car_instance.finances.total_vat)
|
|
* int(item.get("quantity")),
|
|
}
|
|
)
|
|
|
|
estimate_itemtxs = {
|
|
item.get("item_number"): {
|
|
"unit_cost": item.get("unit_cost"),
|
|
"unit_revenue": item.get("unit_revenue"),
|
|
"quantity": item.get("quantity"),
|
|
"total_amount": item.get("total_amount"),
|
|
}
|
|
for item in items_txs
|
|
}
|
|
else:
|
|
item = entity.get_items_all().filter(pk=items).first()
|
|
instance = models.Car.objects.get(vin=item.name)
|
|
estimate_itemtxs = {
|
|
item.item_number: {
|
|
"unit_cost": instance.finances.cost_price,
|
|
"unit_revenue": instance.finances.selling_price,
|
|
"quantity": Decimal(quantities),
|
|
"total_amount": instance.finances.total_vat * int(quantities),
|
|
}
|
|
}
|
|
|
|
estimate.migrate_itemtxs(
|
|
itemtxs=estimate_itemtxs,
|
|
commit=True,
|
|
operation=EstimateModel.ITEMIZE_APPEND,
|
|
)
|
|
|
|
if isinstance(items, list):
|
|
for item in items:
|
|
item_instance = ItemModel.objects.get(pk=item)
|
|
instance = models.Car.objects.get(vin=item_instance.name)
|
|
reserve_car(instance, request)
|
|
|
|
else:
|
|
item_instance = ItemModel.objects.get(pk=items)
|
|
instance = models.Car.objects.get(vin=item_instance.name)
|
|
response = reserve_car(instance, request)
|
|
|
|
url = reverse("estimate_detail", kwargs={"pk": estimate.pk})
|
|
return JsonResponse(
|
|
{
|
|
"status": "success",
|
|
"message": "Estimate created successfully!",
|
|
"url": f"{url}",
|
|
}
|
|
)
|
|
|
|
form = forms.EstimateModelCreateForm(entity_slug=entity.slug, user_model=entity.admin)
|
|
form.fields["customer"].queryset = entity.get_customers().filter(active=True)
|
|
car_list = models.Car.objects.filter(
|
|
dealer=dealer, finances__selling_price__gt=0
|
|
).exclude(status="reserved")
|
|
context = {
|
|
"form": form,
|
|
"items": [
|
|
{
|
|
"car": x,
|
|
"product": entity.get_items_all()
|
|
.filter(item_role=ItemModel.ITEM_ROLE_PRODUCT, name=x.vin)
|
|
.first(),
|
|
}
|
|
for x in car_list
|
|
],
|
|
}
|
|
|
|
return render(request, "sales/estimates/estimate_form.html", context)
|
|
|
|
|
|
class EstimateDetailView(LoginRequiredMixin, DetailView):
|
|
model = EstimateModel
|
|
template_name = "sales/estimates/estimate_detail.html"
|
|
context_object_name = "estimate"
|
|
|
|
def get_context_data(self, **kwargs):
|
|
estimate = kwargs.get("object")
|
|
if estimate.get_itemtxs_data():
|
|
calculator = CarFinanceCalculator(estimate)
|
|
finance_data = calculator.get_finance_data()
|
|
kwargs['data'] = finance_data
|
|
print(finance_data)
|
|
kwargs["invoice"] = (
|
|
InvoiceModel.objects.all().filter(ce_model=estimate).first()
|
|
)
|
|
return super().get_context_data(**kwargs)
|
|
|
|
|
|
def create_sale_order(request, pk):
|
|
estimate = get_object_or_404(EstimateModel, pk=pk)
|
|
items = estimate.get_itemtxs_data()[0].all()
|
|
|
|
if request.method == "POST":
|
|
form = forms.SaleOrderForm(request.POST)
|
|
if form.is_valid():
|
|
form.save()
|
|
if not estimate.is_approved():
|
|
estimate.mark_as_approved()
|
|
estimate.save()
|
|
messages.success(request, "Sale Order created successfully")
|
|
return redirect("estimate_detail", pk=pk)
|
|
|
|
form = forms.SaleOrderForm()
|
|
form.fields["estimate"].queryset = EstimateModel.objects.filter(pk=pk)
|
|
form.initial['estimate'] = estimate
|
|
# data = get_car_finance_data(estimate)
|
|
calculator = CarFinanceCalculator(estimate)
|
|
finance_data = calculator.get_finance_data()
|
|
return render(
|
|
request,
|
|
"sales/estimates/sale_order_form.html",
|
|
{"form": form, "estimate": estimate, "items": items, "data": finance_data},
|
|
)
|
|
|
|
|
|
def preview_sale_order(request, pk):
|
|
estimate = get_object_or_404(EstimateModel, pk=pk)
|
|
data = get_car_finance_data(estimate)
|
|
return render(request, 'sales/estimates/sale_order_preview.html',
|
|
{'order': estimate.sale_orders.first(), "data": data, "estimate": estimate})
|
|
|
|
|
|
class PaymentRequest(LoginRequiredMixin, DetailView):
|
|
model = EstimateModel
|
|
template_name = "sales/estimates/payment_request_detail.html"
|
|
context_object_name = "estimate"
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context["cars"] = [
|
|
models.Car.objects.get(vin=car.item_model.name)
|
|
for car in context["estimate"].get_itemtxs_data()[0].all()
|
|
]
|
|
return context
|
|
|
|
|
|
class EstimatePreviewView(LoginRequiredMixin, DetailView):
|
|
model = EstimateModel
|
|
context_object_name = "estimate"
|
|
template_name = "sales/estimates/estimate_preview.html"
|
|
|
|
def get_context_data(self, **kwargs):
|
|
estimate = kwargs.get("object")
|
|
if estimate.get_itemtxs_data():
|
|
data = get_financial_values(estimate)
|
|
|
|
kwargs["vat_amount"] = data["vat_amount"]
|
|
kwargs["total"] = data["grand_total"]
|
|
kwargs["discount_amount"] = data["discount_amount"]
|
|
kwargs["vat"] = data["vat"]
|
|
# kwargs["car_and_item_info"] = data["car_and_item_info"]
|
|
kwargs["additional_services"] = data["additional_services"]
|
|
return super().get_context_data(**kwargs)
|
|
|
|
|
|
@login_required
|
|
def estimate_mark_as(request, pk):
|
|
estimate = get_object_or_404(EstimateModel, pk=pk)
|
|
entity = estimate.entity
|
|
mark = request.GET.get("mark")
|
|
if mark:
|
|
if mark == "review":
|
|
if not estimate.can_review():
|
|
messages.error(request, "Estimate is not ready for review")
|
|
return redirect("estimate_detail", pk=estimate.pk)
|
|
estimate.mark_as_review()
|
|
|
|
elif mark == "approved":
|
|
if not estimate.can_approve():
|
|
messages.error(request, "Estimate is not ready for approval")
|
|
return redirect("estimate_detail", pk=estimate.pk)
|
|
estimate.mark_as_approved()
|
|
messages.success(request, "Estimate approved successfully.")
|
|
elif mark == "rejected":
|
|
if not estimate.can_cancel():
|
|
messages.error(request, "Estimate is not ready for rejection")
|
|
return redirect("estimate_detail", pk=estimate.pk)
|
|
estimate.mark_as_canceled()
|
|
messages.success(request, "Estimate canceled successfully.")
|
|
elif mark == "completed":
|
|
if not estimate.can_complete():
|
|
messages.error(request, "Estimate is not ready for completion")
|
|
return redirect("estimate_detail", pk=estimate.pk)
|
|
estimate.save()
|
|
messages.success(request, "Estimate marked as " + mark.upper())
|
|
|
|
return redirect("estimate_detail", pk=estimate.pk)
|
|
|
|
|
|
# Invoice
|
|
class InvoiceListView(LoginRequiredMixin, ListView):
|
|
model = InvoiceModel
|
|
template_name = "sales/invoices/invoice_list.html"
|
|
context_object_name = "invoices"
|
|
paginate_by = 20
|
|
|
|
def get_queryset(self):
|
|
dealer = get_user_type(self.request)
|
|
entity = dealer.entity
|
|
return entity.get_invoices()
|
|
|
|
|
|
class InvoiceDetailView(LoginRequiredMixin, DetailView):
|
|
model = InvoiceModel
|
|
template_name = "sales/invoices/invoice_detail.html"
|
|
context_object_name = "invoice"
|
|
|
|
def get_context_data(self, **kwargs):
|
|
invoice = kwargs.get("object")
|
|
|
|
if invoice.get_itemtxs_data():
|
|
calculator = CarFinanceCalculator(invoice)
|
|
finance_data = calculator.get_finance_data()
|
|
print((finance_data["total_vat_amount"] + finance_data["total_price"]) == finance_data["grand_total"])
|
|
kwargs["data"] = finance_data
|
|
kwargs["payments"] = JournalEntryModel.objects.filter(
|
|
ledger=invoice.ledger
|
|
).all()
|
|
|
|
return super().get_context_data(**kwargs)
|
|
|
|
|
|
class DraftInvoiceModelUpdateFormView(LoginRequiredMixin, UpdateView):
|
|
model = InvoiceModel
|
|
form_class = DraftInvoiceModelUpdateForm
|
|
template_name = "sales/invoices/draft_invoice_update.html"
|
|
success_url = reverse_lazy("invoice_list")
|
|
|
|
def get_form_kwargs(self):
|
|
kwargs = super().get_form_kwargs()
|
|
dealer = get_user_type(self.request)
|
|
kwargs["entity_slug"] = dealer.entity
|
|
kwargs["user_model"] = dealer.entity.admin
|
|
return kwargs
|
|
|
|
|
|
class ApprovedInvoiceModelUpdateFormView(LoginRequiredMixin, UpdateView):
|
|
model = InvoiceModel
|
|
form_class = ApprovedInvoiceModelUpdateForm
|
|
template_name = "sales/invoices/approved_invoice_update.html"
|
|
success_url = reverse_lazy("invoice_list")
|
|
|
|
def get_form_kwargs(self):
|
|
kwargs = super().get_form_kwargs()
|
|
dealer = get_user_type(self.request)
|
|
kwargs["entity_slug"] = dealer.entity
|
|
kwargs["user_model"] = dealer.entity.admin
|
|
return kwargs
|
|
|
|
def get_success_url(self):
|
|
return reverse_lazy("invoice_detail", kwargs={"pk": self.object.pk})
|
|
|
|
|
|
class PaidInvoiceModelUpdateFormView(LoginRequiredMixin, UpdateView):
|
|
model = InvoiceModel
|
|
form_class = PaidInvoiceModelUpdateForm
|
|
template_name = "sales/invoices/paid_invoice_update.html"
|
|
success_url = reverse_lazy("invoice_list")
|
|
|
|
def get_form_kwargs(self):
|
|
kwargs = super().get_form_kwargs()
|
|
dealer = get_user_type(self.request)
|
|
kwargs["entity_slug"] = dealer.entity
|
|
kwargs["user_model"] = dealer.entity.admin
|
|
return kwargs
|
|
|
|
def get_success_url(self):
|
|
return reverse_lazy("invoice_detail", kwargs={"pk": self.object.pk})
|
|
|
|
def form_valid(self, form):
|
|
invoice = form.save()
|
|
|
|
if invoice.get_amount_open() > 0:
|
|
messages.error(self.request, "Invoice is not fully paid")
|
|
return redirect("invoice_detail", pk=invoice.pk)
|
|
else:
|
|
invoice.post_ledger()
|
|
invoice.save()
|
|
return super().form_valid(form)
|
|
|
|
|
|
@login_required
|
|
def invoice_mark_as(request, pk):
|
|
invoice = get_object_or_404(InvoiceModel, pk=pk)
|
|
dealer = get_user_type(request)
|
|
mark = request.GET.get("mark")
|
|
if mark and mark == "accept":
|
|
if not invoice.can_approve():
|
|
messages.error(request, "invoice is not ready for approval")
|
|
return redirect("invoice_detail", pk=invoice.pk)
|
|
invoice.mark_as_approved(
|
|
entity_slug=dealer.entity.slug, user_model=dealer.entity.admin
|
|
)
|
|
invoice.save()
|
|
return redirect("invoice_detail", pk=invoice.pk)
|
|
|
|
|
|
def invoice_create(request, pk):
|
|
estimate = get_object_or_404(EstimateModel, pk=pk)
|
|
dealer = get_user_type(request)
|
|
entity = dealer.entity
|
|
|
|
if request.method == "POST":
|
|
form = forms.InvoiceModelCreateForm(
|
|
request.POST, entity_slug=entity.slug, user_model=entity.admin
|
|
)
|
|
if form.is_valid():
|
|
invoice = form.save(commit=False)
|
|
ledger = entity.create_ledger(name=str(invoice.pk))
|
|
invoice.ledgar = ledger
|
|
ledger.invoicemodel = invoice
|
|
ledger.save()
|
|
invoice.save()
|
|
|
|
# unit_items = estimate.get_itemtxs_data()[0]
|
|
# vat = models.VatRate.objects.filter(is_active=True).first()
|
|
calculator = CarFinanceCalculator(estimate)
|
|
finance_data = calculator.get_finance_data()
|
|
|
|
# total = 0
|
|
# discount_amount = 0
|
|
|
|
# itemtxs = []
|
|
# for item in unit_items:
|
|
# car = models.Car.objects.get(vin=item.item_model.name)
|
|
|
|
# total = Decimal(car.finances.total) * Decimal(item.ce_quantity)
|
|
# discount_amount = car.finances.discount_amount
|
|
|
|
# grand_total = Decimal(total) - Decimal(discount_amount)
|
|
# vat_amount = round(Decimal(grand_total) * Decimal(vat.rate), 2)
|
|
# grand_total += Decimal(vat_amount)
|
|
# unit_cost = grand_total / Decimal(item.ce_quantity)
|
|
# itemtxs.append(
|
|
# {
|
|
# "item_number": item.item_model.item_number,
|
|
# "unit_cost": unit_cost,
|
|
# "unit_revenue": unit_cost,
|
|
# "quantity": item.ce_quantity,
|
|
# "total_amount": grand_total,
|
|
# }
|
|
# )
|
|
invoice_itemtxs = {
|
|
i.get("item_number"): {
|
|
"unit_cost": i.get("total_vat"),
|
|
"quantity": i.get("quantity"),
|
|
"total_amount": i.get("total_vat"),
|
|
}
|
|
for i in finance_data.get("cars")
|
|
}
|
|
|
|
invoice_itemtxs = invoice.migrate_itemtxs(
|
|
itemtxs=invoice_itemtxs,
|
|
commit=True,
|
|
operation=InvoiceModel.ITEMIZE_APPEND,
|
|
)
|
|
invoice.bind_estimate(estimate)
|
|
invoice.mark_as_review()
|
|
estimate.mark_as_completed()
|
|
estimate.save()
|
|
invoice.save()
|
|
messages.success(request, "Invoice created successfully!")
|
|
return redirect("invoice_detail", pk=invoice.pk)
|
|
form = forms.InvoiceModelCreateForm(
|
|
entity_slug=entity.slug, user_model=entity.admin
|
|
)
|
|
|
|
form.initial.update(
|
|
{
|
|
"customer": estimate.customer,
|
|
"cash_account": entity.get_default_coa_accounts().get(name="Cash"),
|
|
"prepaid_account": entity.get_default_coa_accounts().get(
|
|
name="Accounts Receivable"
|
|
),
|
|
"unearned_account": entity.get_default_coa_accounts().get(
|
|
name="Deferred Revenue"
|
|
),
|
|
}
|
|
)
|
|
|
|
context = {
|
|
"form": form,
|
|
"estimate": estimate,
|
|
}
|
|
return render(request, "sales/invoices/invoice_create.html", context)
|
|
|
|
|
|
class InvoicePreviewView(LoginRequiredMixin, DetailView):
|
|
model = InvoiceModel
|
|
context_object_name = "invoice"
|
|
template_name = "sales/invoices/invoice_preview.html"
|
|
|
|
def get_context_data(self, **kwargs):
|
|
invoice = kwargs.get("object")
|
|
if invoice.get_itemtxs_data():
|
|
# data = get_financial_values(invoice)
|
|
calculator = CarFinanceCalculator(invoice)
|
|
finance_data = calculator.get_finance_data()
|
|
kwargs["data"] = finance_data
|
|
# kwargs["vat_amount"] = data["vat_amount"]
|
|
# kwargs["total"] = data["grand_total"]
|
|
# kwargs["discount_amount"] = data["discount_amount"]
|
|
# kwargs["vat"] = data["vat"]
|
|
# kwargs["car_and_item_info"] = data["car_and_item_info"]
|
|
# kwargs["additional_services"] = data["additional_services"]
|
|
return super().get_context_data(**kwargs)
|
|
|
|
|
|
# payments
|
|
|
|
|
|
def PaymentCreateView(request, pk):
|
|
invoice = InvoiceModel.objects.filter(pk=pk).first()
|
|
bill = BillModel.objects.filter(pk=pk).first()
|
|
model = invoice if invoice else bill
|
|
dealer = get_user_type(request)
|
|
entity = dealer.entity
|
|
form = forms.PaymentForm()
|
|
if request.method == "POST":
|
|
form = forms.PaymentForm(request.POST)
|
|
if form.is_valid():
|
|
amount = form.cleaned_data.get("amount")
|
|
invoice = form.cleaned_data.get("invoice")
|
|
bill = form.cleaned_data.get("bill")
|
|
payment_method = form.cleaned_data.get("payment_method")
|
|
redirect_url = "invoice_detail" if invoice else "bill_detail"
|
|
model = invoice if invoice else bill
|
|
|
|
if not model.is_approved():
|
|
model.mark_as_approved(user_model=entity.admin)
|
|
try:
|
|
if invoice:
|
|
set_invoice_payment(dealer, entity, invoice, amount, payment_method)
|
|
elif bill:
|
|
set_bill_payment(dealer, entity, bill, amount, payment_method)
|
|
messages.success(request, "Payment created successfully!")
|
|
return redirect(redirect_url, pk=model.pk)
|
|
except Exception as e:
|
|
messages.error(request, f"Error creating payment: {str(e)}")
|
|
else:
|
|
messages.error(request, f"Invalid form data: {str(form.errors)}")
|
|
# return redirect(redirect_url, pk=model.pk)
|
|
form = forms.PaymentForm()
|
|
if model:
|
|
form.initial["amount"] = model.amount_due - model.amount_paid
|
|
if isinstance(model, InvoiceModel):
|
|
form.initial["invoice"] = model
|
|
form.fields["bill"].widget = HiddenInput()
|
|
elif isinstance(model, BillModel):
|
|
form.initial["bill"] = model
|
|
form.fields["invoice"].widget = HiddenInput()
|
|
return render(
|
|
request, "sales/payments/payment_form.html", {"model": model, "form": form}
|
|
)
|
|
|
|
|
|
def PaymentListView(request):
|
|
dealer = get_user_type(request)
|
|
|
|
entity = dealer.entity
|
|
journals = JournalEntryModel.objects.filter(ledger__entity=entity).all()
|
|
return render(request, "sales/payments/payment_list.html", {"journals": journals})
|
|
|
|
|
|
def PaymentDetailView(request, pk):
|
|
journal = JournalEntryModel.objects.filter(pk=pk).first()
|
|
transactions = (
|
|
TransactionModel.objects.filter(journal_entry=journal)
|
|
.order_by("account__code")
|
|
.all()
|
|
)
|
|
return render(
|
|
request,
|
|
"sales/payments/payment_details.html",
|
|
{"journal": journal, "transactions": transactions},
|
|
)
|
|
|
|
|
|
def payment_mark_as_paid(request, pk):
|
|
invoice = get_object_or_404(InvoiceModel, pk=pk)
|
|
if request.method == "POST":
|
|
try:
|
|
if invoice.amount_due == invoice.amount_paid:
|
|
if not invoice.is_paid() and invoice.can_pay():
|
|
invoice.mark_as_paid(
|
|
entity_slug=invoice.ledger.entity.slug,
|
|
user_model=invoice.ledger.entity.admin,
|
|
)
|
|
invoice.save()
|
|
|
|
invoice.ledger.lock_journal_entries()
|
|
invoice.ledger.post_journal_entries()
|
|
|
|
invoice.ledger.post()
|
|
invoice.ledger.save()
|
|
messages.success(request, "Payment created successfully!")
|
|
else:
|
|
messages.error(
|
|
request,
|
|
"Invoice is not fully paid. Payment cannot be marked as paid.",
|
|
)
|
|
except Exception as e:
|
|
messages.error(request, f"Error: {str(e)}")
|
|
return redirect("invoice_detail", pk=invoice.pk)
|
|
|
|
|
|
# activity log
|
|
class UserActivityLogListView(ListView):
|
|
model = models.UserActivityLog
|
|
template_name = "dealers/activity_log.html"
|
|
context_object_name = "logs"
|
|
paginate_by = 20
|
|
|
|
def get_queryset(self):
|
|
queryset = super().get_queryset()
|
|
if "user" in self.request.GET:
|
|
queryset = queryset.filter(user__email=self.request.GET["user"])
|
|
return queryset
|
|
|
|
|
|
# CRM RELATED VIEWS
|
|
class LeadListView(ListView):
|
|
model = models.Lead
|
|
template_name = "crm/leads/lead_list.html"
|
|
context_object_name = "leads"
|
|
paginate_by = 10
|
|
|
|
def get_queryset(self):
|
|
dealer = get_user_type(self.request)
|
|
return models.Lead.objects.filter(dealer=dealer).all()
|
|
|
|
|
|
class LeadDetailView(DetailView):
|
|
model = models.Lead
|
|
template_name = "crm/leads/lead_detail.html"
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context["notes"] = models.Notes.objects.filter(
|
|
content_type__model="lead", object_id=self.object.id
|
|
)
|
|
context["activities"] = models.Activity.objects.filter(
|
|
content_type__model="lead", object_id=self.object.id
|
|
)
|
|
context["status_history"] = models.LeadStatusHistory.objects.filter(
|
|
lead=self.object
|
|
)
|
|
return context
|
|
|
|
|
|
class LeadCreateView(CreateView, SuccessMessageMixin, LoginRequiredMixin):
|
|
model = models.Lead
|
|
form_class = forms.LeadForm
|
|
template_name = "crm/leads/lead_form.html"
|
|
# success_message = "Lead created successfully!"
|
|
success_url = reverse_lazy("lead_list")
|
|
|
|
def form_valid(self, form):
|
|
print("Form data:", form.cleaned_data) # Debug form data
|
|
dealer = get_user_type(self.request)
|
|
form.instance.dealer = dealer
|
|
return super().form_valid(form)
|
|
|
|
|
|
def get_car_models(request):
|
|
make_id = request.GET.get("id_car_make")
|
|
if make_id:
|
|
car_models = models.CarModel.objects.filter(id_car_make=make_id).values(
|
|
"id_car_model", "name", "arabic_name"
|
|
)
|
|
return JsonResponse(list(car_models), safe=False)
|
|
return JsonResponse([], safe=False)
|
|
|
|
|
|
class LeadUpdateView(UpdateView):
|
|
model = models.Lead
|
|
form_class = forms.LeadForm
|
|
template_name = "crm/leads/lead_form.html"
|
|
success_url = reverse_lazy("lead_list")
|
|
|
|
|
|
class LeadDeleteView(DeleteView):
|
|
model = models.Lead
|
|
template_name = "crm/leads/lead_confirm_delete.html"
|
|
success_url = reverse_lazy("lead_list")
|
|
|
|
|
|
def add_note_to_lead(request, pk):
|
|
lead = get_object_or_404(models.Lead, pk=pk)
|
|
if request.method == "POST":
|
|
form = forms.NoteForm(request.POST)
|
|
if form.is_valid():
|
|
note = form.save(commit=False)
|
|
note.content_object = lead
|
|
|
|
note.created_by = request.user
|
|
note.save()
|
|
return redirect("lead_detail", pk=pk)
|
|
else:
|
|
form = forms.NoteForm()
|
|
return render(request, "crm/add_note.html", {"form": form, "lead": lead})
|
|
|
|
|
|
def add_activity_to_lead(request, pk):
|
|
lead = get_object_or_404(models.Lead, pk=pk)
|
|
if request.method == "POST":
|
|
form = forms.ActivityForm(request.POST)
|
|
if form.is_valid():
|
|
activity = form.save(commit=False)
|
|
activity.content_object = lead
|
|
activity.created_by = request.user
|
|
activity.save()
|
|
return redirect("lead_detail", pk=pk)
|
|
else:
|
|
form = forms.ActivityForm()
|
|
return render(request, "crm/add_activity.html", {"form": form, "lead": lead})
|
|
|
|
|
|
class OpportunityCreateView(CreateView):
|
|
model = models.Opportunity
|
|
form_class = forms.OpportunityForm
|
|
template_name = "crm/opportunities/opportunity_form.html"
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
dealer = get_user_type(self.request)
|
|
context["customer"] = models.Customer.objects.filter(dealer=dealer)
|
|
context["cars"] = models.Car.objects.filter(dealer=dealer)
|
|
return context
|
|
|
|
def form_valid(self, form):
|
|
dealer = get_user_type(self.request)
|
|
form.instance.dealer = dealer
|
|
# staff = dealer.staff
|
|
print(dealer)
|
|
# print(staff)
|
|
# form.instance.staff = staff
|
|
return super().form_valid(form)
|
|
|
|
def get_success_url(self):
|
|
return reverse_lazy("opportunity_detail", kwargs={"pk": self.object.pk})
|
|
|
|
|
|
class OpportunityUpdateView(UpdateView):
|
|
model = models.Opportunity
|
|
form_class = forms.OpportunityForm
|
|
template_name = "crm/opportunities/opportunity_form.html"
|
|
|
|
def get_success_url(self):
|
|
return reverse_lazy("opportunity_detail", kwargs={"pk": self.object.pk})
|
|
|
|
|
|
class OpportunityDetailView(DetailView):
|
|
model = models.Opportunity
|
|
template_name = "crm/opportunities/opportunity_detail.html"
|
|
context_object_name = "opportunity"
|
|
|
|
|
|
class OpportunityListView(ListView):
|
|
model = models.Opportunity
|
|
template_name = "crm/opportunities/opportunity_list.html"
|
|
context_object_name = "opportunities"
|
|
paginate_by = 10
|
|
|
|
def get_queryset(self):
|
|
dealer = get_user_type(self.request)
|
|
return models.Opportunity.objects.filter(dealer=dealer).all()
|
|
|
|
|
|
@login_required
|
|
def delete_opportunity(request, pk):
|
|
opportunity = get_object_or_404(models.Opportunity, pk=pk)
|
|
opportunity.delete()
|
|
messages.success(request, _("Opportunity deleted successfully."))
|
|
return redirect("opportunity_list")
|
|
|
|
|
|
# class OpportunityLogsView(LoginRequiredMixin, ListView):
|
|
# model = models.OpportunityLog
|
|
# template_name = "crm/opportunity_logs.html"
|
|
# context_object_name = "logs"
|
|
#
|
|
# def get_queryset(self):
|
|
# opportunity_id = self.kwargs["pk"]
|
|
# return models.OpportunityLog.objects.filter(
|
|
# opportunity_id=opportunity_id
|
|
# ).order_by("-created_at")
|
|
#
|
|
# def get_context_data(self, **kwargs):
|
|
# context = super().get_context_data(**kwargs)
|
|
# context["opportunity"] = models.Opportunity.objects.get(pk=self.kwargs["pk"])
|
|
# return context
|
|
|
|
|
|
class NotificationListView(LoginRequiredMixin, ListView):
|
|
model = models.Notification
|
|
template_name = "crm/notifications_history.html"
|
|
context_object_name = "notifications"
|
|
paginate_by = 20
|
|
ordering = "-created"
|
|
|
|
def get_queryset(self):
|
|
return models.Notification.objects.filter(user=self.request.user)
|
|
|
|
|
|
@login_required
|
|
def mark_notification_as_read(request, pk):
|
|
notification = get_object_or_404(models.Notification, pk=pk, user=request.user)
|
|
notification.is_read = True
|
|
notification.save()
|
|
messages.success(request, _("Notification marked as read."))
|
|
return redirect("notifications_history")
|
|
|
|
|
|
@login_required
|
|
def fetch_notifications(request):
|
|
notifications = models.Notification.objects.filter(
|
|
user=request.user, is_read=False
|
|
).order_by("-created")
|
|
# notifications_data = [
|
|
# {
|
|
# "id": notification.id,
|
|
# "message": notification.message,
|
|
# "created": notification.created.strftime("%Y-%m-%d %H:%M:%S"),
|
|
# }
|
|
# for notification in notifications
|
|
# ]
|
|
# return JsonResponse({"notifications": notifications_data})
|
|
return render(request, "notifications.html", {"notifications_": notifications})
|
|
|
|
|
|
class ItemServiceCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
|
|
model = models.AdditionalServices
|
|
form_class = forms.AdditionalServiceForm
|
|
template_name = "items/service/service_create.html"
|
|
success_url = reverse_lazy("item_service_list")
|
|
success_message = _("Service created successfully.")
|
|
context_object_name = "service"
|
|
|
|
def form_valid(self, form):
|
|
vat = models.VatRate.objects.get(is_active=True)
|
|
form.instance.dealer = get_user_type(self.request.user.dealer)
|
|
if form.instance.taxable:
|
|
form.instance.price = (form.instance.price * vat.rate) + form.instance.price
|
|
return super().form_valid(form)
|
|
|
|
|
|
class ItemServiceUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
|
|
model = models.AdditionalServices
|
|
form_class = forms.AdditionalServiceForm
|
|
template_name = "items/service/service_create.html"
|
|
success_url = reverse_lazy("item_service_list")
|
|
success_message = _("Service updated successfully.")
|
|
context_object_name = "service"
|
|
|
|
def form_valid(self, form):
|
|
vat = models.VatRate.objects.get(is_active=True)
|
|
form.instance.dealer = get_user_type(self.request.user.dealer)
|
|
if form.instance.taxable:
|
|
form.instance.price = (form.instance.price * vat.rate) + form.instance.price
|
|
return super().form_valid(form)
|
|
|
|
|
|
class ItemServiceListView(ListView):
|
|
model = models.AdditionalServices
|
|
template_name = "items/service/service_list.html"
|
|
context_object_name = "services"
|
|
paginate_by = 20
|
|
|
|
def get_queryset(self):
|
|
dealer = get_user_type(self.request)
|
|
return models.AdditionalServices.objects.filter(dealer=dealer).all()
|
|
|
|
|
|
class ItemExpenseCreateView(CreateView):
|
|
model = ItemModel
|
|
form_class = ExpenseItemCreateForm
|
|
template_name = "items/expenses/expense_create.html"
|
|
success_url = reverse_lazy("item_expense_list")
|
|
|
|
def get_form_kwargs(self):
|
|
dealer = get_user_type(self.request)
|
|
kwargs = super().get_form_kwargs()
|
|
kwargs["entity_slug"] = dealer.entity.slug
|
|
kwargs["user_model"] = dealer.entity.admin
|
|
return kwargs
|
|
|
|
def form_valid(self, form):
|
|
dealer = get_user_type(self.request)
|
|
form.instance.entity = dealer.entity
|
|
return super().form_valid(form)
|
|
|
|
|
|
class ItemExpenseUpdateView(UpdateView):
|
|
model = ItemModel
|
|
form_class = ExpenseItemUpdateForm
|
|
template_name = "items/expenses/expense_update.html"
|
|
success_url = reverse_lazy("item_expense_list")
|
|
|
|
def get_form_kwargs(self):
|
|
dealer = get_user_type(self.request)
|
|
kwargs = super().get_form_kwargs()
|
|
kwargs["entity_slug"] = dealer.entity.slug
|
|
kwargs["user_model"] = dealer.entity.admin
|
|
return kwargs
|
|
|
|
def form_valid(self, form):
|
|
dealer = get_user_type(self.request)
|
|
form.instance.entity = dealer.entity
|
|
return super().form_valid(form)
|
|
|
|
|
|
class ItemExpenseListView(ListView):
|
|
model = ItemModel
|
|
template_name = "items/expenses/expenses_list.html"
|
|
context_object_name = "expenses"
|
|
paginate_by = 20
|
|
|
|
def get_queryset(self):
|
|
dealer = get_user_type(self.request)
|
|
return dealer.entity.get_items_expenses()
|
|
|
|
|
|
class BillListView(ListView):
|
|
model = BillModel
|
|
template_name = "ledger/bills/bill_list.html"
|
|
context_object_name = "bills"
|
|
|
|
def get_queryset(self):
|
|
dealer = get_user_type(self.request)
|
|
qs = dealer.entity.get_bills()
|
|
return qs
|
|
|
|
|
|
class BillDetailView(LoginRequiredMixin, DetailView):
|
|
model = BillModel
|
|
template_name = "ledger/bills/bill_detail.html"
|
|
context_object_name = "bill"
|
|
|
|
def get_context_data(self, **kwargs):
|
|
bill = kwargs.get("object")
|
|
if bill.get_itemtxs_data():
|
|
txs = bill.get_itemtxs_data()[0]
|
|
|
|
car_and_item_info = [
|
|
{
|
|
"car": models.Car.objects.get(vin=x.item_model.name),
|
|
"total": models.Car.objects.get(
|
|
vin=x.item_model.name
|
|
).finances.cost_price
|
|
* Decimal(x.quantity),
|
|
"itemmodel": x,
|
|
}
|
|
for x in txs
|
|
]
|
|
grand_total = sum(
|
|
Decimal(
|
|
models.Car.objects.get(vin=x.item_model.name).finances.cost_price
|
|
)
|
|
* Decimal(x.quantity)
|
|
for x in txs
|
|
)
|
|
vat = models.VatRate.objects.filter(is_active=True).first()
|
|
if vat:
|
|
grand_total += round(Decimal(grand_total) * Decimal(vat.rate), 2)
|
|
kwargs["car_and_item_info"] = car_and_item_info
|
|
kwargs["grand_total"] = grand_total
|
|
return super().get_context_data(**kwargs)
|
|
|
|
|
|
class InReviewBillView(LoginRequiredMixin, UpdateView):
|
|
model = BillModel
|
|
form_class = InReviewBillModelUpdateForm
|
|
template_name = "ledger/bills/bill_update_form.html"
|
|
success_url = reverse_lazy("bill_list")
|
|
success_message = _("Bill updated successfully.")
|
|
context_object_name = "bill"
|
|
|
|
def get_form_kwargs(self):
|
|
kwargs = super().get_form_kwargs()
|
|
dealer = get_user_type(self.request)
|
|
kwargs["entity_model"] = dealer.entity
|
|
kwargs["user_model"] = dealer.entity.admin
|
|
return kwargs
|
|
|
|
def get_success_url(self):
|
|
return reverse_lazy("bill_detail", kwargs={"pk": self.kwargs["pk"]})
|
|
|
|
def form_valid(self, form):
|
|
dealer = get_user_type(self.request)
|
|
form.instance.entity = dealer.entity
|
|
self.object.mark_as_review()
|
|
return super().form_valid(form)
|
|
|
|
|
|
class ApprovedBillModelView(LoginRequiredMixin, UpdateView):
|
|
model = BillModel
|
|
form_class = ApprovedBillModelUpdateForm
|
|
template_name = "ledger/bills/bill_update_form.html"
|
|
success_url = reverse_lazy("bill_list")
|
|
success_message = _("Bill updated successfully.")
|
|
context_object_name = "bill"
|
|
|
|
def get_form_kwargs(self):
|
|
kwargs = super().get_form_kwargs()
|
|
dealer = get_user_type(self.request)
|
|
kwargs["entity_model"] = dealer.entity
|
|
kwargs["user_model"] = dealer.entity.admin
|
|
return kwargs
|
|
|
|
def get_success_url(self):
|
|
return reverse_lazy("bill_detail", kwargs={"pk": self.kwargs["pk"]})
|
|
|
|
def form_valid(self, form):
|
|
dealer = get_user_type(self.request)
|
|
form.instance.entity = dealer.entity
|
|
if not self.object.is_approved():
|
|
self.object.mark_as_approved(user_model=dealer.entity.admin)
|
|
return super().form_valid(form)
|
|
|
|
|
|
def bill_mark_as_approved(request, pk):
|
|
bill = get_object_or_404(BillModel, pk=pk)
|
|
if request.method == "POST":
|
|
dealer = get_user_type(request)
|
|
if bill.is_approved():
|
|
messages.error(request, _("Bill is already approved."))
|
|
return redirect("bill_detail", pk=bill.pk)
|
|
bill.mark_as_approved(user_model=dealer.entity.admin)
|
|
bill.save()
|
|
messages.success(request, _("Bill marked as approved successfully."))
|
|
return redirect("bill_detail", pk=bill.pk)
|
|
|
|
|
|
def bill_mark_as_paid(request, pk):
|
|
bill = get_object_or_404(BillModel, pk=pk)
|
|
if request.method == "POST":
|
|
dealer = get_user_type(request)
|
|
if bill.is_paid():
|
|
messages.error(request, _("Bill is already paid."))
|
|
return redirect("bill_detail", pk=bill.pk)
|
|
if bill.amount_due == bill.amount_paid:
|
|
bill.mark_as_paid(user_model=dealer.entity.admin)
|
|
bill.save()
|
|
bill.ledger.lock_journal_entries()
|
|
bill.ledger.post_journal_entries()
|
|
bill.ledger.post()
|
|
bill.ledger.save()
|
|
messages.success(request, _("Bill marked as paid successfully."))
|
|
else:
|
|
messages.error(request, _("Amount paid is not equal to amount due."))
|
|
return redirect("bill_detail", pk=bill.pk)
|
|
|
|
# def get_context_data(self, **kwargs):
|
|
# dealer = get_user_type(self.request)
|
|
# context = super().get_context_data(**kwargs)
|
|
# context['entity_model'] = dealer.entity
|
|
# context['user_model'] = dealer.entity.admin
|
|
|
|
# return context
|
|
|
|
|
|
# class BillCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
|
|
# model = BillModel
|
|
# form_class = BillModelCreateForm
|
|
# template_name = "ledger/bills/bill_form.html"
|
|
# success_url = reverse_lazy("bill_list")
|
|
# success_message = _("Bill created successfully.")
|
|
# def get_form_kwargs(self):
|
|
# dealer = get_user_type(self.request)
|
|
# kwargs = super().get_form_kwargs()
|
|
# kwargs["entity_model"] = dealer.entity
|
|
# return kwargs
|
|
|
|
|
|
# def form_valid(self, form):
|
|
# dealer = get_user_type(self.request)
|
|
# form.instance.entity = dealer.entity
|
|
# ledger = dealer.entity.create_ledger(
|
|
# name=f"Bill for Vendor {form.instance.vendor.vendor_name}", posted=True
|
|
# )
|
|
# form.instance.ledger = ledger
|
|
# return super().form_valid(form)
|
|
@login_required
|
|
def bill_create(request):
|
|
dealer = get_user_type(request)
|
|
entity = dealer.entity
|
|
|
|
if request.method == "POST":
|
|
data = json.loads(request.body)
|
|
vendor_id = data.get("vendor")
|
|
terms = data.get("terms")
|
|
vendor = entity.get_vendors().filter(pk=vendor_id).first()
|
|
|
|
items = data.get("item", [])
|
|
quantities = data.get("quantity", [])
|
|
|
|
if not all([items, quantities]):
|
|
return JsonResponse(
|
|
{"status": "error", "message": "Items and Quantities are required"},
|
|
status=400,
|
|
)
|
|
if isinstance(quantities, list):
|
|
if "0" in quantities:
|
|
return JsonResponse(
|
|
{"status": "error", "message": "Quantity must be greater than zero"}
|
|
)
|
|
else:
|
|
if int(quantities) <= 0:
|
|
return JsonResponse(
|
|
{"status": "error", "message": "Quantity must be greater than zero"}
|
|
)
|
|
|
|
bill = entity.create_bill(vendor_model=vendor, terms=terms)
|
|
if isinstance(items, list):
|
|
item_quantity_map = {}
|
|
for item, quantity in zip(items, quantities):
|
|
if item in item_quantity_map:
|
|
item_quantity_map[item] += int(quantity)
|
|
else:
|
|
item_quantity_map[item] = int(quantity)
|
|
item_list = list(item_quantity_map.keys())
|
|
quantity_list = list(item_quantity_map.values())
|
|
|
|
items_list = [
|
|
{"item_id": item_list[i], "quantity": quantity_list[i]}
|
|
for i in range(len(item_list))
|
|
]
|
|
items_txs = []
|
|
for item in items_list:
|
|
item_instance = ItemModel.objects.get(pk=item.get("item_id"))
|
|
car = models.Car.objects.get(vin=item_instance.name)
|
|
quantity = Decimal(item.get("quantity"))
|
|
items_txs.append(
|
|
{
|
|
"item_number": item_instance.item_number,
|
|
"quantity": quantity,
|
|
"unit_cost": car.finances.cost_price,
|
|
"total_amount": car.finances.cost_price * quantity,
|
|
}
|
|
)
|
|
|
|
bill_itemtxs = {
|
|
item.get("item_number"): {
|
|
"unit_cost": item.get("unit_cost"),
|
|
"quantity": item.get("quantity"),
|
|
"total_amount": item.get("total_amount"),
|
|
}
|
|
for item in items_txs
|
|
}
|
|
else:
|
|
item = entity.get_items_all().filter(pk=items).first()
|
|
instance = models.Car.objects.get(vin=item.name)
|
|
bill_itemtxs = {
|
|
item.item_number: {
|
|
"unit_cost": instance.finances.cost_price,
|
|
"quantity": Decimal(quantities),
|
|
"total_amount": instance.finances.cost_price * Decimal(quantities),
|
|
}
|
|
}
|
|
|
|
bill_itemtxs = bill.migrate_itemtxs(
|
|
itemtxs=bill_itemtxs,
|
|
commit=True,
|
|
operation=BillModel.ITEMIZE_APPEND,
|
|
)
|
|
|
|
url = reverse("bill_detail", kwargs={"pk": bill.pk})
|
|
return JsonResponse(
|
|
{
|
|
"status": "success",
|
|
"message": "Bill created successfully!",
|
|
"url": f"{url}",
|
|
}
|
|
)
|
|
|
|
form = forms.BillModelCreateForm(entity_model=entity)
|
|
form.initial.update(
|
|
{
|
|
"cash_account": entity.get_default_coa_accounts().get(name="Cash"),
|
|
"prepaid_account": entity.get_default_coa_accounts().get(
|
|
name="Prepaid Expenses"
|
|
),
|
|
"unearned_account": entity.get_default_coa_accounts().get(
|
|
name="Accounts Payable"
|
|
),
|
|
}
|
|
)
|
|
car_list = models.Car.objects.filter(
|
|
dealer=dealer, finances__selling_price__gt=0, status="available"
|
|
)
|
|
context = {
|
|
"form": form,
|
|
"items": [
|
|
{
|
|
"car": x,
|
|
"product": entity.get_items_products()
|
|
.filter(name=x.vin)
|
|
.first(),
|
|
}
|
|
for x in car_list
|
|
],
|
|
}
|
|
|
|
return render(request, "ledger/bills/bill_form.html", context)
|
|
|
|
|
|
def BillDeleteView(request, pk):
|
|
bill = get_object_or_404(BillModel, pk=pk)
|
|
bill.delete()
|
|
return redirect("bill_list")
|
|
|
|
|
|
class SubscriptionPlans(ListView):
|
|
model = models.SubscriptionPlan
|
|
template_name = "subscriptions/subscription_plan.html"
|
|
context_object_name = "plans"
|
|
|
|
|
|
# orders
|
|
|
|
class OrderListView(ListView):
|
|
model = models.SaleOrder
|
|
template_name = "sales/orders/order_list.html"
|
|
context_object_name = "orders"
|
|
|
|
|
|
# email
|
|
def send_email_view(request, pk):
|
|
estimate = get_object_or_404(EstimateModel, pk=pk)
|
|
if request.method == "POST":
|
|
# if not estimate.can_review():
|
|
# messages.error(request, "Estimate is not ready for review")
|
|
# return redirect("estimate_detail", pk=estimate.pk)
|
|
if not estimate.get_itemtxs_data()[0]:
|
|
messages.error(request, "Estimate has no items")
|
|
return redirect("estimate_detail", pk=estimate.pk)
|
|
|
|
send_email(
|
|
"manager@tenhal.com",
|
|
request.POST.get("to"),
|
|
request.POST.get("subject"),
|
|
request.POST.get("message"),
|
|
)
|
|
estimate.mark_as_review()
|
|
messages.success(request, "Email sent successfully!")
|
|
return redirect("estimate_detail", pk=estimate.pk)
|
|
link = reverse_lazy("estimate_preview", kwargs={"pk": estimate.pk})
|
|
msg = f"""
|
|
السلام عليكم
|
|
Dear {estimate.customer.customer_name},
|
|
|
|
أود أن أشارككم تقدير المشروع الذي ناقشناه. يرجى العثور على الوثيقة التفصيلية للمقترح المرفقة.
|
|
|
|
I hope this email finds you well. I wanted to share with you the estimate for the project we discussed. Please find the detailed estimate document attached.
|
|
|
|
يرجى مراجعة المقترح وإعلامي إذا كانت لديك أي أسئلة أو مخاوف. إذا كانت كل شيء يبدو جيدًا، يمكننا المضي قدمًا في المشروع.
|
|
|
|
Please review the estimate and let me know if you have any questions or concerns. If everything looks good, we can proceed with the project.
|
|
|
|
Estimate Link:
|
|
{link}
|
|
|
|
شكراً لاهتمامكم بهذا الأمر.
|
|
Thank you for your attention to this matter.
|
|
|
|
تحياتي,
|
|
Best regards,
|
|
[Your Name]
|
|
[Your Position]
|
|
[Your Company]
|
|
[Your Contact Information]
|
|
"""
|
|
return render(
|
|
request,
|
|
"sales/estimates/estimate_send.html",
|
|
{"estimate": estimate, "message": msg},
|
|
)
|
|
|
|
|
|
# errors
|
|
def custom_page_not_found_view(request, exception):
|
|
return render(request, "errors/404.html", {})
|
|
|
|
|
|
def custom_error_view(request, exception=None):
|
|
return render(request, "errors/500.html", {})
|
|
|
|
|
|
def custom_permission_denied_view(request, exception=None):
|
|
return render(request, "errors/403.html", {})
|
|
|
|
|
|
def custom_bad_request_view(request, exception=None):
|
|
return render(request, "errors/400.html", {})
|
|
|
|
|
|
# from django_ledger.io.io_core import get_localdate
|
|
# from django_ledger.views.mixins import (DjangoLedgerSecurityMixIn)
|
|
# from django.views.generic import RedirectView
|
|
from django_ledger.views.financial_statement import FiscalYearBalanceSheetView, BaseIncomeStatementRedirectView, \
|
|
FiscalYearIncomeStatementView
|
|
from django.views.generic import DetailView, RedirectView
|
|
|
|
from django_ledger.io.io_core import get_localdate
|
|
from django_ledger.models import EntityModel, EntityUnitModel
|
|
from django_ledger.views.mixins import (
|
|
QuarterlyReportMixIn, YearlyReportMixIn,
|
|
MonthlyReportMixIn, DateReportMixIn, DjangoLedgerSecurityMixIn, EntityUnitMixIn,
|
|
BaseDateNavigationUrlMixIn, PDFReportMixIn
|
|
)
|
|
|
|
|
|
# BALANCE SHEET -----------
|
|
|
|
class BaseBalanceSheetRedirectView(DjangoLedgerSecurityMixIn, RedirectView):
|
|
|
|
def get_redirect_url(self, *args, **kwargs):
|
|
year = get_localdate().year
|
|
return reverse('entity-bs-year',
|
|
kwargs={
|
|
'entity_slug': self.kwargs['entity_slug'],
|
|
'year': year
|
|
})
|
|
|
|
|
|
class FiscalYearBalanceSheetViewBase(FiscalYearBalanceSheetView):
|
|
template_name = "ledger/reports/balance_sheet.html"
|
|
|
|
|
|
class QuarterlyBalanceSheetView(FiscalYearBalanceSheetViewBase, QuarterlyReportMixIn):
|
|
"""
|
|
Quarter Balance Sheet View.
|
|
"""
|
|
|
|
|
|
class MonthlyBalanceSheetView(FiscalYearBalanceSheetViewBase, MonthlyReportMixIn):
|
|
"""
|
|
Monthly Balance Sheet View.
|
|
"""
|
|
|
|
|
|
class DateBalanceSheetView(FiscalYearBalanceSheetViewBase, DateReportMixIn):
|
|
"""
|
|
Date Balance Sheet View.
|
|
"""
|
|
|
|
|
|
class BaseIncomeStatementRedirectViewBase(BaseIncomeStatementRedirectView):
|
|
|
|
def get_redirect_url(self, *args, **kwargs):
|
|
year = get_localdate().year
|
|
dealer = get_user_type(self.request)
|
|
return reverse('entity-ic-year',
|
|
kwargs={
|
|
'entity_slug': dealer.entity.slug,
|
|
'year': year
|
|
})
|
|
|
|
|
|
class FiscalYearIncomeStatementViewBase(FiscalYearIncomeStatementView):
|
|
template_name = "ledger/reports/income_statement.html"
|
|
|
|
|
|
class QuarterlyIncomeStatementView(FiscalYearIncomeStatementView, QuarterlyReportMixIn):
|
|
"""
|
|
Quarter Income Statement View.
|
|
"""
|
|
|
|
|
|
class MonthlyIncomeStatementView(FiscalYearIncomeStatementView, MonthlyReportMixIn):
|
|
"""
|
|
Monthly Income Statement View.
|
|
"""
|
|
|
|
|
|
class DateModelIncomeStatementView(FiscalYearIncomeStatementView, DateReportMixIn):
|
|
"""
|
|
Date Income Statement View.
|
|
"""
|