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
elif result:=elm(vin):
return result
elif result:=decode_vin_haikalna(vin):
return result
# elif result:=decode_vin_haikalna(vin):
# return result
else:
return None
@ -118,7 +118,6 @@ def decode_vin(vin):
"model": v.Model,
"modelYear": v.ModelYear,
}
print(data)
return data if all([x for x in data.values()]) else None
@ -160,5 +159,4 @@ def elm(vin):
"model": response["data"]["model"],
"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,
description=f"Initial location set for car {instance.vin}.",
)
print("Car Location created")
except Exception as 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 urllib.parse import urlparse, urlunparse
#####################################################################
from django.db.models.deletion import RestrictedError
# Django
@ -53,7 +52,6 @@ from django.views.generic import (
TemplateView,
ArchiveIndexView,
)
#####################################################################
# Django Ledger
from django_ledger.io import roles
@ -123,9 +121,7 @@ from django_ledger.views.mixins import (
DjangoLedgerSecurityMixIn,
EntityUnitMixIn,
)
#####################################################################
# Other
from plans.models import Plan
from inventory.filters import AccountModelFilter
@ -153,7 +149,6 @@ from .utils import (
CarTransfer,
)
#####################################################################
logger = logging.getLogger(__name__)
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):
"""
@ -354,58 +325,6 @@ class HomeView(LoginRequiredMixin, TemplateView):
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)
#
# 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):
"""
@ -594,12 +513,6 @@ class SalesDashboard(LoginRequiredMixin, TemplateView):
context["staff"] = staff
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
# context['new_leads'] = new_leads
# context['pending_leads'] = pending_leads
# context['canceled_leads'] = canceled_leads
context["reserved_percentage"] = reserved_percentage
context["sold_percentage"] = sold_percentage
context["available_cars"] = available_cars
@ -609,10 +522,6 @@ class SalesDashboard(LoginRequiredMixin, TemplateView):
context["damaged_cars"] = damaged_cars
context["transfer_cars"] = transfer_cars
context["car"] = json.dumps(car_by_make)
# context['customers'] = customers
# context['staff'] = staff
# context['total_leads'] = total_leads
# context['invoices'] = invoices
return context
@ -635,7 +544,6 @@ class WelcomeView(TemplateView):
context = super().get_context_data(**kwargs)
dealer = get_user_type(self.request)
plan_list = Plan.objects.all()
# pricing = PlanPricing.objects.filter(plan=plan).
context["plan_list"] = plan_list
return context
@ -662,7 +570,6 @@ class CarCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
template_name = "inventory/car_form.html"
permission_required = ["inventory.add_car"]
# success_url = reverse_lazy('inventory_stats')
def get_form(self, form_class=None):
form = super().get_form(form_class)
@ -898,49 +805,54 @@ class AjaxHandlerView(LoginRequiredMixin, View):
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")
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"
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:
if not image_file:
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):
"""
@ -1874,17 +1786,15 @@ class DealerDetailView(LoginRequiredMixin, DetailView):
return models.Dealer.objects.annotate(
staff_count=Coalesce(
Count("staff"), Value(0)
) # Get the number of staff members
)
)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
dealer = self.object
car_makes = models.CarMake.objects.filter(car_dealers__dealer=dealer)
# Fetch current staff count from the annotated queryset
staff_count = dealer.staff_count
cars_count = models.Car.objects.filter(dealer=dealer).count()
# Get the quota value dynamically
quota_dict = get_user_quota(dealer.user)
allowed_users = quota_dict.get("Users", None)
@ -2116,7 +2026,6 @@ def CustomerCreateView(request):
):
messages.error(request, _("Customer with this email already exists."))
else:
# Create customer name
customer_name = (
f"{form.cleaned_data['first_name']} "
f"{form.cleaned_data['last_name']}"
@ -2126,7 +2035,6 @@ def CustomerCreateView(request):
for x in request.POST
if x != "csrfmiddlewaretoken"
}
# Create customer instance
try:
customer = dealer.entity.create_customer(
commit=False,
@ -2137,7 +2045,6 @@ def CustomerCreateView(request):
"email": form.cleaned_data["email"],
},
)
# customer.additional_info = {}
customer.additional_info.update({"customer_info": customer_dict})
customer.additional_info.update({"type": "customer"})
customer.save()
@ -2148,7 +2055,6 @@ def CustomerCreateView(request):
except Exception as e:
messages.error(request, _(f"An error occurred: {str(e)}"))
else:
# Form is invalid, show errors
messages.error(request, _("Please correct the errors below"))
return render(request, "customers/customer_form.html", {"form": form})
@ -3636,7 +3542,7 @@ def sales_list_view(request):
transactions = ItemTransactionModel.objects.for_entity(
entity_slug=entity.slug, user_model=dealer.user
)
).order_by('created')
paginator = Paginator(transactions, 10)
page_number = request.GET.get("page")
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">
<form id="car-form">
<div>
<label for="vin_no">VIN/Barcode/QR Code:</label>
<input type="text" id="vin_no" name="vin_no" readonly>
<button type="button" id="capture-btn">Capture Code</button>
{% extends 'base.html' %}
{% load static i18n%}
{% block content %}
<div class="container my-4">
<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>
<button type="submit">Search</button>
</form>
<!-- Camera Stream -->
<div id="camera-container" style="display:none;">
<video id="camera" autoplay playsinline width="400"></video>
<button id="toggle-btn">Toggle Camera</button>
<button id="scan-btn">Scan</button>
<div id="camera-container" class="my-3" style="display:none;">
<video id="camera" class="border rounded" autoplay playsinline width="100%"></video>
<div class="mt-2 d-flex gap-2">
<button class="btn btn-warning" id="toggle-btn">{{ _("Switch Camera")}}</button>
<button class="btn btn-info" id="scan-btn">{{ _("Scan") }}</button>
</div>
</div>
<p id="result"></p>
<div id="result" class="alert mt-3" style="display:none;"></div>
</div>
<script>
let captureBtn = document.getElementById('capture-btn');
let cameraContainer = document.getElementById('camera-container');
let videoElement = document.getElementById('camera');
let toggleBtn = document.getElementById('toggle-btn');
let scanBtn = document.getElementById('scan-btn');
let vinInput = document.getElementById('vin_no');
let resultElement = document.getElementById('result');
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let cookie of cookies) {
cookie = cookie.trim();
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 currentCameraIndex = 0;
let cameras = [];
// List available cameras
form.addEventListener('submit', function (e) {
e.preventDefault(); // Prevent form reload
});
async function getCameras() {
const devices = await navigator.mediaDevices.enumerateDevices();
cameras = devices.filter(device => device.kind === 'videoinput');
}
// Start the camera with the given device ID
async function startCamera(deviceId) {
if (mediaStream) {
mediaStream.getTracks().forEach(track => track.stop()); // Stop the current stream
mediaStream.getTracks().forEach(track => track.stop());
}
mediaStream = await navigator.mediaDevices.getUserMedia({
@ -52,14 +77,18 @@
videoElement.srcObject = mediaStream;
}
// Toggle between cameras
toggleBtn.addEventListener('click', async () => {
currentCameraIndex = (currentCameraIndex + 1) % cameras.length;
await startCamera(cameras[currentCameraIndex].deviceId);
if (cameras.length > 1) {
currentCameraIndex = (currentCameraIndex + 1) % cameras.length;
await startCamera(cameras[currentCameraIndex].deviceId);
}
});
// Capture image and send to backend
scanBtn.addEventListener('click', () => {
resultElement.style.display = 'block';
resultElement.className = 'alert alert-secondary';
resultElement.textContent = 'Scanning...';
const canvas = document.createElement('canvas');
canvas.width = videoElement.videoWidth;
canvas.height = videoElement.videoHeight;
@ -70,42 +99,52 @@
const formData = new FormData();
formData.append('image', blob, 'code_image.jpg');
fetch('{% url 'car_search' %}', {
fetch('{% url "car_search" %}', {
method: 'POST',
headers: { 'X-CSRFToken': '{% csrf_token %}' },
headers: {
"X-Requested-With": "XMLHttpRequest",
'X-CSRFToken': getCookie('csrftoken')
},
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
vinInput.value = data.code;
resultElement.textContent = `Code found: ${data.code}`;
.then(response => response.json())
.then(data => {
if (data.success) {
if (data.redirect_url) {
window.location.href = data.redirect_url;
} else {
resultElement.textContent = `Error: ${data.error}`;
vinInput.value = data.code;
resultElement.className = 'alert alert-success';
resultElement.textContent = `Code found: ${data.code}`;
}
})
.catch(err => {
console.error('Error processing code:', err);
resultElement.textContent = 'Error processing code.';
})
.finally(() => {
stopCamera();
});
} else {
resultElement.className = 'alert alert-danger';
resultElement.textContent = `Error: ${data.error}`;
}
})
.catch(err => {
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 () => {
cameraContainer.style.display = 'block';
await getCameras();
if (cameras.length > 0) {
await startCamera(cameras[currentCameraIndex].deviceId);
} else {
resultElement.className = 'alert alert-warning';
resultElement.textContent = 'No cameras found.';
resultElement.style.display = 'block';
}
});
// Stop the camera stream
function stopCamera() {
if (mediaStream) {
mediaStream.getTracks().forEach(track => track.stop());
@ -113,5 +152,5 @@
}
cameraContainer.style.display = 'none';
}
</script>
</script>
{% endblock content %}