858 lines
31 KiB
Python
858 lines
31 KiB
Python
import logging
|
|
import json
|
|
from decimal import Decimal
|
|
|
|
from django.views.decorators.csrf import csrf_exempt
|
|
from vin import VIN
|
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
|
from django.contrib.auth.decorators import login_required
|
|
from django.http import JsonResponse
|
|
from django.shortcuts import render, get_object_or_404, redirect
|
|
from django.utils.translation import gettext_lazy as _
|
|
from django.db.models import Q
|
|
from django.views.generic import (
|
|
View,
|
|
ListView,
|
|
DetailView,
|
|
CreateView,
|
|
UpdateView,
|
|
DeleteView,
|
|
TemplateView
|
|
)
|
|
from django.utils import timezone, translation
|
|
from django.conf import settings
|
|
from urllib.parse import urlparse, urlunparse
|
|
from django.forms import ChoiceField, ModelForm, RadioSelect
|
|
from django.urls import reverse, reverse_lazy
|
|
from django.contrib import messages
|
|
from django.db.models import Sum, F, Count
|
|
from .services import elm, decodevin, get_make, get_model, normalize_name, get_ledger_data
|
|
from . import models, forms
|
|
from django_tables2.export.views import ExportMixin
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
logging.basicConfig(level=logging.INFO)
|
|
|
|
|
|
def switch_language(request):
|
|
language = request.GET.get('language', 'en')
|
|
referer = request.META.get('HTTP_REFERER', '/')
|
|
parsed_url = urlparse(referer)
|
|
path_parts = parsed_url.path.split('/')
|
|
|
|
if path_parts[1] in dict(settings.LANGUAGES):
|
|
path_parts.pop(1)
|
|
|
|
new_path = '/'.join(path_parts)
|
|
new_url = urlunparse(
|
|
(parsed_url.scheme, parsed_url.netloc, new_path, parsed_url.params, parsed_url.query, parsed_url.fragment)
|
|
)
|
|
|
|
if language in dict(settings.LANGUAGES):
|
|
logger.debug(f"Switching language to: {language}")
|
|
response = redirect(new_url)
|
|
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, language)
|
|
translation.activate(language)
|
|
request.session[settings.LANGUAGE_COOKIE_NAME] = language
|
|
logger.debug(f"Language switched to: {language}, Session: {request.session[settings.LANGUAGE_COOKIE_NAME]}")
|
|
return response
|
|
else:
|
|
logger.warning(f"Invalid language code: {language}")
|
|
return redirect('/')
|
|
|
|
|
|
class HomeView(LoginRequiredMixin, TemplateView):
|
|
template_name = 'index.html'
|
|
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
if not hasattr(request.user, 'dealer') or not request.user.is_authenticated:
|
|
messages.error(request, _('You are not associated with any dealer.'))
|
|
return redirect('welcome')
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
|
|
total_cars = models.Car.objects.count()
|
|
total_reservations = models.CarReservation.objects.filter(reserved_until__gte=timezone.now()).count()
|
|
stats = models.CarFinance.objects.aggregate(
|
|
total_cost_price=Sum('cost_price'),
|
|
total_selling_price=Sum('selling_price'),
|
|
)
|
|
total_cost_price = stats['total_cost_price'] or 0
|
|
total_selling_price = stats['total_selling_price'] or 0
|
|
total_profit = total_selling_price - total_cost_price
|
|
|
|
context['total_cars'] = total_cars
|
|
context['total_reservations'] = total_reservations
|
|
context['total_cost_price'] = total_cost_price
|
|
context['total_selling_price'] = total_selling_price
|
|
context['total_profit'] = total_profit
|
|
return context
|
|
|
|
|
|
class WelcomeView(TemplateView):
|
|
template_name = "welcome.html"
|
|
|
|
|
|
class CarCreateView(LoginRequiredMixin, CreateView):
|
|
model = models.Car
|
|
form_class = forms.CarForm
|
|
template_name = 'inventory/car_form.html'
|
|
# success_url = reverse_lazy('inventory_stats')
|
|
|
|
def get_success_url(self):
|
|
"""Determine the redirect URL based on user choice."""
|
|
if self.request.POST.get('add_another'):
|
|
return reverse('car_add')
|
|
return reverse('inventory_stats')
|
|
|
|
def form_valid(self, form):
|
|
form.instance.dealer = self.request.user.dealer
|
|
form.save()
|
|
messages.success(self.request, 'Car saved successfully.')
|
|
return super().form_valid(form)
|
|
|
|
|
|
class AjaxHandlerView(LoginRequiredMixin, View):
|
|
def get(self, request, *args, **kwargs):
|
|
action = request.GET.get('action')
|
|
handlers = {
|
|
'decode_vin': self.decode_vin,
|
|
'get_models': self.get_models,
|
|
'get_series': self.get_series,
|
|
'get_trims': self.get_trims,
|
|
'get_specifications': self.get_specifications,
|
|
}
|
|
handler = handlers.get(action)
|
|
if handler:
|
|
return handler(request)
|
|
else:
|
|
return JsonResponse({'error': 'Invalid action'}, status=400)
|
|
|
|
def decode_vin(self, request):
|
|
vin_no = request.GET.get('vin_no')
|
|
if not vin_no or len(vin_no.strip()) != 17:
|
|
return JsonResponse({'success': False, 'error': 'Invalid VIN number provided.'}, status=400)
|
|
|
|
vin_no = vin_no.strip()
|
|
vin_data = {}
|
|
decoding_method = ''
|
|
|
|
# manufacturer_name = model_name = year_model = None
|
|
if not (result :=decodevin(vin_no)):
|
|
return JsonResponse({'success': False, 'error': 'VIN not found in all sources.'}, status=404)
|
|
|
|
manufacturer_name,model_name,year_model = result.values()
|
|
make = get_make(manufacturer_name)
|
|
model = get_model(model_name,make)
|
|
|
|
logger.info(
|
|
f"VIN decoded using {decoding_method}: Make={manufacturer_name}, Model={model_name}, Year={year_model}"
|
|
)
|
|
car_model = model
|
|
car_make = make
|
|
|
|
if not car_make:
|
|
return JsonResponse({'success': False, 'error': 'Manufacturer not found in the database.'}, status=404)
|
|
vin_data['make_id'] = car_make.id_car_make
|
|
vin_data['name'] = car_make.name
|
|
vin_data['arabic_name'] = car_make.arabic_name
|
|
|
|
if not car_model:
|
|
vin_data['model_id'] = ""
|
|
else:
|
|
vin_data['model_id'] = car_model.id_car_model
|
|
vin_data['year'] = year_model
|
|
return JsonResponse({'success': True, 'data': vin_data})
|
|
|
|
def get_models(self, request):
|
|
make_id = request.GET.get('make_id')
|
|
car_models = (models.CarModel.objects.filter(id_car_make=make_id)
|
|
.values('id_car_model', 'name', 'arabic_name')
|
|
.order_by('name'))
|
|
return JsonResponse(list(car_models), safe=False)
|
|
|
|
def get_series(self, request):
|
|
model_id = request.GET.get('model_id')
|
|
year = request.GET.get('year')
|
|
|
|
# Validate inputs
|
|
if not model_id or not year:
|
|
return JsonResponse({'error': 'Missing required parameters: model_id or year'}, status=400)
|
|
|
|
try:
|
|
year = int(year)
|
|
except ValueError:
|
|
return JsonResponse({'error': 'Invalid year format'}, status=400)
|
|
|
|
series = models.CarSerie.objects.filter(
|
|
id_car_model=model_id).values(
|
|
'id_car_serie', 'name', 'arabic_name'
|
|
)
|
|
|
|
return JsonResponse(list(series), safe=False)
|
|
|
|
def get_trims(self, request):
|
|
serie_id = request.GET.get('serie_id')
|
|
model_id = request.GET.get('model_id')
|
|
trims = models.CarTrim.objects.filter(
|
|
id_car_serie=serie_id, id_car_model=model_id).values('id_car_trim', 'name', 'arabic_name')
|
|
return JsonResponse(list(trims), safe=False)
|
|
|
|
def get_specifications(self, request):
|
|
trim_id = request.GET.get('trim_id')
|
|
car_spec_values = models.CarSpecificationValue.objects.filter(id_car_trim=trim_id)
|
|
lang = translation.get_language()
|
|
specs_by_parent = {}
|
|
for value in car_spec_values:
|
|
specification = value.id_car_specification
|
|
parent = specification.id_parent
|
|
parent_id = parent.id_car_specification if parent else 0
|
|
if lang == 'ar':
|
|
parent_name = parent.arabic_name if parent else "Root"
|
|
else:
|
|
parent_name = parent.name if parent else "Root"
|
|
if parent_id not in specs_by_parent:
|
|
specs_by_parent[parent_id] = {'parent_name': parent_name, 'specifications': []}
|
|
spec_data = {
|
|
'specification_id': specification.id_car_specification,
|
|
's_name': specification.arabic_name if lang == 'ar' else specification.name,
|
|
's_value': value.value,
|
|
's_unit': value.unit if value.unit else "",
|
|
'trim_name': value.id_car_trim.name
|
|
}
|
|
specs_by_parent[parent_id]['specifications'].append(spec_data)
|
|
serialized_specs = [
|
|
{'parent_name': v['parent_name'], 'specifications': v['specifications']}
|
|
for v in specs_by_parent.values()
|
|
]
|
|
return JsonResponse(serialized_specs, safe=False)
|
|
|
|
|
|
class CarInventory(LoginRequiredMixin, ListView):
|
|
model = models.Car
|
|
home_label = _('inventory')
|
|
template_name = 'inventory/car_inventory.html'
|
|
context_object_name = 'cars'
|
|
paginate_by = 10
|
|
ordering = ['receiving_date']
|
|
|
|
def get_queryset(self, *args, **kwargs):
|
|
query = self.request.GET.get('q')
|
|
make_id = self.kwargs['make_id']
|
|
model_id = self.kwargs['model_id']
|
|
trim_id = self.kwargs['trim_id']
|
|
cars = models.Car.objects.filter(
|
|
dealer__user=self.request.user,
|
|
id_car_make=make_id,
|
|
id_car_model=model_id,
|
|
id_car_trim=trim_id,).order_by('receiving_date')
|
|
|
|
if query:
|
|
cars = cars.filter(Q(vin__icontains=query))
|
|
return cars
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context['query'] = self.request.GET.get('q', '')
|
|
context['make_id'] = self.kwargs['make_id']
|
|
context['model_id'] = self.kwargs['model_id']
|
|
context['trim_id'] = self.kwargs['trim_id']
|
|
return context
|
|
|
|
|
|
class CarColorCreate(LoginRequiredMixin, CreateView):
|
|
model = models.CarColors
|
|
form_class = forms.CarColorsForm
|
|
template_name = "inventory/add_colors.html"
|
|
|
|
def form_valid(self, form):
|
|
car = get_object_or_404(models.Car, pk=self.kwargs['car_pk'])
|
|
form.instance.car = car
|
|
return super().form_valid(form)
|
|
|
|
def get_success_url(self):
|
|
return reverse_lazy('car_detail', kwargs={'pk': self.kwargs['car_pk']})
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context['car'] = get_object_or_404(models.Car, pk=self.kwargs['car_pk'])
|
|
return context
|
|
|
|
|
|
@login_required
|
|
def inventory_stats_view(request):
|
|
dealer = request.user.dealer
|
|
|
|
# Annotate total cars by make, model, and trim
|
|
cars = (
|
|
models.Car.objects.filter(dealer=dealer)
|
|
.select_related('id_car_make', 'id_car_model', 'id_car_trim')
|
|
.annotate(
|
|
make_total=Count('id_car_make'),
|
|
model_total=Count('id_car_model'),
|
|
trim_total=Count('id_car_trim')
|
|
)
|
|
)
|
|
|
|
# Prepare the nested structure
|
|
inventory = {}
|
|
for car in cars:
|
|
# Make Level
|
|
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 Level
|
|
model = car.id_car_model
|
|
if model and model.id_car_model not in inventory[make.id_car_make]['models']:
|
|
inventory[make.id_car_make]['models'][model.id_car_model] = {
|
|
'model_id': model.id_car_model,
|
|
'model_name': model.get_local_name(),
|
|
'total_cars': 0,
|
|
'trims': {}
|
|
}
|
|
inventory[make.id_car_make]['models'][model.id_car_model]['total_cars'] += 1
|
|
|
|
# Trim Level
|
|
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
|
|
|
|
# Convert to a list for easier template rendering
|
|
result = {
|
|
'total_cars': cars.count(),
|
|
'makes': [
|
|
{
|
|
'make_id': make_data['make_id'],
|
|
'make_name': make_data['make_name'],
|
|
'total_cars': make_data['total_cars'],
|
|
'models': [
|
|
{
|
|
'model_id': model_data['model_id'],
|
|
'model_name': model_data['model_name'],
|
|
'total_cars': model_data['total_cars'],
|
|
'trims': list(model_data['trims'].values())
|
|
}
|
|
for model_data in make_data['models'].values()
|
|
]
|
|
}
|
|
for make_data in inventory.values()
|
|
]
|
|
}
|
|
|
|
return render(request, 'inventory/inventory_stats.html', {'inventory': result})
|
|
|
|
|
|
class CarDetailView(LoginRequiredMixin, DetailView):
|
|
model = models.Car
|
|
template_name = 'inventory/car_detail.html'
|
|
context_object_name = 'car'
|
|
|
|
|
|
class CarFinanceCreateView(LoginRequiredMixin, CreateView):
|
|
model = models.CarFinance
|
|
form_class = forms.CarFinanceForm
|
|
template_name = 'inventory/car_finance_form.html'
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
self.car = get_object_or_404(models.Car, pk=self.kwargs['car_pk'])
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
def form_valid(self, form):
|
|
form.instance.car = self.car
|
|
messages.success(self.request, _('Car finance details saved successfully.'))
|
|
return super().form_valid(form)
|
|
|
|
def get_success_url(self):
|
|
return reverse('car_detail', kwargs={'pk': self.car.pk})
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context['car'] = self.car
|
|
return context
|
|
|
|
|
|
class CarFinanceUpdateView(LoginRequiredMixin, UpdateView):
|
|
model = models.CarFinance
|
|
form_class = forms.CarFinanceForm
|
|
template_name = 'inventory/car_finance_form.html'
|
|
|
|
def form_valid(self, form):
|
|
messages.success(self.request, _('Car finance updated successfully.'))
|
|
return super().form_valid(form)
|
|
|
|
def get_success_url(self):
|
|
return reverse('car_detail', kwargs={'pk': self.object.car.pk})
|
|
|
|
|
|
class CarUpdateView(LoginRequiredMixin, UpdateView):
|
|
model = models.Car
|
|
form_class = forms.CarUpdateForm
|
|
template_name = 'inventory/car_edit.html'
|
|
|
|
def form_valid(self, form):
|
|
messages.success(self.request, _('Car updated successfully.'))
|
|
return super().form_valid(form)
|
|
|
|
def get_success_url(self):
|
|
return reverse('car_detail', kwargs={'pk': self.object.pk})
|
|
|
|
|
|
class CarDeleteView(LoginRequiredMixin, DeleteView):
|
|
model = models.Car
|
|
template_name = 'inventory/car_confirm_delete.html'
|
|
success_url = reverse_lazy('inventory_stats')
|
|
|
|
def delete(self, request, *args, **kwargs):
|
|
messages.success(request, _('Car deleted successfully.'))
|
|
return super().delete(request, *args, **kwargs)
|
|
|
|
|
|
class CarLocationCreateView(CreateView):
|
|
model = models.CarLocation
|
|
form_class = forms.CarLocationForm
|
|
template_name = 'inventory/car_location_form.html'
|
|
|
|
def get_success_url(self):
|
|
return reverse_lazy('car_detail', kwargs={'pk': self.object.car.pk})
|
|
|
|
def form_valid(self, form):
|
|
form.instance.car = get_object_or_404(models.Car, pk=self.kwargs['car_pk'])
|
|
form.instance.owner = self.request.user.dealer
|
|
form.save()
|
|
messages.success(self.request, 'Car saved successfully.')
|
|
return super().form_valid(form)
|
|
|
|
|
|
class CarLocationUpdateView(UpdateView):
|
|
model = models.CarLocation
|
|
form_class = forms.CarLocationForm
|
|
template_name = 'inventory/car_location_form.html'
|
|
|
|
def get_success_url(self):
|
|
return reverse_lazy('car_detail', kwargs={'pk': self.object.car.pk})
|
|
|
|
|
|
class CustomCardCreateView(LoginRequiredMixin, CreateView):
|
|
model = models.CustomCard
|
|
form_class = forms.CustomCardForm
|
|
template_name = "inventory/add_custom_card.html"
|
|
|
|
def form_valid(self, form):
|
|
car = get_object_or_404(models.Car, pk=self.kwargs['car_pk'])
|
|
form.instance.car = car
|
|
return super().form_valid(form)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context['car'] = get_object_or_404(models.Car, pk=self.kwargs['car_pk'])
|
|
return context
|
|
|
|
def get_success_url(self):
|
|
messages.success(self.request, _("Custom Card added successfully."))
|
|
return reverse_lazy('car_detail', kwargs={'pk': self.kwargs['car_pk']})
|
|
|
|
|
|
@login_required()
|
|
def reserve_car_view(request, car_id):
|
|
if request.method == "POST":
|
|
car = get_object_or_404(models.Car, pk=car_id)
|
|
if car.is_reserved():
|
|
messages.error(request, _("This car is already reserved."))
|
|
return redirect('car_detail', pk=car.pk)
|
|
|
|
try:
|
|
reserved_until = timezone.now() + timezone.timedelta(hours=24)
|
|
models.CarReservation.objects.create(
|
|
car=car,
|
|
reserved_by=request.user,
|
|
reserved_until=reserved_until
|
|
)
|
|
messages.success(request, _("Car reserved successfully."))
|
|
except Exception as e:
|
|
messages.error(request, f"Error reserving car: {e}")
|
|
|
|
return redirect('car_detail', pk=car.pk)
|
|
return JsonResponse({"success": False, "message": "Invalid request method."}, status=400)
|
|
|
|
|
|
@login_required
|
|
def manage_reservation(request, reservation_id):
|
|
reservation = get_object_or_404(models.CarReservation, pk=reservation_id, reserved_by=request.user)
|
|
|
|
if request.method == "POST":
|
|
action = request.POST.get("action")
|
|
if action == "renew":
|
|
reservation.reserved_until = timezone.now() + timezone.timedelta(hours=24)
|
|
reservation.save()
|
|
messages.success(request, _("Reservation renewed successfully."))
|
|
return redirect('car_detail', pk=reservation.car.pk)
|
|
|
|
elif action == "cancel":
|
|
reservation.delete()
|
|
messages.success(request, _("Reservation canceled successfully."))
|
|
return redirect('car_detail', pk=reservation.car.pk)
|
|
|
|
else:
|
|
return JsonResponse({"success": False, "message": _("Invalid action.")}, status=400)
|
|
|
|
return JsonResponse({"success": False, "message": _("Invalid request method.")}, status=400)
|
|
|
|
|
|
class DealerListView(LoginRequiredMixin, ListView):
|
|
model = models.Dealer
|
|
template_name = 'dealer_list.html'
|
|
context_object_name = 'dealers'
|
|
|
|
|
|
class DealerDetailView(LoginRequiredMixin, DetailView):
|
|
model = models.Dealer
|
|
template_name = 'dealers/dealer_detail.html'
|
|
context_object_name = 'dealer'
|
|
|
|
|
|
class DealerCreateView(LoginRequiredMixin, CreateView):
|
|
model = models.Dealer
|
|
form_class = forms.DealerForm
|
|
template_name = 'dealer_form.html'
|
|
success_url = reverse_lazy('dealer_list')
|
|
|
|
def form_valid(self, form):
|
|
messages.success(self.request, _('Dealer created successfully.'))
|
|
return super().form_valid(form)
|
|
|
|
|
|
class DealerUpdateView(LoginRequiredMixin, UpdateView):
|
|
model = models.Dealer
|
|
form_class = forms.DealerForm
|
|
template_name = 'dealers/dealer_form.html'
|
|
success_url = reverse_lazy('dealer_detail')
|
|
|
|
def form_valid(self, form):
|
|
messages.success(self.request, _('Dealer updated successfully.'))
|
|
return super().form_valid(form)
|
|
|
|
|
|
class DealerDeleteView(LoginRequiredMixin, DeleteView):
|
|
model = models.Dealer
|
|
template_name = 'dealer_confirm_delete.html'
|
|
success_url = reverse_lazy('dealer_list')
|
|
|
|
def delete(self, request, *args, **kwargs):
|
|
messages.success(request, _('Dealer deleted successfully.'))
|
|
return super().delete(request, *args, **kwargs)
|
|
|
|
|
|
class CustomerListView(LoginRequiredMixin, ListView):
|
|
model = models.Customer
|
|
home_label = _('customers')
|
|
context_object_name = 'customers'
|
|
paginate_by = 10
|
|
template_name = "customers/customer_list.html"
|
|
|
|
def get_queryset(self):
|
|
query = self.request.GET.get('q')
|
|
customers = models.Customer.objects.filter(dealer__user=self.request.user)
|
|
|
|
if query:
|
|
customers = customers.filter(
|
|
Q(national_id__icontains=query) |
|
|
Q(first_name__icontains=query) |
|
|
Q(last_name__icontains=query)
|
|
)
|
|
return customers
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context['query'] = self.request.GET.get('q', '')
|
|
return context
|
|
|
|
|
|
class CustomerDetailView(LoginRequiredMixin, DetailView):
|
|
model = models.Customer
|
|
template_name = 'customers/view_customer.html'
|
|
context_object_name = 'customer'
|
|
|
|
|
|
class CustomerCreateView(LoginRequiredMixin, CreateView):
|
|
model = models.Customer
|
|
form_class = forms.CustomerForm
|
|
template_name = 'customers/customer_form.html'
|
|
success_url = reverse_lazy('customer_list')
|
|
|
|
def form_valid(self, form):
|
|
if form.is_valid():
|
|
form.instance.dealer = self.request.user.dealer
|
|
form.save()
|
|
messages.success(self.request, _('Customer created successfully.'))
|
|
return super().form_valid(form)
|
|
else:
|
|
return form.errors
|
|
|
|
|
|
class CustomerUpdateView(LoginRequiredMixin, UpdateView):
|
|
model = models.Customer
|
|
form_class = forms.CustomerForm
|
|
template_name = 'customers/customer_form.html'
|
|
success_url = reverse_lazy('customer_list')
|
|
|
|
def form_valid(self, form):
|
|
if form.is_valid():
|
|
form.instance.dealer = self.request.user.dealer
|
|
form.save()
|
|
messages.success(self.request, _('Customer updated successfully.'))
|
|
return super().form_valid(form)
|
|
else:
|
|
return form.errors
|
|
|
|
|
|
@login_required
|
|
def delete_customer(request, pk):
|
|
customer = get_object_or_404(models.Customer, pk=pk)
|
|
customer.delete()
|
|
messages.success(request, _('Customer deleted successfully.'))
|
|
return redirect('customer_list')
|
|
|
|
|
|
class VendorListView(LoginRequiredMixin, ListView):
|
|
model = models.Vendor
|
|
context_object_name = 'vendors'
|
|
paginate_by = 10
|
|
template_name = "vendors/vendors_list.html"
|
|
|
|
|
|
class VendorDetailView(LoginRequiredMixin, DetailView):
|
|
model = models.Vendor
|
|
template_name = "vendors/view_vendor.html"
|
|
|
|
|
|
class VendorCreateView(LoginRequiredMixin, CreateView):
|
|
model = models.Vendor
|
|
form_class = forms.VendorForm
|
|
template_name = 'vendors/vendor_form.html'
|
|
success_url = reverse_lazy('vendor_list')
|
|
|
|
def form_valid(self, form):
|
|
if form.is_valid():
|
|
form.instance.dealer = self.request.user.dealer
|
|
form.save()
|
|
messages.success(self.request, _('Vendor created successfully.'))
|
|
return super().form_valid(form)
|
|
else:
|
|
return form.errors
|
|
|
|
|
|
class VendorUpdateView(LoginRequiredMixin, UpdateView):
|
|
model = models.Vendor
|
|
form_class = forms.VendorForm
|
|
template_name = 'vendors/vendor_form.html'
|
|
success_url = reverse_lazy('vendor_list')
|
|
|
|
def form_valid(self, form):
|
|
if form.is_valid():
|
|
form.instance.dealer = self.request.user.dealer
|
|
form.save()
|
|
messages.success(self.request, _('Vendor updated successfully.'))
|
|
return super().form_valid(form)
|
|
else:
|
|
return form.errors
|
|
|
|
|
|
@login_required
|
|
def delete_vendor(request, pk):
|
|
vendor = get_object_or_404(models.Vendor, pk=pk)
|
|
vendor.delete()
|
|
messages.success(request, _('Vendor deleted successfully.'))
|
|
return redirect('vendor_list')
|
|
|
|
|
|
class QuotationCreateView(LoginRequiredMixin, CreateView):
|
|
model = models.SaleQuotation
|
|
form_class = forms.QuotationForm
|
|
template_name = 'sales/quotation_form.html'
|
|
|
|
def form_valid(self, form):
|
|
quotation = form.save()
|
|
selected_cars = form.cleaned_data.get("cars")
|
|
for car in selected_cars:
|
|
car_finance = car.finances
|
|
if car_finance:
|
|
models.SaleQuotationCar.objects.create(
|
|
quotation=quotation,
|
|
car=car,
|
|
)
|
|
|
|
messages.success(self.request, _("Quotation created successfully."))
|
|
return redirect('quotation_list')
|
|
|
|
|
|
class QuotationListView(LoginRequiredMixin, ListView):
|
|
model = models.SaleQuotation
|
|
template_name = "sales/quotation_list.html"
|
|
context_object_name = "quotations"
|
|
paginate_by = 10
|
|
|
|
def get_queryset(self):
|
|
status = self.request.GET.get("status")
|
|
queryset = models.SaleQuotation.objects.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
|
|
|
|
# Totals Calculation
|
|
context['vat_rate'] = settings.VAT_RATE
|
|
context['total_sales_before_vat'] = sum(item.car.selling_price * item.quantity for item in quotation.quotation_cars.all())
|
|
context['vat_amount'] = sum(item.car.vat_amount * item.quantity for item in quotation.quotation_cars.all())
|
|
context['total_sales_after_vat'] = context['total_sales_before_vat'] + context['vat_amount']
|
|
|
|
# Additional Costs
|
|
total_quantity = quotation.total_quantity
|
|
context['administration_fee'] = sum(item.car.administration_fee for item in quotation.quotation_cars.all())
|
|
context['transportation_fee'] = sum(item.car.transportation_fee for item in quotation.quotation_cars.all())
|
|
context['custom_card_fee'] = sum(item.car.custom_card_fee for item in quotation.quotation_cars.all())
|
|
context['registration_fee'] = sum(item.car.registration_fee for item in quotation.quotation_cars.all())
|
|
context['administration_fee_vat'] = sum(item.car.administration_fee_vat for item in quotation.quotation_cars.all())
|
|
context['transportation_fee_vat'] = sum(item.car.transportation_fee_vat for item in quotation.quotation_cars.all())
|
|
context['custom_card_fee_vat'] = sum(item.car.custom_card_fee_vat for item in quotation.quotation_cars.all())
|
|
context['administration_fee_total'] = sum(item.car.administration_fee + context['administration_fee_vat'] for item in quotation.quotation_cars.all())
|
|
context['transportation_fee_total'] = sum(item.car.transportation_fee + context['transportation_fee_vat'] for item in quotation.quotation_cars.all())
|
|
context['custom_card_fee_total'] = sum(item.car.custom_card_fee + context['custom_card_fee_vat'] for item in quotation.quotation_cars.all())
|
|
context['registration_fee_total'] = sum(item.car.registration_fee * total_quantity for item in quotation.quotation_cars.all())
|
|
|
|
return context
|
|
|
|
|
|
@login_required
|
|
def confirm_quotation(request, pk):
|
|
quotation = get_object_or_404(models.SaleQuotation, pk=pk)
|
|
try:
|
|
quotation.confirm()
|
|
models.SalesOrder.objects.create(
|
|
quotation=quotation,
|
|
total_amount=quotation.quotation_cars.aggregate(Sum("total_amount"))["total_amount__sum"],
|
|
)
|
|
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"
|
|
|
|
|
|
class OrganizationListView(LoginRequiredMixin, ListView):
|
|
model = models.Organization
|
|
template_name = 'organizations/organization_list.html'
|
|
context_object_name = 'organizations'
|
|
|
|
|
|
class OrganizationDetailView(DetailView):
|
|
model = models.Organization
|
|
template_name = 'organizations/organization_detail.html'
|
|
context_object_name = 'organization'
|
|
|
|
|
|
class OrganizationCreateView(LoginRequiredMixin, CreateView):
|
|
model = models.Organization
|
|
form_class = forms.OrganizationForm
|
|
template_name = 'organizations/organization_form.html'
|
|
success_url = reverse_lazy('organization_list')
|
|
|
|
def form_valid(self, form):
|
|
if form.is_valid():
|
|
form.instance.dealer = self.request.user.dealer
|
|
form.save()
|
|
success_message = "Organization created successfully."
|
|
return super().form_valid(form)
|
|
else:
|
|
return form.errors
|
|
|
|
|
|
class OrganizationUpdateView(LoginRequiredMixin, UpdateView):
|
|
model = models.Organization
|
|
form_class = forms.OrganizationForm
|
|
template_name = 'organizations/organization_form.html'
|
|
success_url = reverse_lazy('organization_list')
|
|
success_message = "Organization updated successfully."
|
|
|
|
|
|
class OrganizationDeleteView(LoginRequiredMixin, DeleteView):
|
|
model = models.Organization
|
|
template_name = 'organizations/organization_confirm_delete.html'
|
|
success_url = reverse_lazy('organization_list')
|
|
success_message = "Organization deleted successfully."
|
|
|
|
|
|
class RepresentativeListView(LoginRequiredMixin, ListView):
|
|
model = models.Representative
|
|
template_name = 'representatives/representative_list.html'
|
|
context_object_name = 'representatives'
|
|
|
|
|
|
class RepresentativeDetailView(DetailView):
|
|
model = models.Representative
|
|
template_name = 'representatives/representative_detail.html'
|
|
context_object_name = 'representative'
|
|
|
|
|
|
class RepresentativeCreateView(LoginRequiredMixin, CreateView):
|
|
model = models.Representative
|
|
form_class = forms.RepresentativeForm
|
|
template_name = 'representatives/representative_form.html'
|
|
success_url = reverse_lazy('representative_list')
|
|
|
|
def form_valid(self, form):
|
|
if form.is_valid():
|
|
form.instance.dealer = self.request.user.dealer
|
|
form.save()
|
|
success_message = "Representative created successfully."
|
|
return super().form_valid(form)
|
|
else:
|
|
return form.errors
|
|
|
|
|
|
class RepresentativeUpdateView(LoginRequiredMixin, 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, DeleteView):
|
|
model = models.Representative
|
|
template_name = 'representatives/representative_confirm_delete.html'
|
|
success_url = reverse_lazy('representative_list')
|
|
success_message = "Representative deleted successfully."
|
|
|
|
|