This commit is contained in:
Marwan Alwali 2025-04-24 19:12:18 +03:00
parent 780eb1b35c
commit e551064560
6 changed files with 718 additions and 997 deletions

View File

@ -88,8 +88,8 @@ def decodevin(vin):
return result return result
elif result:=elm(vin): elif result:=elm(vin):
return result return result
elif result:=decode_vin_haikalna(vin): # elif result:=decode_vin_haikalna(vin):
return result # return result
else: else:
return None return None
@ -118,7 +118,6 @@ def decode_vin(vin):
"model": v.Model, "model": v.Model,
"modelYear": v.ModelYear, "modelYear": v.ModelYear,
} }
print(data)
return data if all([x for x in data.values()]) else None return data if all([x for x in data.values()]) else None
@ -160,5 +159,4 @@ def elm(vin):
"model": response["data"]["model"], "model": response["data"]["model"],
"modelYear": response["data"]["modelYear"], "modelYear": response["data"]["modelYear"],
} }
print(data)
return data if all([x for x in data.values()]) else None return data if all([x for x in data.values()]) else None

View File

@ -96,7 +96,6 @@ def create_car_location(sender, instance, created, **kwargs):
showroom=instance.dealer, showroom=instance.dealer,
description=f"Initial location set for car {instance.vin}.", description=f"Initial location set for car {instance.vin}.",
) )
print("Car Location created")
except Exception as e: except Exception as e:
print(f"Failed to create CarLocation for car {instance.vin}: {e}") print(f"Failed to create CarLocation for car {instance.vin}: {e}")

View File

@ -11,7 +11,6 @@ from calendar import month_name
from pyzbar.pyzbar import decode from pyzbar.pyzbar import decode
from urllib.parse import urlparse, urlunparse from urllib.parse import urlparse, urlunparse
#####################################################################
from django.db.models.deletion import RestrictedError from django.db.models.deletion import RestrictedError
# Django # Django
@ -53,7 +52,6 @@ from django.views.generic import (
TemplateView, TemplateView,
ArchiveIndexView, ArchiveIndexView,
) )
#####################################################################
# Django Ledger # Django Ledger
from django_ledger.io import roles from django_ledger.io import roles
@ -123,9 +121,7 @@ from django_ledger.views.mixins import (
DjangoLedgerSecurityMixIn, DjangoLedgerSecurityMixIn,
EntityUnitMixIn, EntityUnitMixIn,
) )
#####################################################################
# Other # Other
from plans.models import Plan from plans.models import Plan
from inventory.filters import AccountModelFilter from inventory.filters import AccountModelFilter
@ -153,7 +149,6 @@ from .utils import (
CarTransfer, CarTransfer,
) )
#####################################################################
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
@ -306,30 +301,6 @@ def dealer_signup(request, *args, **kwargs):
) )
# class OTPView(View, LoginRequiredMixin):
# template_name = "account/otp_verification.html"
#
# def get(self, request, *args, **kwargs):
# # device = default_device(request.user)
# # device.generate_challenge()
# 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(LoginRequiredMixin, TemplateView): class HomeView(LoginRequiredMixin, TemplateView):
""" """
@ -354,58 +325,6 @@ class HomeView(LoginRequiredMixin, TemplateView):
return redirect("welcome") return redirect("welcome")
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# dealer = get_user_type(self.request)
#
# try:
# # Fetch car-related statistics
# 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
#
# # Fetch financial statistics
# stats = models.CarFinance.objects.aggregate(
# total_cost_price=Sum("cost_price"),
# total_selling_price=Sum("selling_price"),
# )
# total_cost_price = stats.get("total_cost_price", 0) or 0
# total_selling_price = stats.get("total_selling_price", 0) or 0
# total_profit = total_selling_price - total_cost_price
#
# # Prepare context data
# context.update({
# "dealer": dealer,
# "total_cars": total_cars,
# "cars_in_house": cars_in_house,
# "cars_outside": cars_outside,
# "total_reservations": total_reservations,
# "total_cost_price": total_cost_price,
# "total_selling_price": total_selling_price,
# "total_profit": total_profit,
# })
#
# except Exception as e:
# # Log the error (you can use Django's logging framework)
# print(f"Error fetching data: {e}")
# # Provide default values in case of an error
# context.update({
# "dealer": dealer,
# "total_cars": 0,
# "cars_in_house": 0,
# "cars_outside": 0,
# "total_reservations": 0,
# "total_cost_price": 0,
# "total_selling_price": 0,
# "total_profit": 0,
# })
# return context
class TestView(TemplateView): class TestView(TemplateView):
""" """
@ -594,12 +513,6 @@ class SalesDashboard(LoginRequiredMixin, TemplateView):
context["staff"] = staff context["staff"] = staff
context["total_cars"] = total_cars context["total_cars"] = total_cars
context["total_reservations"] = total_reservations context["total_reservations"] = total_reservations
# context["total_cost_price"] = total_cost_price
# context["total_selling_price"] = total_selling_price
# context["total_profit"] = total_profit
# context['new_leads'] = new_leads
# context['pending_leads'] = pending_leads
# context['canceled_leads'] = canceled_leads
context["reserved_percentage"] = reserved_percentage context["reserved_percentage"] = reserved_percentage
context["sold_percentage"] = sold_percentage context["sold_percentage"] = sold_percentage
context["available_cars"] = available_cars context["available_cars"] = available_cars
@ -609,10 +522,6 @@ class SalesDashboard(LoginRequiredMixin, TemplateView):
context["damaged_cars"] = damaged_cars context["damaged_cars"] = damaged_cars
context["transfer_cars"] = transfer_cars context["transfer_cars"] = transfer_cars
context["car"] = json.dumps(car_by_make) context["car"] = json.dumps(car_by_make)
# context['customers'] = customers
# context['staff'] = staff
# context['total_leads'] = total_leads
# context['invoices'] = invoices
return context return context
@ -635,7 +544,6 @@ class WelcomeView(TemplateView):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
dealer = get_user_type(self.request) dealer = get_user_type(self.request)
plan_list = Plan.objects.all() plan_list = Plan.objects.all()
# pricing = PlanPricing.objects.filter(plan=plan).
context["plan_list"] = plan_list context["plan_list"] = plan_list
return context return context
@ -662,7 +570,6 @@ class CarCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
template_name = "inventory/car_form.html" template_name = "inventory/car_form.html"
permission_required = ["inventory.add_car"] permission_required = ["inventory.add_car"]
# success_url = reverse_lazy('inventory_stats')
def get_form(self, form_class=None): def get_form(self, form_class=None):
form = super().get_form(form_class) form = super().get_form(form_class)
@ -898,49 +805,54 @@ class AjaxHandlerView(LoginRequiredMixin, View):
return JsonResponse(serialized_options, safe=False) return JsonResponse(serialized_options, safe=False)
import cv2
import numpy as np
from pyzbar.pyzbar import decode
from django.views import View
from django.shortcuts import render, get_object_or_404, redirect
from django.http import JsonResponse
from django.contrib.auth.mixins import LoginRequiredMixin
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from django.urls import reverse
from . import models # Adjust to your project structure
@method_decorator(csrf_exempt, name="dispatch") @method_decorator(csrf_exempt, name="dispatch")
class SearchCodeView(LoginRequiredMixin, View): class SearchCodeView(LoginRequiredMixin, View):
"""
View to handle barcode/QR code scanning and car detail retrieval.
This class is responsible for rendering the form page for scanning and processing
images to decode VIN codes using uploaded images. It handles both GET and POST
requests. Upon successfully decoding a VIN, it redirects to the car detail page.
:ivar template_name: Path to the template used for the form rendering.
:type template_name: str
"""
template_name = "inventory/scan_vin.html" template_name = "inventory/scan_vin.html"
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
"""Render the form page."""
return render(request, self.template_name) return render(request, self.template_name)
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
image_file = request.FILES.get("image") image_file = request.FILES.get("image")
if image_file: if not 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"}) return JsonResponse({"success": False, "error": "No image provided"})
try:
np_arr = np.frombuffer(image_file.read(), np.uint8)
image = cv2.imdecode(np_arr, cv2.IMREAD_COLOR)
if image is None:
raise ValueError("Invalid image format")
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
decoded_objects = decode(gray)
if not decoded_objects:
return JsonResponse({"success": False, "error": "No QR/Barcode detected"})
code = decoded_objects[0].data.decode("utf-8").strip()
car = get_object_or_404(models.Car, vin=code)
return JsonResponse({
"success": True,
"code": code,
"redirect_url": reverse("car_detail", args=[car.pk])
})
except Exception as e:
return JsonResponse({"success": False, "error": str(e)})
class CarInventory(LoginRequiredMixin, PermissionRequiredMixin, ListView): class CarInventory(LoginRequiredMixin, PermissionRequiredMixin, ListView):
""" """
@ -1874,17 +1786,15 @@ class DealerDetailView(LoginRequiredMixin, DetailView):
return models.Dealer.objects.annotate( return models.Dealer.objects.annotate(
staff_count=Coalesce( staff_count=Coalesce(
Count("staff"), Value(0) Count("staff"), Value(0)
) # Get the number of staff members )
) )
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
dealer = self.object dealer = self.object
car_makes = models.CarMake.objects.filter(car_dealers__dealer=dealer) car_makes = models.CarMake.objects.filter(car_dealers__dealer=dealer)
# Fetch current staff count from the annotated queryset
staff_count = dealer.staff_count staff_count = dealer.staff_count
cars_count = models.Car.objects.filter(dealer=dealer).count() cars_count = models.Car.objects.filter(dealer=dealer).count()
# Get the quota value dynamically
quota_dict = get_user_quota(dealer.user) quota_dict = get_user_quota(dealer.user)
allowed_users = quota_dict.get("Users", None) allowed_users = quota_dict.get("Users", None)
@ -2116,7 +2026,6 @@ def CustomerCreateView(request):
): ):
messages.error(request, _("Customer with this email already exists.")) messages.error(request, _("Customer with this email already exists."))
else: else:
# Create customer name
customer_name = ( customer_name = (
f"{form.cleaned_data['first_name']} " f"{form.cleaned_data['first_name']} "
f"{form.cleaned_data['last_name']}" f"{form.cleaned_data['last_name']}"
@ -2126,7 +2035,6 @@ def CustomerCreateView(request):
for x in request.POST for x in request.POST
if x != "csrfmiddlewaretoken" if x != "csrfmiddlewaretoken"
} }
# Create customer instance
try: try:
customer = dealer.entity.create_customer( customer = dealer.entity.create_customer(
commit=False, commit=False,
@ -2137,7 +2045,6 @@ def CustomerCreateView(request):
"email": form.cleaned_data["email"], "email": form.cleaned_data["email"],
}, },
) )
# customer.additional_info = {}
customer.additional_info.update({"customer_info": customer_dict}) customer.additional_info.update({"customer_info": customer_dict})
customer.additional_info.update({"type": "customer"}) customer.additional_info.update({"type": "customer"})
customer.save() customer.save()
@ -2148,7 +2055,6 @@ def CustomerCreateView(request):
except Exception as e: except Exception as e:
messages.error(request, _(f"An error occurred: {str(e)}")) messages.error(request, _(f"An error occurred: {str(e)}"))
else: else:
# Form is invalid, show errors
messages.error(request, _("Please correct the errors below")) messages.error(request, _("Please correct the errors below"))
return render(request, "customers/customer_form.html", {"form": form}) return render(request, "customers/customer_form.html", {"form": form})
@ -3636,7 +3542,7 @@ def sales_list_view(request):
transactions = ItemTransactionModel.objects.for_entity( transactions = ItemTransactionModel.objects.for_entity(
entity_slug=entity.slug, user_model=dealer.user entity_slug=entity.slug, user_model=dealer.user
) ).order_by('created')
paginator = Paginator(transactions, 10) paginator = Paginator(transactions, 10)
page_number = request.GET.get("page") page_number = request.GET.get("page")
page_obj = paginator.get_page(page_number) page_obj = paginator.get_page(page_number)

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -1,48 +1,73 @@
<div class="row-fluid p-2"> {% extends 'base.html' %}
<form id="car-form"> {% load static i18n%}
<div>
<label for="vin_no">VIN/Barcode/QR Code:</label> {% block content %}
<input type="text" id="vin_no" name="vin_no" readonly> <div class="container my-4">
<button type="button" id="capture-btn">Capture Code</button> <h3 class="mb-4">{{ _("Scan Vehicle Code")}}</h3>
<form id="car-form" class="mb-3">
<div class="mb-3">
<label for="vin_no" class="form-label">{{ _("VIN / Barcode / QR Code")}}</label>
<input type="text" class="form-control" id="vin_no" name="vin_no" readonly>
</div>
<div class="d-flex gap-2">
<button type="button" class="btn btn-primary" id="capture-btn">{{ _("Start Scanning")}}</button>
<button type="submit" class="btn btn-success">{{ _("Search") }}</button>
</div> </div>
<button type="submit">Search</button>
</form> </form>
<!-- Camera Stream --> <div id="camera-container" class="my-3" style="display:none;">
<div id="camera-container" style="display:none;"> <video id="camera" class="border rounded" autoplay playsinline width="100%"></video>
<video id="camera" autoplay playsinline width="400"></video> <div class="mt-2 d-flex gap-2">
<button id="toggle-btn">Toggle Camera</button> <button class="btn btn-warning" id="toggle-btn">{{ _("Switch Camera")}}</button>
<button id="scan-btn">Scan</button> <button class="btn btn-info" id="scan-btn">{{ _("Scan") }}</button>
</div>
</div> </div>
<p id="result"></p> <div id="result" class="alert mt-3" style="display:none;"></div>
</div> </div>
<script> <script>
let captureBtn = document.getElementById('capture-btn'); function getCookie(name) {
let cameraContainer = document.getElementById('camera-container'); let cookieValue = null;
let videoElement = document.getElementById('camera'); if (document.cookie && document.cookie !== '') {
let toggleBtn = document.getElementById('toggle-btn'); const cookies = document.cookie.split(';');
let scanBtn = document.getElementById('scan-btn'); for (let cookie of cookies) {
let vinInput = document.getElementById('vin_no'); cookie = cookie.trim();
let resultElement = document.getElementById('result'); if (cookie.startsWith(name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
const captureBtn = document.getElementById('capture-btn');
const cameraContainer = document.getElementById('camera-container');
const videoElement = document.getElementById('camera');
const toggleBtn = document.getElementById('toggle-btn');
const scanBtn = document.getElementById('scan-btn');
const vinInput = document.getElementById('vin_no');
const resultElement = document.getElementById('result');
const form = document.getElementById('car-form');
let mediaStream = null; let mediaStream = null;
let currentCameraIndex = 0; let currentCameraIndex = 0;
let cameras = []; let cameras = [];
// List available cameras form.addEventListener('submit', function (e) {
e.preventDefault(); // Prevent form reload
});
async function getCameras() { async function getCameras() {
const devices = await navigator.mediaDevices.enumerateDevices(); const devices = await navigator.mediaDevices.enumerateDevices();
cameras = devices.filter(device => device.kind === 'videoinput'); cameras = devices.filter(device => device.kind === 'videoinput');
} }
// Start the camera with the given device ID
async function startCamera(deviceId) { async function startCamera(deviceId) {
if (mediaStream) { if (mediaStream) {
mediaStream.getTracks().forEach(track => track.stop()); // Stop the current stream mediaStream.getTracks().forEach(track => track.stop());
} }
mediaStream = await navigator.mediaDevices.getUserMedia({ mediaStream = await navigator.mediaDevices.getUserMedia({
@ -52,14 +77,18 @@
videoElement.srcObject = mediaStream; videoElement.srcObject = mediaStream;
} }
// Toggle between cameras
toggleBtn.addEventListener('click', async () => { toggleBtn.addEventListener('click', async () => {
currentCameraIndex = (currentCameraIndex + 1) % cameras.length; if (cameras.length > 1) {
await startCamera(cameras[currentCameraIndex].deviceId); currentCameraIndex = (currentCameraIndex + 1) % cameras.length;
await startCamera(cameras[currentCameraIndex].deviceId);
}
}); });
// Capture image and send to backend
scanBtn.addEventListener('click', () => { scanBtn.addEventListener('click', () => {
resultElement.style.display = 'block';
resultElement.className = 'alert alert-secondary';
resultElement.textContent = 'Scanning...';
const canvas = document.createElement('canvas'); const canvas = document.createElement('canvas');
canvas.width = videoElement.videoWidth; canvas.width = videoElement.videoWidth;
canvas.height = videoElement.videoHeight; canvas.height = videoElement.videoHeight;
@ -70,42 +99,52 @@
const formData = new FormData(); const formData = new FormData();
formData.append('image', blob, 'code_image.jpg'); formData.append('image', blob, 'code_image.jpg');
fetch('{% url 'car_search' %}', { fetch('{% url "car_search" %}', {
method: 'POST', method: 'POST',
headers: { 'X-CSRFToken': '{% csrf_token %}' }, headers: {
"X-Requested-With": "XMLHttpRequest",
'X-CSRFToken': getCookie('csrftoken')
},
body: formData body: formData
}) })
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if (data.success) { if (data.success) {
vinInput.value = data.code; if (data.redirect_url) {
resultElement.textContent = `Code found: ${data.code}`; window.location.href = data.redirect_url;
} else { } else {
resultElement.textContent = `Error: ${data.error}`; vinInput.value = data.code;
resultElement.className = 'alert alert-success';
resultElement.textContent = `Code found: ${data.code}`;
} }
}) } else {
.catch(err => { resultElement.className = 'alert alert-danger';
console.error('Error processing code:', err); resultElement.textContent = `Error: ${data.error}`;
resultElement.textContent = 'Error processing code.'; }
}) })
.finally(() => { .catch(err => {
stopCamera(); resultElement.className = 'alert alert-danger';
}); resultElement.textContent = 'Unexpected error occurred.';
console.error(err);
})
.finally(() => {
stopCamera();
});
}); });
}); });
// Open camera and start video stream
captureBtn.addEventListener('click', async () => { captureBtn.addEventListener('click', async () => {
cameraContainer.style.display = 'block'; cameraContainer.style.display = 'block';
await getCameras(); await getCameras();
if (cameras.length > 0) { if (cameras.length > 0) {
await startCamera(cameras[currentCameraIndex].deviceId); await startCamera(cameras[currentCameraIndex].deviceId);
} else { } else {
resultElement.className = 'alert alert-warning';
resultElement.textContent = 'No cameras found.'; resultElement.textContent = 'No cameras found.';
resultElement.style.display = 'block';
} }
}); });
// Stop the camera stream
function stopCamera() { function stopCamera() {
if (mediaStream) { if (mediaStream) {
mediaStream.getTracks().forEach(track => track.stop()); mediaStream.getTracks().forEach(track => track.stop());
@ -113,5 +152,5 @@
} }
cameraContainer.style.display = 'none'; cameraContainer.style.display = 'none';
} }
</script> </script>
{% endblock content %}