diff --git a/inventory/admin.py b/inventory/admin.py index 31e59ca1..68f3a6f7 100644 --- a/inventory/admin.py +++ b/inventory/admin.py @@ -72,6 +72,7 @@ admin.site.register(models.UserActivityLog) admin.site.register(models.DealersMake) admin.site.register(models.ExtraInfo) admin.site.register(models.Ticket) +admin.site.register(models.UserRegistration) @admin.register(models.Car) diff --git a/inventory/forms.py b/inventory/forms.py index cd1fa83d..211e32db 100644 --- a/inventory/forms.py +++ b/inventory/forms.py @@ -9,7 +9,7 @@ from plans.models import PlanPricing from django.contrib.auth import get_user_model from inventory.validators import SaudiPhoneNumberValidator -from .models import CustomGroup, Status, Stage +from .models import CustomGroup, Status, Stage, UserRegistration from .mixins import AddClassMixin from django_ledger.forms.invoice import ( InvoiceModelCreateForm as InvoiceModelCreateFormBase, @@ -2255,3 +2255,10 @@ class TicketResolutionForm(forms.ModelForm): super().__init__(*args, **kwargs) # Limit status choices to resolution options self.fields["status"].choices = [("resolved", "Resolved"), ("closed", "Closed")] + + + +class CarDealershipRegistrationForm(forms.ModelForm): + class Meta: + model = UserRegistration + fields = "__all__" \ No newline at end of file diff --git a/inventory/models.py b/inventory/models.py index 0237332c..c5aca880 100644 --- a/inventory/models.py +++ b/inventory/models.py @@ -3858,3 +3858,42 @@ class CarImage(models.Model): self.id, task_name=f"generate_car_image_{self.car.vin}", ) + + + +class UserRegistration(models.Model): + name = models.CharField(_("Name"), max_length=255) + arabic_name = models.CharField(_("Arabic Name"), max_length=255) + email = models.EmailField(_("email address"), unique=True) + phone_number = models.CharField( + max_length=255, + verbose_name=_("Phone Number"), + validators=[SaudiPhoneNumberValidator()], + ) + crn = models.CharField(_("Commercial Registration Number"), max_length=10, unique=True) + vrn = models.CharField(_("Vehicle Registration Number"), max_length=15, unique=True) + address = models.TextField(_("Address")) + password = models.CharField(_("Password"), max_length=255) + is_created = models.BooleanField(default=False) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + REQUIRED_FIELDS = ["username", "arabic_name", "crn", "vrn", "address", "phone_number"] + + def __str__(self): + return self.email + + def create_account(self): + from django_q.tasks import async_task + from .tasks import create_user_dealer + + password = User.objects.make_random_password() + + if self.is_created: + return + dealer = create_user_dealer(self.email,password,self.name,self.arabic_name,self.phone,self.crn,self.vrn,self.address) + + if dealer: + self.is_created = True + self.password = password + self.save() \ No newline at end of file diff --git a/inventory/signals.py b/inventory/signals.py index 8c34c395..87ad08cb 100644 --- a/inventory/signals.py +++ b/inventory/signals.py @@ -72,12 +72,12 @@ User = get_user_model() # check with marwan -@receiver(post_save, sender=models.Car) -def create_dealers_make(sender, instance, created, **kwargs): - if created: - models.DealersMake.objects.get_or_create( - dealer=instance.dealer, car_make=instance.id_car_make - ) +# @receiver(post_save, sender=models.Car) +# def create_dealers_make(sender, instance, created, **kwargs): +# if created: +# models.DealersMake.objects.get_or_create( +# dealer=instance.dealer, car_make=instance.id_car_make +# ) @receiver(post_save, sender=models.Car) @@ -325,6 +325,7 @@ def create_item_model(sender, instance, created, **kwargs): inventory.save() else: instance.item_model.default_amount = instance.marked_price + instance.item_model.save() # inventory = entity.create_item_inventory( # name=instance.vin, @@ -1386,3 +1387,15 @@ def handle_car_image(sender, instance, created, **kwargs): except Exception as e: logger.error(f"Error handling car image for {car.vin}: {e}") + + +@receiver(post_save, sender=models.UserRegistration) +def handle_user_registration(sender, instance, created, **kwargs): + if instance.is_created: + send_email( + "Account Created", + f"Your account has been created and you can login with your email and password: {instance.password}", + settings.DEFAULT_FROM_EMAIL, + [instance.email], + fail_silently=False, + ) \ No newline at end of file diff --git a/inventory/tasks.py b/inventory/tasks.py index 7fca2852..3cd79b94 100644 --- a/inventory/tasks.py +++ b/inventory/tasks.py @@ -1,3 +1,4 @@ +import time import base64 import logging import requests @@ -123,7 +124,7 @@ def retry_entity_creation(dealer_id, retry_count=0): Retry entity creation if initial attempt failed """ from .models import Dealer - from yourapp.models import EntityModel + from django_ledger.models import EntityModel max_retries = 3 diff --git a/inventory/urls.py b/inventory/urls.py index 3e2f6ee8..e58a905c 100644 --- a/inventory/urls.py +++ b/inventory/urls.py @@ -2,14 +2,16 @@ from inventory.utils import get_user_type from . import views from django.urls import path from django.urls import reverse_lazy -from django.views.generic import RedirectView +from django.views.generic import RedirectView,TemplateView from django_tables2.export.export import TableExport from django.conf.urls import handler403, handler400, handler404, handler500 urlpatterns = [ # main URLs path("", views.WelcomeView, name="welcome"), - path("signup/", views.dealer_signup, name="account_signup"), + # path("signup/", views.dealer_signup, name="account_signup"), + path('signup/', views.CarDealershipSignUpView.as_view(), name='account_signup'), + path('success/', TemplateView.as_view(template_name='account/success.html'), name='registration_success'), path("", views.HomeView, name="home"), path('refund-policy/',views.refund_policy,name='refund_policy'), path("/", views.HomeView, name="home"), diff --git a/inventory/validators.py b/inventory/validators.py index f5fc43c7..777a8391 100644 --- a/inventory/validators.py +++ b/inventory/validators.py @@ -6,7 +6,7 @@ import re class SaudiPhoneNumberValidator(RegexValidator): def __init__(self, *args, **kwargs): super().__init__( - regex=r"^(\+9665|05)[0-9]{8}$", + regex=r"^(\+9665|05|9665)[0-9]{8}$", message=_("Enter a valid Saudi phone number (05XXXXXXXX or +9665XXXXXXXX)"), ) diff --git a/inventory/views.py b/inventory/views.py index 679d47ca..60451bbd 100644 --- a/inventory/views.py +++ b/inventory/views.py @@ -5125,7 +5125,8 @@ def create_sale_order(request, dealer_slug, pk): f"KeyError: 'car_info' or 'status' key missing when attempting to update status to 'sold' for item.item_model PK: {getattr(item.item_model, 'pk', 'N/A')}." ) pass - + item.item_model.car.sold_date=timezone.now() # to be checked added by faheed + item.item_model.car.save()# to be checked added byfaheed item.item_model.car.mark_as_sold() messages.success(request, "Sale Order created successfully") @@ -7539,17 +7540,17 @@ class NotificationListView(LoginRequiredMixin, ListView): def get_queryset(self): return models.Notification.objects.filter(user=self.request.user) - + def get_context_data(self, **kwargs): - + context = super().get_context_data(**kwargs) user_notifications = self.get_queryset() - + # Calculate the number of total, read and unread notifications context['total_count'] = user_notifications.count() context['read_count'] = user_notifications.filter(is_read=True).count() context['unread_count'] = user_notifications.filter(is_read=False).count() - + return context @@ -11078,7 +11079,7 @@ def purchase_report_view(request, dealer_slug): bills = po.get_po_bill_queryset() vendors = set([bill.vendor.vendor_name for bill in bills]) vendors_str = ", ".join(sorted(list(vendors))) if vendors else "N/A" - + data.append({ "po_number": po.po_number, "po_created": po.created, @@ -11194,7 +11195,7 @@ def car_sale_report_view(request, dealer_slug): cars_sold = cars_sold.filter(year=selected_year) if selected_stock_type: cars_sold = cars_sold.filter(stock_type=selected_stock_type) - + # Corrected: Apply date filters using the 'sold_date' field if start_date_str: try: @@ -11202,7 +11203,7 @@ def car_sale_report_view(request, dealer_slug): cars_sold = cars_sold.filter(sold_date__gte=start_date) except (ValueError, TypeError): pass - + if end_date_str: try: end_date = datetime.strptime(end_date_str, '%Y-%m-%d').date() @@ -11224,7 +11225,7 @@ def car_sale_report_view(request, dealer_slug): total_vat_from_additonals = sum([car.get_additional_services()['services_vat'] for car in cars_sold]) total_vat_collected = total_vat_on_cars + total_vat_from_additonals total_revenue_collected = total_revenue_from_cars + total_revenue_from_additonals - + total_discount = cars_sold.aggregate(total=Sum('discount_amount'))['total'] or 0 current_time = timezone.now().strftime("%Y-%m-%d %H:%M:%S") @@ -11263,7 +11264,7 @@ def car_sale_report_view(request, dealer_slug): @login_required def get_filtered_choices(request, dealer_slug): dealer = get_object_or_404(models.Dealer, slug=dealer_slug) - + # Get all filter parameters from the request selected_make = request.GET.get('make') selected_model = request.GET.get('model') @@ -11277,10 +11278,10 @@ def get_filtered_choices(request, dealer_slug): # Apply filters based on what is selected if selected_make: queryset = queryset.filter(id_car_make__name=selected_make) - + if selected_model: queryset = queryset.filter(id_car_model__name=selected_model) - + if selected_serie: queryset = queryset.filter(id_car_serie__name=selected_serie) @@ -11351,7 +11352,7 @@ def car_sale_report_csv_export(request, dealer_slug): cars_sold = cars_sold.filter(year=selected_year) if selected_stock_type: cars_sold = cars_sold.filter(stock_type=selected_stock_type) - + # Corrected: Apply date filters for CSV export if start_date_str: try: @@ -11669,6 +11670,18 @@ class CharOfAccountModelActionView(CharOfAccountModelActionViewBase): -#for refund policy -def refund_policy(request): - return render(request,'haikal_policy/refund_policy.html') +class CarDealershipSignUpView(CreateView): + model = models.UserRegistration + form_class = forms.CarDealershipRegistrationForm + template_name = 'account/signup-wizard.html' + success_url = reverse_lazy('registration_success') + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['title'] = _('Car Dealership Registration') + return context + + def form_valid(self, form): + response = super().form_valid(form) + messages.success(self.request, _('Your request has been submitted. We will contact you soon.')) + return response \ No newline at end of file diff --git a/requirements_prod.txt b/requirements_prod.txt index c1941c00..7592a4f2 100644 --- a/requirements_prod.txt +++ b/requirements_prod.txt @@ -58,6 +58,7 @@ django-widget-tweaks==1.5.0 djangorestframework==3.16.0 djhtml==3.0.8 djlint==1.36.4 +dnspython==2.7.0 docopt==0.6.2 EditorConfig==0.17.1 Faker==37.4.0 @@ -77,6 +78,8 @@ hyperlink==21.0.0 icalendar==6.3.1 idna==3.10 incremental==24.7.2 +iron-core==1.2.1 +iron-mq==0.9 jiter==0.10.0 jsbeautifier==1.15.4 json5==0.12.0 @@ -114,6 +117,7 @@ pycparser==2.22 pydantic==2.11.7 pydantic_core==2.33.2 Pygments==2.19.2 +pymongo==4.14.1 pyOpenSSL==25.1.0 python-dateutil==2.9.0.post0 python-dotenv==1.1.1 diff --git a/templates/account/signup-wizard.html b/templates/account/signup-wizard.html index 19fdf246..22ed1586 100644 --- a/templates/account/signup-wizard.html +++ b/templates/account/signup-wizard.html @@ -1,6 +1,7 @@ {% extends "welcome_base.html" %} {% load crispy_forms_filters %} {% load i18n static %} + {% block content %}
@@ -20,272 +21,27 @@
-

{% trans 'Sign Up' %}

-

{% trans 'Create your account today' %}

+

{% trans 'Car Dealership Registration' %}

+

{% trans 'Create your dealership account today' %}

-
- -
-
-
-
-
- - -
{% trans "Please enter a valid email address" %}
-
-
- - -
- {% trans "Password does not match. or length is less than 8 characters." %} -
-
-
- - * - -
- {% trans "Password does not match. or length is less than 8 characters." %} -
-
-
-
-
-
-
- - -
-
- - -
-
- - * - -
{% trans "Please enter a valid phone number" %}
-
-
-
-
-
-
- - -
-
- - -
-
- - -
-
-
-
-
-
-
- - -
-
-
-
-
{% trans 'You are all set!' %}
-

- {% trans 'Now you can access your account' %} -
- {% trans 'anytime' %} {% trans 'anywhere' %} -

- -
-
-
-
+ +
+ {% csrf_token %} +
+
+ {{ form|crispy }}
-
- + +
- +
- + {% include 'footer.html' %} {% endblock content %} @@ -416,7 +172,7 @@ document.addEventListener('DOMContentLoaded', function() { const button = event.relatedTarget; // Extract the URL from the button's data-url attribute const url = button.getAttribute('data-url'); - + // Select the modal body element const modalBody = contentModal.querySelector('.modal-body'); diff --git a/templates/account/success.html b/templates/account/success.html new file mode 100644 index 00000000..29246884 --- /dev/null +++ b/templates/account/success.html @@ -0,0 +1,35 @@ +{% extends "welcome_base.html" %} +{% load crispy_forms_filters %} +{% load i18n static %} + +{% block content %} +
+
+
+
+ +
+ {% trans 'home' %} + {% trans 'home' %} +
+
+
+

{% trans 'Account Created Successfully' %}

+

+ {% blocktrans %} + Thank you for registering at Haikal. We will contact you soon. + {% endblocktrans %} +

+
+
+
+
+
+{% endblock content %} diff --git a/templates/registration/signup.html b/templates/registration/signup.html new file mode 100644 index 00000000..fe3fac94 --- /dev/null +++ b/templates/registration/signup.html @@ -0,0 +1,396 @@ +{% extends "welcome_base.html" %} +{% load crispy_forms_filters %} +{% load i18n static %} +{% block content %} +
+
+
+
+ +
+ {% trans 'home' %} + {% trans 'home' %} +
+
+
+

{% trans 'Car Dealership Registration' %}

+

{% trans 'Create your dealership account today' %}

+
+
+ +
+
+
+
+
+ + +
{% trans "Please enter a valid email address" %}
+
+
+ + +
+ {% trans "Password does not match. or length is less than 8 characters." %} +
+
+
+ + * + +
+ {% trans "Password does not match. or length is less than 8 characters." %} +
+
+
+
+
+
+
+ + +
+
+ + +
+
+ + * + +
{% trans "Please enter a valid Saudi phone number (e.g., 05XXXXXXXX)" %}
+
+
+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+
+
+ + +
+
+
+
+
{% trans 'You are all set!' %}
+

+ {% trans 'Now you can access your dealership account' %} +
+ {% trans 'anytime' %} {% trans 'anywhere' %} +

+ +
+
+
+
+
+
+ +
+
+
+
+
+ {% include 'footer.html' %} + +{% endblock content %} +{% block customJS %} + + + + +{% endblock customJS %} \ No newline at end of file