update
This commit is contained in:
parent
780eb1b35c
commit
e551064560
@ -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
|
||||
|
||||
@ -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}")
|
||||
|
||||
|
||||
@ -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
@ -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 %}
|
||||
Loading…
x
Reference in New Issue
Block a user