diff --git a/inventory/management/commands/set_custom_permissions.py b/inventory/management/commands/set_custom_permissions.py new file mode 100644 index 00000000..23e4a9da --- /dev/null +++ b/inventory/management/commands/set_custom_permissions.py @@ -0,0 +1,16 @@ +from inventory import models +from django.contrib.auth.models import Permission +from django.core.management.base import BaseCommand +from django.contrib.contenttypes.models import ContentType +from django_ledger.models import EstimateModel,BillModel,AccountModel,LedgerModel + + +class Command(BaseCommand): + def handle(self, *args, **kwargs): + Permission.objects.get_or_create(name="Can view crm",codename="can_view_crm",content_type=ContentType.objects.get_for_model(models.Lead)) + Permission.objects.get_or_create(name="Can view sales",codename="can_view_sales",content_type=ContentType.objects.get_for_model(EstimateModel)) + Permission.objects.get_or_create(name="Can view reports",codename="can_view_reports",content_type=ContentType.objects.get_for_model(LedgerModel)) + Permission.objects.get_or_create(name="Can view inventory",codename="can_view_inventory",content_type=ContentType.objects.get_for_model(models.Car)) + Permission.objects.get_or_create(name="Can approve bill",codename="can_approve_billmodel",content_type=ContentType.objects.get_for_model(BillModel)) + Permission.objects.get_or_create(name="Can view financials",codename="can_view_financials",content_type=ContentType.objects.get_for_model(AccountModel)) + Permission.objects.get_or_create(name="Can approve estimate",codename="can_approve_estimatemodel",content_type=ContentType.objects.get_for_model(EstimateModel)) \ No newline at end of file diff --git a/inventory/models.py b/inventory/models.py index d6cf5676..2ba6e7cc 100644 --- a/inventory/models.py +++ b/inventory/models.py @@ -2550,15 +2550,6 @@ class CustomGroup(models.Model): pass def set_default_permissions(self): - Permission.objects.get_or_create(name="Can approve estimate",codename="can_approve_estimatemodel",content_type=ContentType.objects.get_for_model(EstimateModel)) - Permission.objects.get_or_create(name="Can approve bill",codename="can_approve_billmodel",content_type=ContentType.objects.get_for_model(BillModel)) - - Permission.objects.get_or_create(name="Can view inventory",codename="can_view_inventory",content_type=ContentType.objects.get_for_model(Car)) - Permission.objects.get_or_create(name="Can view sales",codename="can_view_sales",content_type=ContentType.objects.get_for_model(EstimateModel)) - Permission.objects.get_or_create(name="Can view crm",codename="can_view_crm",content_type=ContentType.objects.get_for_model(Lead)) - Permission.objects.get_or_create(name="Can view financials",codename="can_view_financials",content_type=ContentType.objects.get_for_model(AccountModel)) - Permission.objects.get_or_create(name="Can view reports",codename="can_view_reports",content_type=ContentType.objects.get_for_model(LedgerModel)) - self.clear_permissions() ###################################### ###################################### @@ -2698,7 +2689,6 @@ class CustomGroup(models.Model): "view_carcolors", "view_cartransfer", "view_saleorder", - ], ) self.set_permissions( @@ -2966,4 +2956,22 @@ class ExtraInfo(models.Model): verbose_name_plural = "Extra Info" def __str__(self): - return f"ExtraInfo for {self.content_object} ({self.content_type})" \ No newline at end of file + return f"ExtraInfo for {self.content_object} ({self.content_type})" + + @classmethod + def get_sale_orders(cls, staff=None,is_dealer=False): + if not staff and not is_dealer: + return [] + if is_dealer: + qs = ExtraInfo.objects.filter( + content_type=ContentType.objects.get_for_model(EstimateModel), + related_content_type=ContentType.objects.get_for_model(Staff), + ) + else: + qs = ExtraInfo.objects.filter( + content_type=ContentType.objects.get_for_model(EstimateModel), + related_content_type=ContentType.objects.get_for_model(Staff), + related_object_id=staff.pk, + ) + + return [x.content_object.sale_orders.first for x in qs] diff --git a/inventory/tasks.py b/inventory/tasks.py index 452c28fb..26667b61 100644 --- a/inventory/tasks.py +++ b/inventory/tasks.py @@ -1,10 +1,11 @@ from django.db import transaction from django_ledger.io import roles -from django.core.mail import send_mail -from django.utils.translation import gettext_lazy as _ -from inventory.models import DealerSettings, Dealer from django_q.tasks import async_task - +from django.core.mail import send_mail +from appointment.models import StaffMember +from django.contrib.auth.models import User,Group, Permission +from inventory.models import DealerSettings, Dealer +from django.utils.translation import gettext_lazy as _ def create_settings(pk): instance = Dealer.objects.get(pk=pk) @@ -1459,8 +1460,26 @@ def send_email(from_, to_, subject, message): async_task(send_mail,subject, message, from_email, recipient_list) -# @background -def long_running_task(duration): - """Example background task""" - print("Task completed") - return True +def create_user_dealer(email, password, name, arabic_name, phone, crn, vrn, address): + with transaction.atomic(): + user = User.objects.create(username=email, email=email) + user.set_password(password) + user.save() + group = Group.objects.create(name=f"{user.pk}-Admin") + user.groups.add(group) + for perm in Permission.objects.filter( + content_type__app_label__in=["inventory", "django_ledger"] + ): + group.permissions.add(perm) + + StaffMember.objects.create(user=user) + Dealer.objects.create( + user=user, + name=name, + arabic_name=arabic_name, + crn=crn, + vrn=vrn, + phone_number=phone, + address=address, + ) + diff --git a/inventory/utils.py b/inventory/utils.py index 6a50c28d..9a0438bd 100644 --- a/inventory/utils.py +++ b/inventory/utils.py @@ -1,33 +1,29 @@ import json +import secrets import datetime -from plans.models import AbstractOrder -from django.contrib.auth.models import Group, Permission -from django.db import transaction -from django.urls import reverse import requests from decimal import Decimal +from inventory import models +from django.urls import reverse +from django.conf import settings from django.utils import timezone from django_ledger.io import roles from django.contrib import messages from django.shortcuts import redirect -from django.core.exceptions import ObjectDoesNotExist -from django_ledger.models.journal_entry import JournalEntryModel -from django_ledger.models.transactions import TransactionModel -from inventory import models -from django.conf import settings +from django_q.tasks import async_task from django.core.mail import send_mail -from django.utils.translation import gettext_lazy as _ -from django_ledger.models.items import ItemModel +from plans.models import AbstractOrder from django_ledger.models import ( InvoiceModel, BillModel, VendorModel, ) +from django_ledger.models.items import ItemModel from django.utils.translation import get_language -from appointment.models import StaffMember -from django.contrib.auth.models import User -from django_q.tasks import async_task -import secrets +from django.core.exceptions import ObjectDoesNotExist +from django.utils.translation import gettext_lazy as _ +from django_ledger.models.transactions import TransactionModel +from django_ledger.models.journal_entry import JournalEntryModel import logging logger=logging.getLogger(__name__) @@ -1370,31 +1366,6 @@ def create_make_accounts(dealer): active=True, ) - -def create_user_dealer(email, password, name, arabic_name, phone, crn, vrn, address): - with transaction.atomic(): - user = User.objects.create(username=email, email=email) - user.set_password(password) - user.save() - group = Group.objects.create(name=f"{user.pk}-Admin") - user.groups.add(group) - for perm in Permission.objects.filter( - content_type__app_label__in=["inventory", "django_ledger"] - ): - group.permissions.add(perm) - - StaffMember.objects.create(user=user) - models.Dealer.objects.create( - user=user, - name=name, - arabic_name=arabic_name, - crn=crn, - vrn=vrn, - phone_number=phone, - address=address, - ) - - def handle_payment(request, order): url = "https://api.moyasar.com/v1/payments" callback_url = request.build_absolute_uri(reverse("payment_callback", args=[request.dealer.slug])) diff --git a/inventory/views.py b/inventory/views.py index 040bdef7..8c7e40d5 100644 --- a/inventory/views.py +++ b/inventory/views.py @@ -1,5 +1,6 @@ # Standard import os +import re import io import csv import cv2 @@ -48,8 +49,7 @@ from django.shortcuts import HttpResponse from django.db.models import Sum, F, Count from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger -from django.contrib.auth.models import User -from django.contrib.auth.models import Group +from django.contrib.auth.models import User,Group from django.db.models import Value from django.urls import reverse, reverse_lazy from django.utils import timezone, translation @@ -186,7 +186,6 @@ from .services import ( ) from .utils import ( CarFinanceCalculator, - create_user_dealer, get_car_finance_data, get_item_transactions, handle_payment, @@ -197,11 +196,11 @@ from .utils import ( set_invoice_payment, CarTransfer, ) -from .tasks import create_accounts_for_make, send_email +from .tasks import create_accounts_for_make, create_user_dealer, send_email # djago easy audit log from easyaudit.models import RequestEvent, CRUDEvent, LoginEvent - +from django_q.tasks import async_task logger = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO) @@ -293,38 +292,35 @@ def dealer_signup(request): or failure. :rtype: Union[django.http.HttpResponse, django.http.JsonResponse] """ - form1 = forms.WizardForm1() - form2 = forms.WizardForm2() - form3 = forms.WizardForm3() - if request.method == "POST": - if "Hx-Request" in request.headers: - form1 = forms.WizardForm1(request.POST) - return render( - request, - "account/signup-wizard.html", - {"form1": form1, "form2": form2, "form3": form3}, - ) data = json.loads(request.body) - wf1 = data.get("wizardValidationForm1") - wf2 = data.get("wizardValidationForm2") - wf3 = data.get("wizardValidationForm3") - email = wf1.get("email") - password = wf1.get("password") - password_confirm = wf1.get("confirm_password") - name = wf2.get("name") - arabic_name = wf2.get("arabic_name") - phone = wf2.get("phone_number") - crn = wf3.get("crn") - vrn = wf3.get("vrn") - address = wf3.get("address") + + email = data.get("email") + password = data.get("password") + password_confirm = data.get("confirm_password") + name = data.get("name") + arabic_name = data.get("arabic_name") + phone = data.get("phone_number") + crn = data.get("crn") + vrn = data.get("vrn") + address = data.get("address") + + if User.objects.filter(email=email).exists(): + return JsonResponse({"error": _("Email already exists")}, status=400) + if not re.match( + r"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$", + email, + ): + return JsonResponse({"error": _("Please enter a valid email address")}, status=400) + + if len(password) < 8: + return JsonResponse({"error": _("Password must be at least 8 characters")}, status=400) if password != password_confirm: return JsonResponse({"error": _("Passwords do not match")}, status=400) try: - #Todo make this a django-q task - create_user_dealer( + async_task(create_user_dealer( email, password, name, arabic_name, phone, crn, vrn, address ) logger.info(f"Delear created succesfully with emailID {email}") @@ -335,7 +331,6 @@ def dealer_signup(request): return render( request, "account/signup-wizard.html", - {"form1": form1, "form2": form2, "form3": form3}, ) @@ -1197,7 +1192,7 @@ def inventory_stats_view(request, dealer_slug): "inventory/inventory_stats.html" template. :rtype: HttpResponse """ - + # Base queryset for cars belonging to the dealer cars = models.Car.objects.filter(dealer=request.dealer) @@ -4206,18 +4201,26 @@ def sales_list_view(request, dealer_slug): """ dealer = get_object_or_404(models.Dealer, slug=dealer_slug) entity = dealer.entity - - sale_orders = models.SaleOrder.objects.filter( - dealer=dealer, - ) - paginator = Paginator(sale_orders, 30) + staff = getattr(request.user.staffmember, "staff", None) + qs = [] + try: + if dealer: + qs = models.ExtraInfo.get_sale_orders(staff=staff,is_dealer=True) + else: + qs = models.ExtraInfo.get_sale_orders(staff=staff) + except Exception as e: + print(e) + print(qs) + # sale_orders = models.SaleOrder.objects.filter( + # dealer=dealer, + # ) + paginator = Paginator(qs, 30) page_number = request.GET.get("page") page_obj = paginator.get_page(page_number) context = {"txs": page_obj, "page_obj": page_obj} return render(request, "sales/sales_list.html", context) - class SaleOrderDetailView(LoginRequiredMixin,PermissionRequiredMixin, DetailView): model = models.SaleOrder template_name = "sales/saleorder_detail.html" @@ -4239,6 +4242,15 @@ class SaleOrderDetailView(LoginRequiredMixin,PermissionRequiredMixin, DetailView return context + def post(self, request, *args, **kwargs): + sale_order = self.get_object() + status = request.POST.get("status") + if status: + sale_order.status = status + sale_order.save() + messages.success(request, _("Sale order status updated")) + return redirect("order_detail", dealer_slug=sale_order.dealer.slug, pk=sale_order.pk) + # Estimates class EstimateListView(LoginRequiredMixin, PermissionRequiredMixin, ListView): diff --git a/load_initial_data.sh b/load_initial_data.sh index 1f8885f5..83a7bec2 100755 --- a/load_initial_data.sh +++ b/load_initial_data.sh @@ -26,6 +26,8 @@ python3 manage.py tenhal_plan python3 manage.py set_vat +python3 manage.py set_custom_permissions + python3 manage.py initial_services_offered echo "Done" \ No newline at end of file diff --git a/static/images/customers/sun_mountain.jpg b/static/images/customers/sun_mountain.jpg new file mode 100644 index 00000000..0786c2da Binary files /dev/null and b/static/images/customers/sun_mountain.jpg differ diff --git a/templates/account/signup-wizard-copy.html b/templates/account/signup-wizard-copy.html new file mode 100644 index 00000000..1ca17e0e --- /dev/null +++ b/templates/account/signup-wizard-copy.html @@ -0,0 +1,287 @@ +{% extends "welcome_base.html" %} +{% load crispy_forms_filters %} +{% load i18n static %} + + +{% block content %} +
+
+
+ +
+
+ {% trans 'home' %} + {% trans 'home' %} +
+
+
+

{% trans 'Sign Up' %}

+

{% trans 'Create your account today' %}

+
+ +
+ +
+
+ +
+
+ {{form2|crispy}} +
+
+
+
+ {{form3|crispy}} +
+
+
+
+
+
+
+
+
+
{% trans 'You are all set!' %}
+

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

+
+
+
+
+
+
+ +
+
+
+
+
+
+ {% include 'footer.html' %} +
+ +{% endblock content %} + +{% block customJS %} + + + +{% endblock customJS %} \ No newline at end of file diff --git a/templates/account/signup-wizard.html b/templates/account/signup-wizard.html index 1ca17e0e..1d204109 100644 --- a/templates/account/signup-wizard.html +++ b/templates/account/signup-wizard.html @@ -19,7 +19,19 @@

{% trans 'Create your account today' %}

-
+
-
+
-
- {{form1|crispy}} - {{ _("Read Terms of Service and Privacy Policy")}} + +
+ + +
+ {% 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." %} +
+
-
- {{form2|crispy}} + +
+ + +
+
+ + +
+
+ * + +
+ {% trans "Please enter a valid phone number" %} +
+
-
- {{form3|crispy}} + +
+ + +
+
+ + +
+
+ + +
@@ -62,18 +154,18 @@
{% trans 'You are all set!' %}
-

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

+

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

- - {% endif %} + {% endif %} {% if perms.django_ledger.can_view_reports %}
- -
- {% if perms.inventory.view_carfinance%}

{% trans 'Financial Details' %}

@@ -296,7 +300,7 @@ {% trans "Edit" %} {% else %} {% trans "Cannot Edit, Car in Transfer." %} - {% endif %} + {% endif %} {% endif %} @@ -342,7 +346,7 @@ style="background-color: rgb({{ car.colors.interior.rgb }})">
- + {% if perms.inventory.change_carcolors%} @@ -433,7 +437,7 @@ {% endif %} - + {% endif %} @@ -501,7 +505,7 @@ alt=""> {% endif %}
- +