This commit is contained in:
Faheedkhan 2025-07-06 12:52:32 +03:00
commit 8a073d8df8
14 changed files with 734 additions and 418 deletions

View File

@ -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))

View File

@ -2550,15 +2550,6 @@ class CustomGroup(models.Model):
pass pass
def set_default_permissions(self): 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() self.clear_permissions()
###################################### ######################################
###################################### ######################################
@ -2698,7 +2689,6 @@ class CustomGroup(models.Model):
"view_carcolors", "view_carcolors",
"view_cartransfer", "view_cartransfer",
"view_saleorder", "view_saleorder",
], ],
) )
self.set_permissions( self.set_permissions(
@ -2966,4 +2956,22 @@ class ExtraInfo(models.Model):
verbose_name_plural = "Extra Info" verbose_name_plural = "Extra Info"
def __str__(self): def __str__(self):
return f"ExtraInfo for {self.content_object} ({self.content_type})" 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]

View File

@ -1,10 +1,11 @@
from django.db import transaction from django.db import transaction
from django_ledger.io import roles 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_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): def create_settings(pk):
instance = Dealer.objects.get(pk=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) async_task(send_mail,subject, message, from_email, recipient_list)
# @background def create_user_dealer(email, password, name, arabic_name, phone, crn, vrn, address):
def long_running_task(duration): with transaction.atomic():
"""Example background task""" user = User.objects.create(username=email, email=email)
print("Task completed") user.set_password(password)
return True 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,
)

View File

@ -1,33 +1,29 @@
import json import json
import secrets
import datetime 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 import requests
from decimal import Decimal 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.utils import timezone
from django_ledger.io import roles from django_ledger.io import roles
from django.contrib import messages from django.contrib import messages
from django.shortcuts import redirect from django.shortcuts import redirect
from django.core.exceptions import ObjectDoesNotExist from django_q.tasks import async_task
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.core.mail import send_mail from django.core.mail import send_mail
from django.utils.translation import gettext_lazy as _ from plans.models import AbstractOrder
from django_ledger.models.items import ItemModel
from django_ledger.models import ( from django_ledger.models import (
InvoiceModel, InvoiceModel,
BillModel, BillModel,
VendorModel, VendorModel,
) )
from django_ledger.models.items import ItemModel
from django.utils.translation import get_language from django.utils.translation import get_language
from appointment.models import StaffMember from django.core.exceptions import ObjectDoesNotExist
from django.contrib.auth.models import User from django.utils.translation import gettext_lazy as _
from django_q.tasks import async_task from django_ledger.models.transactions import TransactionModel
import secrets from django_ledger.models.journal_entry import JournalEntryModel
import logging import logging
logger=logging.getLogger(__name__) logger=logging.getLogger(__name__)
@ -1370,31 +1366,6 @@ def create_make_accounts(dealer):
active=True, 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): def handle_payment(request, order):
url = "https://api.moyasar.com/v1/payments" url = "https://api.moyasar.com/v1/payments"
callback_url = request.build_absolute_uri(reverse("payment_callback", args=[request.dealer.slug])) callback_url = request.build_absolute_uri(reverse("payment_callback", args=[request.dealer.slug]))

View File

@ -1,5 +1,6 @@
# Standard # Standard
import os import os
import re
import io import io
import csv import csv
import cv2 import cv2
@ -48,8 +49,7 @@ from django.shortcuts import HttpResponse
from django.db.models import Sum, F, Count from django.db.models import Sum, F, Count
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.contrib.auth.models import User from django.contrib.auth.models import User,Group
from django.contrib.auth.models import Group
from django.db.models import Value from django.db.models import Value
from django.urls import reverse, reverse_lazy from django.urls import reverse, reverse_lazy
from django.utils import timezone, translation from django.utils import timezone, translation
@ -186,7 +186,6 @@ from .services import (
) )
from .utils import ( from .utils import (
CarFinanceCalculator, CarFinanceCalculator,
create_user_dealer,
get_car_finance_data, get_car_finance_data,
get_item_transactions, get_item_transactions,
handle_payment, handle_payment,
@ -197,11 +196,11 @@ from .utils import (
set_invoice_payment, set_invoice_payment,
CarTransfer, 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 # djago easy audit log
from easyaudit.models import RequestEvent, CRUDEvent, LoginEvent from easyaudit.models import RequestEvent, CRUDEvent, LoginEvent
from django_q.tasks import async_task
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
@ -293,38 +292,35 @@ def dealer_signup(request):
or failure. or failure.
:rtype: Union[django.http.HttpResponse, django.http.JsonResponse] :rtype: Union[django.http.HttpResponse, django.http.JsonResponse]
""" """
form1 = forms.WizardForm1()
form2 = forms.WizardForm2()
form3 = forms.WizardForm3()
if request.method == "POST": 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) data = json.loads(request.body)
wf1 = data.get("wizardValidationForm1")
wf2 = data.get("wizardValidationForm2") email = data.get("email")
wf3 = data.get("wizardValidationForm3") password = data.get("password")
email = wf1.get("email") password_confirm = data.get("confirm_password")
password = wf1.get("password") name = data.get("name")
password_confirm = wf1.get("confirm_password") arabic_name = data.get("arabic_name")
name = wf2.get("name") phone = data.get("phone_number")
arabic_name = wf2.get("arabic_name") crn = data.get("crn")
phone = wf2.get("phone_number") vrn = data.get("vrn")
crn = wf3.get("crn") address = data.get("address")
vrn = wf3.get("vrn")
address = wf3.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: if password != password_confirm:
return JsonResponse({"error": _("Passwords do not match")}, status=400) return JsonResponse({"error": _("Passwords do not match")}, status=400)
try: try:
#Todo make this a django-q task async_task(create_user_dealer(
create_user_dealer(
email, password, name, arabic_name, phone, crn, vrn, address email, password, name, arabic_name, phone, crn, vrn, address
) )
logger.info(f"Delear created succesfully with emailID {email}") logger.info(f"Delear created succesfully with emailID {email}")
@ -335,7 +331,6 @@ def dealer_signup(request):
return render( return render(
request, request,
"account/signup-wizard.html", "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. "inventory/inventory_stats.html" template.
:rtype: HttpResponse :rtype: HttpResponse
""" """
# Base queryset for cars belonging to the dealer # Base queryset for cars belonging to the dealer
cars = models.Car.objects.filter(dealer=request.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) dealer = get_object_or_404(models.Dealer, slug=dealer_slug)
entity = dealer.entity entity = dealer.entity
staff = getattr(request.user.staffmember, "staff", None)
sale_orders = models.SaleOrder.objects.filter( qs = []
dealer=dealer, try:
) if dealer:
paginator = Paginator(sale_orders, 30) 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_number = request.GET.get("page")
page_obj = paginator.get_page(page_number) page_obj = paginator.get_page(page_number)
context = {"txs": page_obj, "page_obj": page_obj} context = {"txs": page_obj, "page_obj": page_obj}
return render(request, "sales/sales_list.html", context) return render(request, "sales/sales_list.html", context)
class SaleOrderDetailView(LoginRequiredMixin,PermissionRequiredMixin, DetailView): class SaleOrderDetailView(LoginRequiredMixin,PermissionRequiredMixin, DetailView):
model = models.SaleOrder model = models.SaleOrder
template_name = "sales/saleorder_detail.html" template_name = "sales/saleorder_detail.html"
@ -4239,6 +4242,15 @@ class SaleOrderDetailView(LoginRequiredMixin,PermissionRequiredMixin, DetailView
return context 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 # Estimates
class EstimateListView(LoginRequiredMixin, PermissionRequiredMixin, ListView): class EstimateListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):

View File

@ -26,6 +26,8 @@ python3 manage.py tenhal_plan
python3 manage.py set_vat python3 manage.py set_vat
python3 manage.py set_custom_permissions
python3 manage.py initial_services_offered python3 manage.py initial_services_offered
echo "Done" echo "Done"

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,287 @@
{% extends "welcome_base.html" %}
{% load crispy_forms_filters %}
{% load i18n static %}
{% block content %}
<section class="main my-2">
<div class="container-fluid">
<div class="row form-container" id="form-container">
<div class="col-12 "><a class="d-flex flex-center text-decoration-none mb-4" href="{% url 'home' %}">
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
<img class="d-dark-none" src="{% static 'images/logos/logo-d.png' %}" alt="{% trans 'home' %}" width="58" />
<img class="d-light-none" src="{% static 'images/logos/logo.png' %}" alt="{% trans 'home' %}" width="58" />
</div>
</a>
<div class="text-center">
<h3 class="text-body-highlight">{% trans 'Sign Up' %}</h3>
<p class="text-body-tertiary fs-9">{% trans 'Create your account today' %}</p>
</div>
<div class="card theme-wizard" data-theme-wizard="data-theme-wizard">
<div class="card-header pt-3 pb-2 ">
<ul class="nav justify-content-between nav-wizard nav-wizard-success" role="tablist">
<li class="nav-item" role="presentation"><a class="nav-link active fw-semibold" href="#bootstrap-wizard-validation-tab1" data-bs-toggle="tab" data-wizard-step="1" aria-selected="true" role="tab">
<div class="text-center d-inline-block"><span class="nav-item-circle-parent"><span class="nav-item-circle"><span class="fa fa-lock"></span></span></span><span class="d-none d-md-block mt-1 fs-9">{% trans 'Access' %}</span></div>
</a></li>
<li class="nav-item" role="presentation"><a class="nav-link fw-semibold" href="#bootstrap-wizard-validation-tab2" data-bs-toggle="tab" data-wizard-step="2" aria-selected="false" tabindex="-1" role="tab">
<div class="text-center d-inline-block"><span class="nav-item-circle-parent"><span class="nav-item-circle"><span class="fa fa-user"></span></span></span><span class="d-none d-md-block mt-1 fs-9">{% trans 'Account' %}</span></div>
</a></li>
<li class="nav-item" role="presentation"><a class="nav-link fw-semibold" href="#bootstrap-wizard-validation-tab3" data-bs-toggle="tab" data-wizard-step="3" aria-selected="false" tabindex="-1" role="tab">
<div class="text-center d-inline-block"><span class="nav-item-circle-parent"><span class="nav-item-circle"><svg class="fa fa-file-lines"></svg></span></span><span class="d-none d-md-block mt-1 fs-9">{% trans 'Extra' %}</span></div>
</a></li>
<li class="nav-item" role="presentation"><a class="nav-link fw-semibold" href="#bootstrap-wizard-validation-tab4" data-bs-toggle="tab" data-wizard-step="4" aria-selected="false" tabindex="-1" role="tab">
<div class="text-center d-inline-block"><span class="nav-item-circle-parent"><span class="nav-item-circle"><span class="fa fa-check"></span></span></span><span class="d-none d-md-block mt-1 fs-9">{% trans 'Done' %}</span></div>
</a></li>
</ul>
</div>
<div class="card-body pt-4 pb-0">
<div class="tab-content">
<div class="tab-pane active" role="tabpanel" aria-labelledby="bootstrap-wizard-validation-tab1" id="bootstrap-wizard-validation-tab1">
<form class="needs-validation" id="wizardValidationForm1" novalidate="novalidate" data-wizard-form="1">
{{form1|crispy}}
<a class="fs-10 text-decoration-none" href="{% url 'terms_and_privacy' %}" target="_blank">{{ _("Read Terms of Service and Privacy Policy")}}</a>
</form>
</div>
<div class="tab-pane" role="tabpanel" aria-labelledby="bootstrap-wizard-validation-tab2" id="bootstrap-wizard-validation-tab2">
<form class="needs-validation" id="wizardValidationForm2" novalidate="novalidate" data-wizard-form="2">
{{form2|crispy}}
</form>
</div>
<div class="tab-pane" role="tabpanel" aria-labelledby="bootstrap-wizard-validation-tab3" id="bootstrap-wizard-validation-tab3">
<form class="needs-validation" id="wizardValidationForm3" novalidate="novalidate" data-wizard-form="3">
{{form3|crispy}}
</form>
</div>
<div class="tab-pane" role="tabpanel" aria-labelledby="bootstrap-wizard-validation-tab4" id="bootstrap-wizard-validation-tab4">
<div class="row flex-center pb-8 pt-4 gx-3 gy-4">
<div class="col-12 col-sm-auto">
<div class="text-center text-sm-start"><img class="d-dark-none" src="{% static 'images/spot-illustrations/38.webp' %}" alt="" width="220"><img class="d-light-none" src="{% static 'images/spot-illustrations/dark_38.webp' %}" alt="" width="220"></div>
</div>
<div class="col-12 col-sm-auto">
<div class="text-center text-sm-start">
<h5 class="mb-3">{% trans 'You are all set!' %}</h5>
<p class="text-body-emphasis fs-9">{% trans 'Now you can access your account' %}<br>{% trans 'anytime' %} {% trans 'anywhere' %}</p><button class="btn btn-primary px-6" id='submit_btn'>{% trans 'Submit' %}</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="card-footer border-top-0" data-wizard-footer="data-wizard-footer">
<div class="d-flex pager wizard list-inline mb-0">
<button class="d-none btn btn-link ps-0" type="button" data-wizard-prev-btn="data-wizard-prev-btn">{% trans 'Previous' %}</button>
<div class="flex-1 text-end">
<button class="btn btn-phoenix-primary px-6 px-sm-6 next" type="submit" data-wizard-next-btn="data-wizard-next-btn">{% trans 'Next' %}</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<section class="pt-lg-0 pt-xl-8">
{% include 'footer.html' %}
</section>
<script src="{% static 'js/phoenix.js' %}"></script>
{% endblock content %}
{% block customJS %}
<script src="https://unpkg.com/just-validate@latest/dist/just-validate.production.min.js"></script>
<script>
const validator = new JustValidate('#wizardValidationForm1', {
validateBeforeSubmitting: true,
});
const validator1 = new JustValidate('#wizardValidationForm2', {
validateBeforeSubmitting: true,
});
validator1.addField('#wizardValidationForm2 [name="phone_number"]', [
{
rule: 'required',
},
{
rule: 'customRegexp',
value: /^05\d{8}$/,
errorMessage: '{% trans 'Please enter a valid phone number' %}',
},
{
rule: 'maxLength',
value: 10,
errorMessage: '{% trans 'Please enter a valid phone number' %}',
},
]);
validator.addField('#wizardValidationForm1 [name="email"]', [
{
rule: 'required',
},
{
rule: 'email',
},
]);
validator.addField('#wizardValidationForm1 [name="password"]', [
{
rule: 'required',
},
{
rule: 'minLength',
value: 6,
},
]);
validator.addField('#wizardValidationForm1 [name="confirm_password"]', [
{
rule: 'required',
},
{
rule: 'minLength',
value: 6,
errorMessage: '{% trans 'Password does not match' %}',
},
{
validator: (value, context) => {
if (value !== document.querySelector('#id_password').value) {
return false;
}
return true;
},
},
])
validator.addField('#wizardValidationForm1 [name="terms"]', [
{
rule: 'required',
},
])
/*
validator = new JustValidate('#wizardValidationForm1', {
rules: {
email: {
required: true,
email: true,
},
password: {
required: true,
min: 6,
},
confirm_password: {
required: true,
min: 6,
equalTo: '#password',
},
},
messages: {
name: {
required: 'Please enter your name',
},
email: {
required: 'Please enter your email',
email: 'Please enter a valid email address',
},
password: {
required: 'Please enter your password',
min: 'Password must be at least 6 characters',
},
confirm_password: {
required: 'Please confirm your password',
min: 'Password must be at least 6 characters',
equalTo: 'Passwords do not match',
},
}
})
*/
const url = "{% url 'account_signup' %}";
let submit_btn = document.getElementById('submit_btn');
const csrftoken = getCookie('csrftoken');
submit_btn.addEventListener('click', async () => {
const allFormData = getAllFormData();
try {
showLoading();
const response = await fetch(url, {
method: 'POST',
headers: {
'X-CSRFToken': csrftoken,
'Content-Type': 'application/json',
},
body: JSON.stringify(allFormData),
});
hideLoading();
const data = await response.json();
if (response.ok) {
notify("success","Account created successfully");
setTimeout(() => {
window.location.href = "{% url 'account_login' %}";
}, 1000);
} else {
notify("error",data.error);
}
} catch (error) {
notify("error",error);
}
});
function getAllFormData() {
const forms = document.querySelectorAll('form');
const formData = {};
forms.forEach((form, index) => {
const formId = form.id || `form${index + 1}`;
formData[formId] = {};
const formElements = form.elements;
for (let element of formElements) {
if (element.name) {
formData[formId][element.name] = element.value;
}
}
});
return formData;
}
function showLoading() {
Swal.fire({
title: "{% trans 'Please Wait' %}",
text: "{% trans 'Loading' %}...",
allowOutsideClick: false,
didOpen: () => {
Swal.showLoading();
}
});
}
function hideLoading() {
Swal.close();
}
function notify(tag,msg){
Swal.fire({
icon: tag,
titleText: msg
});
}
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.substring(0, name.length + 1) === name + "=") {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
</script>
{% endblock customJS %}

View File

@ -19,7 +19,19 @@
<p class="text-body-tertiary fs-9">{% trans 'Create your account today' %}</p> <p class="text-body-tertiary fs-9">{% trans 'Create your account today' %}</p>
</div> </div>
<div class="card theme-wizard" data-theme-wizard="data-theme-wizard"> <div
data-signals="{
form1:{email:'',password:'',confirm_password:''},
form2:{name:'',arabic_name:'',phone_number:''},
form3:{crn:'',vrn:'',address:''},
form1_valid:true,
form2_valid:true,
form3_valid:true,
email_valid:true,
password_valid:true,
phone_number_valid:true
}"
class="card theme-wizard" data-theme-wizard="data-theme-wizard">
<div class="card-header pt-3 pb-2 "> <div class="card-header pt-3 pb-2 ">
<ul class="nav justify-content-between nav-wizard nav-wizard-success" role="tablist"> <ul class="nav justify-content-between nav-wizard nav-wizard-success" role="tablist">
<li class="nav-item" role="presentation"><a class="nav-link active fw-semibold" href="#bootstrap-wizard-validation-tab1" data-bs-toggle="tab" data-wizard-step="1" aria-selected="true" role="tab"> <li class="nav-item" role="presentation"><a class="nav-link active fw-semibold" href="#bootstrap-wizard-validation-tab1" data-bs-toggle="tab" data-wizard-step="1" aria-selected="true" role="tab">
@ -37,21 +49,101 @@
</ul> </ul>
</div> </div>
<div class="card-body pt-4 pb-0"> <div class="card-body pt-4 pb-0">
<div class="tab-content"> <div class="tab-content" data-signals-current_form="1">
<div class="tab-pane active" role="tabpanel" aria-labelledby="bootstrap-wizard-validation-tab1" id="bootstrap-wizard-validation-tab1"> <div class="tab-pane active" role="tabpanel" aria-labelledby="bootstrap-wizard-validation-tab1" id="bootstrap-wizard-validation-tab1">
<form class="needs-validation" id="wizardValidationForm1" novalidate="novalidate" data-wizard-form="1"> <form class="needs-validation" id="wizardValidationForm1" novalidate="novalidate" data-wizard-form="1" data-ref-f1>
{{form1|crispy}} <div class="mb-3">
<a class="fs-10 text-decoration-none" href="{% url 'terms_and_privacy' %}" target="_blank">{{ _("Read Terms of Service and Privacy Policy")}}</a> <label
for="email"
data-class="{'text-danger':!$email_valid}"
class="form-label">{% trans "Email"%}
<span
data-show="!$email_valid"
class="text-danger">*</span>
</label>
<input
data-on-input="$email_valid = validateEmail($form1.email)"
data-on-blur="$email_valid = validateEmail($form1.email)"
data-bind-form1.email
data-class="{'is-invalid': !$email_valid , 'is-valid': ($email_valid && $form1.email)}"
type="email"
class="form-control"
id="email"
name="email"
required>
<div class="invalid-feedback" data-show="!$email_valid">
{% trans "Please enter a valid email address" %}
</div>
</div>
<div class="mb-3">
<label for="password" class="form-label">{% trans "Password" %}</label>
<input
data-bind-form1.password
type="password"
data-on-input="$password_valid = validatePassword($form1.password,$form1.confirm_password)"
data-on-blur="$password_valid = validatePassword($form1.password,$form1.confirm_password)"
class="form-control"
data-class="{'is-invalid':($form1.password.length && $form1.password.length < 8),'is-valid':$form1.password.length > 8 }"
id="password"
name="password"
required>
<div class="invalid-feedback" data-show="!$password_valid">
{% trans "Password does not match. or length is less than 8 characters." %}
</div>
</div>
<div class="mb-3">
<label for="confirm_password" class="form-label">{% trans "Confirm Password" %}</label><span class="text-danger" data-show="!$password_valid">*</span>
<input
data-bind-form1.confirm_password
data-on-input="$password_valid = validatePassword($form1.password,$form1.confirm_password)"
data-on-blur="$password_valid = validatePassword($form1.password,$form1.confirm_password)"
type="password"
class="form-control"
data-class="{'is-invalid':!$password_valid,'is-valid':($password_valid&& $form1.confirm_password)}"
id="confirm_password"
name="confirm_password"
required>
<div class="invalid-feedback" data-show="!$password_valid">
{% trans "Password does not match. or length is less than 8 characters." %}
</div>
</div>
</form> </form>
</div> </div>
<div class="tab-pane" role="tabpanel" aria-labelledby="bootstrap-wizard-validation-tab2" id="bootstrap-wizard-validation-tab2"> <div class="tab-pane" role="tabpanel" aria-labelledby="bootstrap-wizard-validation-tab2" id="bootstrap-wizard-validation-tab2">
<form class="needs-validation" id="wizardValidationForm2" novalidate="novalidate" data-wizard-form="2"> <form class="needs-validation" id="wizardValidationForm2" novalidate="novalidate" data-wizard-form="2" data-ref-f2>
{{form2|crispy}} <div class="mb-3">
<label for="name" class="form-label">{% trans "Name" %}</label>
<input data-bind-form2.name type="text" class="form-control" id="name" name="name" required>
</div>
<div class="mb-3">
<label for="arabic_name" class="form-label">{% trans "Arabic Name" %}</label>
<input data-bind-form2.arabic_name type="text" class="form-control" id="arabic_name" name="arabic_name" required>
</div>
<div class="mb-3">
<label for="phone_number" class="form-label">{% trans "Phone Number" %}</label><span data-show="!$phone_number_valid" class="text-danger">*</span>
<input data-bind-form2.phone_number type="tel" data-class="{'is-invalid':!$phone_number_valid}" class="form-control" id="phone_number" name="phone_number" required
data-on-input="$phone_number_valid = validate_sa_phone_number($form2.phone_number)"
>
<div class="invalid-feedback" data-show="!$phone_number_valid">
{% trans "Please enter a valid phone number" %}
</div>
</div>
</form> </form>
</div> </div>
<div class="tab-pane" role="tabpanel" aria-labelledby="bootstrap-wizard-validation-tab3" id="bootstrap-wizard-validation-tab3"> <div class="tab-pane" role="tabpanel" aria-labelledby="bootstrap-wizard-validation-tab3" id="bootstrap-wizard-validation-tab3">
<form class="needs-validation" id="wizardValidationForm3" novalidate="novalidate" data-wizard-form="3"> <form class="needs-validation" id="wizardValidationForm3" novalidate="novalidate" data-wizard-form="3" data-ref-f3>
{{form3|crispy}} <div class="mb-3">
<label for="crn" class="form-label">{% trans "CRN" %}</label>
<input data-bind-form3.crn type="text" class="form-control" id="crn" name="crn" required>
</div>
<div class="mb-3">
<label for="vrn" class="form-label">{% trans "VRN" %}</label>
<input data-bind-form3.vrn type="text" class="form-control" id="vrn" name="vrn" required>
</div>
<div class="mb-3">
<label for="address" class="form-label">{% trans "Address" %}</label>
<textarea data-bind-form3.address class="form-control" id="address" name="address" required></textarea>
</div>
</form> </form>
</div> </div>
<div class="tab-pane" role="tabpanel" aria-labelledby="bootstrap-wizard-validation-tab4" id="bootstrap-wizard-validation-tab4"> <div class="tab-pane" role="tabpanel" aria-labelledby="bootstrap-wizard-validation-tab4" id="bootstrap-wizard-validation-tab4">
@ -62,18 +154,18 @@
<div class="col-12 col-sm-auto"> <div class="col-12 col-sm-auto">
<div class="text-center text-sm-start"> <div class="text-center text-sm-start">
<h5 class="mb-3">{% trans 'You are all set!' %}</h5> <h5 class="mb-3">{% trans 'You are all set!' %}</h5>
<p class="text-body-emphasis fs-9">{% trans 'Now you can access your account' %}<br>{% trans 'anytime' %} {% trans 'anywhere' %}</p><button class="btn btn-primary px-6" id='submit_btn'>{% trans 'Submit' %}</button> <p class="text-body-emphasis fs-9">{% trans 'Now you can access your account' %}<br>{% trans 'anytime' %} {% trans 'anywhere' %}</p><button data-on-click="sendFormData()" class="btn btn-primary px-6" id='submit_btn'>{% trans 'Submit' %}</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="card-footer border-top-0" data-wizard-footer="data-wizard-footer"> <div data-computed-form1_valid="validatePassword($form1.password,$form1.confirm_password) && validateEmail($form1.email)" class="card-footer border-top-0" data-wizard-footer="data-wizard-footer">
<div class="d-flex pager wizard list-inline mb-0"> <div class="d-flex pager wizard list-inline mb-0">
<button class="d-none btn btn-link ps-0" type="button" data-wizard-prev-btn="data-wizard-prev-btn">{% trans 'Previous' %}</button> <button class="d-none btn btn-link ps-0" type="button" data-wizard-prev-btn="data-wizard-prev-btn">{% trans 'Previous' %}</button>
<div class="flex-1 text-end"> <div class="flex-1 text-end">
<button class="btn btn-phoenix-primary px-6 px-sm-6 next" type="submit" data-wizard-next-btn="data-wizard-next-btn">{% trans 'Next' %}</button> <button data-attr-disabled="!$form1_valid" data-attr-disabled="!$phone_number_valid" class="btn btn-phoenix-primary px-6 px-sm-6 next" type="button" id="next_btn" data-wizard-next-btn="data-wizard-next-btn">{% trans 'Next' %}</button>
</div> </div>
</div> </div>
</div> </div>
@ -84,167 +176,43 @@
</section> </section>
<section class="pt-lg-0 pt-xl-8"> <section class="pt-lg-0 pt-xl-8">
{% include 'footer.html' %} {% include 'footer.html' %}
</section> </section>
<script src="{% static 'js/phoenix.js' %}"></script> <script src="{% static 'js/phoenix.js' %}"></script>
{% endblock content %} {% endblock content %}
{% block customJS %} {% block customJS %}
<script src="https://unpkg.com/just-validate@latest/dist/just-validate.production.min.js"></script> <script src="{% static 'js/main.js' %}"></script>
<script src="{% static 'js/sweetalert2.all.min.js' %}"></script>
<script type="module" src="https://cdn.jsdelivr.net/gh/starfederation/datastar@v1.0.0-beta.11/bundles/datastar.js"></script>
<script> <script>
const validator = new JustValidate('#wizardValidationForm1', { function validatePassword(password, confirmPassword) {
validateBeforeSubmitting: true, return password === confirmPassword && password.length > 7 && password !== '';
}); }
const validator1 = new JustValidate('#wizardValidationForm2', { function validateEmail(email) {
validateBeforeSubmitting: true, const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
}); return emailRegex.test(email) && email !== '';
}
validator1.addField('#wizardValidationForm2 [name="phone_number"]', [ function validateform2(name,arabic_name,phone_number) {
{ if (name === '' || arabic_name === '' || phone_number === '' || phone_number.length < 10 || !phone_number.startsWith('056')) {
rule: 'required',
},
{
rule: 'customRegexp',
value: /^05\d{8}$/,
errorMessage: '{% trans 'Please enter a valid phone number' %}',
},
{
rule: 'maxLength',
value: 10,
errorMessage: '{% trans 'Please enter a valid phone number' %}',
},
]);
validator.addField('#wizardValidationForm1 [name="email"]', [
{
rule: 'required',
},
{
rule: 'email',
},
]);
validator.addField('#wizardValidationForm1 [name="password"]', [
{
rule: 'required',
},
{
rule: 'minLength',
value: 6,
},
]);
validator.addField('#wizardValidationForm1 [name="confirm_password"]', [
{
rule: 'required',
},
{
rule: 'minLength',
value: 6,
errorMessage: '{% trans 'Password does not match' %}',
},
{
validator: (value, context) => {
if (value !== document.querySelector('#id_password').value) {
return false; return false;
}
return true;
},
},
])
validator.addField('#wizardValidationForm1 [name="terms"]', [
{
rule: 'required',
},
])
/*
validator = new JustValidate('#wizardValidationForm1', {
rules: {
email: {
required: true,
email: true,
},
password: {
required: true,
min: 6,
},
confirm_password: {
required: true,
min: 6,
equalTo: '#password',
},
},
messages: {
name: {
required: 'Please enter your name',
},
email: {
required: 'Please enter your email',
email: 'Please enter a valid email address',
},
password: {
required: 'Please enter your password',
min: 'Password must be at least 6 characters',
},
confirm_password: {
required: 'Please confirm your password',
min: 'Password must be at least 6 characters',
equalTo: 'Passwords do not match',
},
} }
}) return true
*/ }
const url = "{% url 'account_signup' %}"; function validate_sa_phone_number(phone_number) {
let submit_btn = document.getElementById('submit_btn'); const phone_numberRegex = /^056[0-9]{7}$/;
const csrftoken = getCookie('csrftoken'); return phone_numberRegex.test(phone_number) && phone_numberRegex !== '';
}
submit_btn.addEventListener('click', async () => {
const allFormData = getAllFormData();
try {
showLoading();
const response = await fetch(url, {
method: 'POST',
headers: {
'X-CSRFToken': csrftoken,
'Content-Type': 'application/json',
},
body: JSON.stringify(allFormData),
});
hideLoading();
const data = await response.json();
if (response.ok) {
notify("success","Account created successfully");
setTimeout(() => {
window.location.href = "{% url 'account_login' %}";
}, 1000);
} else {
notify("error",data.error);
}
} catch (error) {
notify("error",error);
}
});
function getAllFormData() { function getAllFormData() {
const forms = document.querySelectorAll('form'); const forms = document.querySelectorAll('.needs-validation');
const formData = {}; const formData = {};
forms.forEach(form => {
forms.forEach((form, index) => { const fields = form.querySelectorAll('input,textarea,select');
const formId = form.id || `form${index + 1}`; fields.forEach(field => {
formData[formId] = {}; formData[field.name] = field.value;
});
const formElements = form.elements; });
for (let element of formElements) { return formData;
if (element.name) {
formData[formId][element.name] = element.value;
}
}
});
return formData;
} }
function showLoading() { function showLoading() {
@ -268,7 +236,7 @@
titleText: msg titleText: msg
}); });
} }
function getCookie(name) { function getCookie(name) {
let cookieValue = null; let cookieValue = null;
if (document.cookie && document.cookie !== "") { if (document.cookie && document.cookie !== "") {
const cookies = document.cookie.split(";"); const cookies = document.cookie.split(";");
@ -282,6 +250,33 @@
} }
return cookieValue; return cookieValue;
} }
async function sendFormData() {
const formData = getAllFormData();
const url = "{% url 'account_signup' %}";
const csrftoken = getCookie('csrftoken');
try {
showLoading();
const response = await fetch(url, {
method: 'POST',
headers: {
'X-CSRFToken': '{{csrf_token}}',
'Content-Type': 'application/json',
},
body: JSON.stringify(formData),
});
hideLoading();
const data = await response.json();
if (response.ok) {
notify("success","Account created successfully");
setTimeout(() => {
window.location.href = "{% url 'account_login' %}";
}, 1000);
} else {
notify("error",data.error);
}
} catch (error) {
notify("error",error);
}
}
</script> </script>
{% endblock customJS %} {% endblock customJS %}

View File

@ -26,7 +26,6 @@
<meta name="msapplication-TileImage" content="{% static 'images/logos/logo-d.png' %}"> <meta name="msapplication-TileImage" content="{% static 'images/logos/logo-d.png' %}">
<meta name="theme-color" content="#ffffff"> <meta name="theme-color" content="#ffffff">
<script src="{% static 'vendors/simplebar/simplebar.min.js' %}"></script> <script src="{% static 'vendors/simplebar/simplebar.min.js' %}"></script>
<script src="{% static 'js/config.js' %}"></script> <script src="{% static 'js/config.js' %}"></script>
<script src="{% static 'js/sweetalert2.all.min.js' %}"></script> <script src="{% static 'js/sweetalert2.all.min.js' %}"></script>
@ -51,7 +50,6 @@
<script src="{% static 'js/jquery.min.js' %}"></script> <script src="{% static 'js/jquery.min.js' %}"></script>
<script src="{% static 'js/echarts.js' %}"></script> <script src="{% static 'js/echarts.js' %}"></script>
{% block customCSS %} {% block customCSS %}
{% endblock %} {% endblock %}
@ -96,7 +94,7 @@
<script src="{% static 'vendors/is/is.min.js' %}"></script> <script src="{% static 'vendors/is/is.min.js' %}"></script>
<!--2--> <!--2-->
<script src="{% static 'vendors/fontawesome/all.min.js' %}"></script> <script src="{% static 'vendors/fontawesome/all.min.js' %}"></script>
<script src="{% static 'vendors/lodash/lodash.min.js' %}"></script> <script src="{% static 'vendors/lodash/lodash.min.js' %}"></script>
<script src="{% static 'vendors/list.js/list.min.js' %}"></script> <script src="{% static 'vendors/list.js/list.min.js' %}"></script>
<script src="{% static 'vendors/feather-icons/feather.min.js' %}"></script> <script src="{% static 'vendors/feather-icons/feather.min.js' %}"></script>

View File

@ -5,125 +5,60 @@
<div class="collapse navbar-collapse" id="navbarVerticalCollapse"> <div class="collapse navbar-collapse" id="navbarVerticalCollapse">
<div class="navbar-vertical-content"> <div class="navbar-vertical-content">
<ul class="navbar-nav flex-column" id="navbarVerticalNav"> <ul class="navbar-nav flex-column" id="navbarVerticalNav">
<li class="nav-item"> <li class="nav-item">
<p class="navbar-vertical-label">Apps</p> <p class="navbar-vertical-label">Apps</p>
<hr class="navbar-vertical-line" /> <hr class="navbar-vertical-line" />
{% if perms.inventory.can_view_inventory %} {% if perms.inventory.can_view_inventory %}
<div class="nav-item-wrapper"> <div class="nav-item-wrapper">
<a id="inventory-nav" class="nav-link dropdown-indicator label-1 inventory-nav" href="#nv-inventory" role="button" data-bs-toggle="collapse" aria-expanded="false" aria-controls="nv-inventory"> <a id="inventory-nav" class="nav-link dropdown-indicator label-1 inventory-nav" href="#nv-inventory" role="button" data-bs-toggle="collapse" aria-expanded="false" aria-controls="nv-inventory">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<div class="dropdown-indicator-icon-wrapper"><span class="fas fa-caret-right dropdown-indicator-icon"></span></div>
<span class="nav-link-icon"><span class="fas fa-warehouse"></span></span><span class="nav-link-text">{% trans "Inventory"|capfirst %}</span>
</div>
</a>
<div class="parent-wrapper label-1">
<ul class="nav collapse parent" data-bs-parent="#navbarVerticalCollapse" id="nv-inventory">
<li class="collapsed-nav-item-title d-none">{% trans "Inventory"|capfirst %}</li>
{% if perms.inventory.add_car %}
<li class="nav-item">
<a id="btn-add-car" class="nav-link btn-add-car" href="{% url 'car_add' request.dealer.slug %}">
<div class="d-flex align-items-center">
<span class="nav-link-icon"><span class="fas fa-plus-circle"></span></span><span class="nav-link-text">{% trans "add car"|capfirst %}</span>
</div>
</a>
</li>
</li>
{% endif %}
{% if perms.inventory.view_car%}
<li class="nav-item">
<a class="nav-link" href="{% url 'inventory_stats' request.dealer.slug %}">
<div class="d-flex align-items-center">
<span class="nav-link-icon"><span class="fas fa-car-side"></span></span><span class="nav-link-text">{% trans 'Cars'|capfirst %}</span>
</div>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'car_list' request.dealer.slug %}">
<div class="d-flex align-items-center">
<span class="nav-link-icon"><span class="fas fa-car-side"></span></span><span class="nav-link-text">{% trans 'Stock'|capfirst %}</span>
</div>
</a>
</li>
{% endif %}
{% if perms.inventory.add_car %}
<li class="nav-item">
<a class="nav-link" href="{% url 'upload_cars' request.dealer.slug %}">
<div class="d-flex align-items-center">
<span class="nav-link-icon"><span class="fas fa-file-import"></span></span><span class="nav-link-text">{% trans "Bulk Upload"|capfirst %}</span>
</div>
</a>
</li>
{% endif %}
</ul>
</div>
</div>
{% endif %}
{% if perms.django_ledger.can_view_sales %}
<div class="nav-item-wrapper">
<a class="nav-link dropdown-indicator label-1" href="#nv-sales" role="button" data-bs-toggle="collapse" aria-expanded="false" aria-controls="nv-sales">
<div class="d-flex align-items-center">
<div class="dropdown-indicator-icon-wrapper"><span class="fas fa-caret-right dropdown-indicator-icon"></span></div> <div class="dropdown-indicator-icon-wrapper"><span class="fas fa-caret-right dropdown-indicator-icon"></span></div>
<span class="nav-link-icon"><span data-feather="shopping-cart"></span></span><span class="nav-link-text">{% trans 'sales'|capfirst %}</span> <span class="nav-link-icon"><span class="fas fa-warehouse"></span></span><span class="nav-link-text">{% trans "Inventory"|capfirst %}</span>
</div> </div>
</a> </a>
<div class="parent-wrapper label-1"> <div class="parent-wrapper label-1">
<ul class="nav collapse parent" data-bs-parent="#navbarVerticalCollapse" id="nv-sales"> <ul class="nav collapse parent" data-bs-parent="#navbarVerticalCollapse" id="nv-inventory">
<li class="collapsed-nav-item-title d-none">{% trans 'sales'|capfirst %}</li> <li class="collapsed-nav-item-title d-none">{% trans "Inventory"|capfirst %}</li>
{% if perms.django_ledger.add_estimatemodel %} {% if perms.inventory.add_car %}
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{% url 'estimate_create' request.dealer.slug %}"> <a id="btn-add-car" class="nav-link btn-add-car" href="{% url 'car_add' request.dealer.slug %}">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<span class="nav-link-icon"><span class="fas fa-handshake"></span></span><span class="nav-link-text">{% trans "create quotation"|capfirst %}</span> <span class="nav-link-icon"><span class="fas fa-plus-circle"></span></span><span class="nav-link-text">{% trans "add car"|capfirst %}</span>
</div> </div>
</a> </a>
</li> </li>
</li>
{% endif %} {% endif %}
{% if perms.django_ledger.view_estimatemodel %}
<li class="nav-item"> {% if perms.inventory.view_car%}
<a class="nav-link" href="{% url 'estimate_list' request.dealer.slug %}">
<div class="d-flex align-items-center">
<span class="nav-link-icon"><span class="fas fa-handshake"></span></span><span class="nav-link-text">{% trans "quotations"|capfirst %}</span>
</div>
</a>
</li>
{% endif %}
{%if perms.inventory.view_saleorder%}
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{% url 'sales_list' request.dealer.slug %}"> <a class="nav-link" href="{% url 'inventory_stats' request.dealer.slug %}">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<span class="nav-link-icon"><span class="fas fa-money-check"></span></span><span class="nav-link-text">{% trans "Sales Orders"|capfirst %}</span> <span class="nav-link-icon"><span class="fas fa-car-side"></span></span><span class="nav-link-text">{% trans 'Cars'|capfirst %}</span>
</div> </div>
</a> </a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'car_list' request.dealer.slug %}">
<div class="d-flex align-items-center">
<span class="nav-link-icon"><span class="fas fa-car-side"></span></span><span class="nav-link-text">{% trans 'Stock'|capfirst %}</span>
</div>
</a>
</li> </li>
{% endif %} {% endif %}
{% if perms.django_ledger.view_invoicemodel %} {% if perms.inventory.add_car %}
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{% url 'invoice_list' request.dealer.slug %}"> <a class="nav-link" href="{% url 'upload_cars' request.dealer.slug %}">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<span class="nav-link-icon"><span class="fas fa-file-invoice"></span></span><span class="nav-link-text">{% trans "invoices"|capfirst %}</span> <span class="nav-link-icon"><span class="fas fa-file-import"></span></span><span class="nav-link-text">{% trans "Bulk Upload"|capfirst %}</span>
</div> </div>
</a> </a>
</li> </li>
{% endif %} {% endif %}
{% if perms.inventory.view_payment %} </ul>
<li class="nav-item"> </div>
<a class="nav-link" href="{% url 'payment_list' request.dealer.slug %}">
<div class="d-flex align-items-center">
<span class="nav-link-icon"><span class="fas fa-money-check"></span></span><span class="nav-link-text">{% trans "payments"|capfirst %}</span>
</div>
</a>
</li>
{% endif %}
</ul>
</div> </div>
</div>
{% endif %} {% endif %}
{% if perms.inventory.can_view_crm %} {% if perms.inventory.can_view_crm %}
@ -200,6 +135,69 @@
</div> </div>
{% endif %} {% endif %}
{% if perms.django_ledger.can_view_sales %}
<div class="nav-item-wrapper">
<a class="nav-link dropdown-indicator label-1" href="#nv-sales" role="button" data-bs-toggle="collapse" aria-expanded="false" aria-controls="nv-sales">
<div class="d-flex align-items-center">
<div class="dropdown-indicator-icon-wrapper"><span class="fas fa-caret-right dropdown-indicator-icon"></span></div>
<span class="nav-link-icon"><span data-feather="shopping-cart"></span></span><span class="nav-link-text">{% trans 'sales'|capfirst %}</span>
</div>
</a>
<div class="parent-wrapper label-1">
<ul class="nav collapse parent" data-bs-parent="#navbarVerticalCollapse" id="nv-sales">
<li class="collapsed-nav-item-title d-none">{% trans 'sales'|capfirst %}</li>
{% if perms.django_ledger.add_estimatemodel %}
<li class="nav-item">
<a class="nav-link" href="{% url 'estimate_create' request.dealer.slug %}">
<div class="d-flex align-items-center">
<span class="nav-link-icon"><span class="fas fa-handshake"></span></span><span class="nav-link-text">{% trans "create quotation"|capfirst %}</span>
</div>
</a>
</li>
{% endif %}
{% if perms.django_ledger.view_estimatemodel %}
<li class="nav-item">
<a class="nav-link" href="{% url 'estimate_list' request.dealer.slug %}">
<div class="d-flex align-items-center">
<span class="nav-link-icon"><span class="fas fa-handshake"></span></span><span class="nav-link-text">{% trans "quotations"|capfirst %}</span>
</div>
</a>
</li>
{% endif %}
{%if perms.inventory.view_saleorder%}
<li class="nav-item">
<a class="nav-link" href="{% url 'sales_list' request.dealer.slug %}">
<div class="d-flex align-items-center">
<span class="nav-link-icon"><span class="fas fa-money-check"></span></span><span class="nav-link-text">{% trans "Sales Orders"|capfirst %}</span>
</div>
</a>
</li>
{% endif %}
{% if perms.django_ledger.view_invoicemodel %}
<li class="nav-item">
<a class="nav-link" href="{% url 'invoice_list' request.dealer.slug %}">
<div class="d-flex align-items-center">
<span class="nav-link-icon"><span class="fas fa-file-invoice"></span></span><span class="nav-link-text">{% trans "invoices"|capfirst %}</span>
</div>
</a>
</li>
{% endif %}
{% if perms.inventory.view_payment %}
<li class="nav-item">
<a class="nav-link" href="{% url 'payment_list' request.dealer.slug %}">
<div class="d-flex align-items-center">
<span class="nav-link-icon"><span class="fas fa-money-check"></span></span><span class="nav-link-text">{% trans "payments"|capfirst %}</span>
</div>
</a>
</li>
{% endif %}
</ul>
</div>
</div>
{% endif %}
{% if perms.django_ledger.can_view_financials %} {% if perms.django_ledger.can_view_financials %}
<div class="nav-item-wrapper"> <div class="nav-item-wrapper">
<a class="nav-link dropdown-indicator label-1" href="#nv-financial" role="button" data-bs-toggle="collapse" aria-expanded="false" aria-controls="nv-financial"> <a class="nav-link dropdown-indicator label-1" href="#nv-financial" role="button" data-bs-toggle="collapse" aria-expanded="false" aria-controls="nv-financial">
@ -225,11 +223,11 @@
<a class="nav-link" href="{% url 'bank_account_list' request.dealer.slug %}"> <a class="nav-link" href="{% url 'bank_account_list' request.dealer.slug %}">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<span class="nav-link-icon"><span data-feather="credit-card"></span></span><span class="nav-link-text">{% trans 'Bank Accounts'|capfirst %}</span> <span class="nav-link-icon"><span data-feather="credit-card"></span></span><span class="nav-link-text">{% trans 'Bank Accounts'|capfirst %}</span>
</div> </div>
</a> </a>
</li> </li>
{% endif %} {% endif %}
{% if perms.django_ledger.view_journalentrymodel %} {% if perms.django_ledger.view_journalentrymodel %}
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{% url 'ledger_list' request.dealer.slug %}"> <a class="nav-link" href="{% url 'ledger_list' request.dealer.slug %}">
@ -290,7 +288,7 @@
</ul> </ul>
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% if perms.django_ledger.can_view_reports %} {% if perms.django_ledger.can_view_reports %}
<div class="nav-item-wrapper"> <div class="nav-item-wrapper">
<a class="nav-link dropdown-indicator label-1" href="#nv-reports" role="button" data-bs-toggle="collapse" aria-expanded="false" aria-controls="nv-reports"> <a class="nav-link dropdown-indicator label-1" href="#nv-reports" role="button" data-bs-toggle="collapse" aria-expanded="false" aria-controls="nv-reports">

View File

@ -164,6 +164,7 @@
<tr> <tr>
<th>{% trans "Custom Card" %}</th> <th>{% trans "Custom Card" %}</th>
<td> <td>
{% if perms.inventory.add_customcard %}
<button type="button" <button type="button"
class="btn btn-sm btn-phoenix-success" class="btn btn-sm btn-phoenix-success"
data-bs-toggle="modal" data-bs-toggle="modal"
@ -172,6 +173,7 @@
hx-target=".main-modal-body" hx-target=".main-modal-body"
hx-swap="innerHTML" hx-swap="innerHTML"
>{% trans 'Add' %}</button> >{% trans 'Add' %}</button>
{% endif %}
</td> </td>
</tr> </tr>
{% endif %} {% endif %}
@ -190,15 +192,16 @@
<tr> <tr>
<th>{% trans "Registration" %}</th> <th>{% trans "Registration" %}</th>
<td> <td>
{% if perms.inventory.add_carregistration %}
<button type="button" <button type="button"
class="btn btn-sm btn-phoenix-success" class="btn btn-sm btn-phoenix-success"
data-bs-toggle="modal" data-bs-toggle="modal"
data-bs-target="#mainModal" data-bs-target="#mainModal"
hx-get="{% url 'add_registration' request.dealer.slug car.slug %}" hx-get="{% url 'add_registration' request.dealer.slug car.slug %}"
hx-target=".main-modal-body" hx-target=".main-modal-body"
hx-swap="innerHTML" hx-swap="innerHTML"
>{% trans 'Add' %}</button> >{% trans 'Add' %}</button>
{% endif %}
</td> </td>
</tr> </tr>
{% endif %} {% endif %}
@ -212,14 +215,18 @@
{% else %} {% else %}
{{ car.location.showroom.get_local_name }} {{ car.location.showroom.get_local_name }}
{% endif %} {% endif %}
<a href="{% url 'update_car_location' car.slug car.location.pk %}" {% if perms.inventory.add_cartransfer %}
class="btn btn-phoenix-danger btn-sm"> <a href="{% url 'update_car_location' car.slug car.location.pk %}"
{% trans "transfer"|capfirst %} class="btn btn-phoenix-danger btn-sm">
</a> {% trans "transfer"|capfirst %}
</a>
{% endif %}
{% else %} {% else %}
{% trans "No location available." %} {% trans "No location available." %}
<a href="{% url 'add_car_location' car.slug %}" {% if perms.inventory.add_carlocation %}
class="btn btn-phoenix-success btn-sm ms-2">{% trans "Add" %}</a> <a href="{% url 'add_car_location' car.slug %}"
class="btn btn-phoenix-success btn-sm ms-2">{% trans "Add" %}</a>
{% endif %}
{% endif %} {% endif %}
{% endif %} {% endif %}
</td> </td>
@ -243,11 +250,8 @@
</div> </div>
</div> </div>
</div> </div>
<div class="col-lg-6 col-xl-6"> <div class="col-lg-6 col-xl-6">
{% if perms.inventory.view_carfinance%} {% if perms.inventory.view_carfinance%}
<div class="card rounded shadow d-flex align-content-center {% if car.get_transfer %}transfer{% endif %}"> <div class="card rounded shadow d-flex align-content-center {% if car.get_transfer %}transfer{% endif %}">
<p class="card-header rounded-top fw-bold">{% trans 'Financial Details' %}</p> <p class="card-header rounded-top fw-bold">{% trans 'Financial Details' %}</p>
@ -296,7 +300,7 @@
<a href="{% url 'car_finance_update' request.dealer.slug car.finances.pk %}" class="btn btn-phoenix-warning btn-sm mb-3">{% trans "Edit" %}</a> <a href="{% url 'car_finance_update' request.dealer.slug car.finances.pk %}" class="btn btn-phoenix-warning btn-sm mb-3">{% trans "Edit" %}</a>
{% else %} {% else %}
<span class="badge bg-danger">{% trans "Cannot Edit, Car in Transfer." %}</span> <span class="badge bg-danger">{% trans "Cannot Edit, Car in Transfer." %}</span>
{% endif %} {% endif %}
</td> </td>
</tr> </tr>
{% endif %} {% endif %}
@ -342,7 +346,7 @@
style="background-color: rgb({{ car.colors.interior.rgb }})"></div> style="background-color: rgb({{ car.colors.interior.rgb }})"></div>
</td> </td>
</tr> </tr>
{% if perms.inventory.change_carcolors%} {% if perms.inventory.change_carcolors%}
<tr> <tr>
<td colspan="2"> <td colspan="2">
@ -433,7 +437,7 @@
</td> </td>
</tr> </tr>
{% endif %} {% endif %}
</tbody> </tbody>
{% endif %} {% endif %}
</table> </table>
@ -501,7 +505,7 @@
alt=""> alt="">
{% endif %} {% endif %}
</div> </div>
<!-- Custom Card Modal --> <!-- Custom Card Modal -->
<div class="modal fade" <div class="modal fade"
id="customCardModal" id="customCardModal"

View File

@ -19,7 +19,7 @@
<div class="card-body"> <div class="card-body">
<!-- Basic Information --> <!-- Basic Information -->
<div class="row mb-4"> <div class="row mb-4">
<div class="col-md-6"> <div class="col-md-4">
<h4>{% trans "Customer Information" %}</h4> <h4>{% trans "Customer Information" %}</h4>
<p> <p>
<strong>{% trans "Name" %}:</strong> {{ sale_order.full_name }}<br> <strong>{% trans "Name" %}:</strong> {{ sale_order.full_name }}<br>
@ -30,7 +30,7 @@
</p> </p>
</div> </div>
<div class="col-md-6"> <div class="col-md-4">
<h4>{% trans "Order Details" %}</h4> <h4>{% trans "Order Details" %}</h4>
<p> <p>
<strong>{% trans "Order Date" %}:</strong> {{ sale_order.order_date|date }}<br> <strong>{% trans "Order Date" %}:</strong> {{ sale_order.order_date|date }}<br>
@ -38,6 +38,30 @@
<strong>{% trans "Created By" %}:</strong> {{ sale_order.created_by }} <strong>{% trans "Created By" %}:</strong> {{ sale_order.created_by }}
</p> </p>
</div> </div>
{% if not prems.inventory.change_saleorder %}
<div class="col-md-4 row mb-4">
<div class="col-12">
<h4>{% trans "Update Order Status" %}</h4>
<form method="post" action="">
{% csrf_token %}
<div class="mb-3">
<label for="status" class="form-label">{% trans "Status" %}</label>
<select class="form-select" id="status" name="status">
<option value="PENDING_APPROVAL" {% if sale_order.status == 'PENDING_APPROVAL' %}selected{% endif %}>{% trans "Pending Approval" %}</option>
<option value="APPROVED" {% if sale_order.status == 'APPROVED' %}selected{% endif %}>{% trans "Approved" %}</option>
<option value="IN_FINANCING" {% if sale_order.status == 'IN_FINANCING' %}selected{% endif %}>{% trans "In Financing" %}</option>
<option value="PARTIALLY_PAID" {% if sale_order.status == 'PARTIALLY_PAID' %}selected{% endif %}>{% trans "Partially Paid" %}</option>
<option value="FULLY_PAID" {% if sale_order.status == 'FULLY_PAID' %}selected{% endif %}>{% trans "Fully Paid" %}</option>
<option value="PENDING_DELIVERY" {% if sale_order.status == 'PENDING_DELIVERY' %}selected{% endif %}>{% trans "Pending Delivery" %}</option>
<option value="DELIVERED" {% if sale_order.status == 'DELIVERED' %}selected{% endif %}>{% trans "Delivered" %}</option>
<option value="CANCELLED" {% if sale_order.status == 'CANCELLED' %}selected{% endif %}>{% trans "Cancelled" %}</option>
</select>
</div>
<button type="submit" class="btn btn-primary">{% trans "Save" %}</button>
</form>
</div>
</div>
{% endif %}
</div> </div>
<!-- Estimate Information --> <!-- Estimate Information -->

View File

@ -109,11 +109,6 @@
{{ _("Customer Name")}}</th> {{ _("Customer Name")}}</th>
<th class="sort align-middle ps-3" scope="col" data-sort="customer_address" style="width:5%;">{{ _("Customer Address")}}</th> <th class="sort align-middle ps-3" scope="col" data-sort="customer_address" style="width:5%;">{{ _("Customer Address")}}</th>
<th class="sort align-middle ps-3" scope="col" data-sort="customer_phone" style="width:5%;">{{ _("Customer Phone")}}</th> <th class="sort align-middle ps-3" scope="col" data-sort="customer_phone" style="width:5%;">{{ _("Customer Phone")}}</th>
<th class="sort align-middle ps-3" scope="col" data-sort="make" style="width:5%;">{{ _("Make") }}</th>
<th class="sort align-middle ps-3" scope="col" data-sort="model" style="width:5%;">{{ _("Model") }}</th>
<th class="sort align-middle ps-3" scope="col" data-sort="vin" style="width:5%;">{{ _("VIN") }}</th>
<th class="sort align-middle ps-3" scope="col" data-sort="trim" style="width:5%;">{{ _("Trim") }}</th>
<th class="sort align-middle ps-3" scope="col" data-sort="price" style="width:5%;">{{ _("Price") }}</th>
<th class="sort align-middle ps-3" scope="col" data-sort="quotation" style="width:7%;">{{ _("Quotation") }}</th> <th class="sort align-middle ps-3" scope="col" data-sort="quotation" style="width:7%;">{{ _("Quotation") }}</th>
<th class="sort align-middle ps-3" scope="col" data-sort="invoice" style="width:7%;">{{ _("Invoice") }}</th> <th class="sort align-middle ps-3" scope="col" data-sort="invoice" style="width:7%;">{{ _("Invoice") }}</th>
<th class="sort align-middle ps-3" scope="col" data-sort="status" style="width:7%;">{{ _("Status") }}</th> <th class="sort align-middle ps-3" scope="col" data-sort="status" style="width:7%;">{{ _("Status") }}</th>
@ -125,7 +120,7 @@
{% for tx in txs %} {% for tx in txs %}
<tr class="position-static"> <tr class="position-static">
<td class="align-middle white-space-nowrap customer_name px-1"> <td class="align-middle white-space-nowrap customer_name px-1">
<p class="mb-0 fs-9 text-body">{{tx.customer}}</p> <p class="mb-0 fs-9 text-body">{{tx}}</p>
</td> </td>
<td class="align-middle white-space-nowrap customer_address"> <td class="align-middle white-space-nowrap customer_address">
<p class="mb-0 fs-9 text-body">{{tx.customer.address}}</p> <p class="mb-0 fs-9 text-body">{{tx.customer.address}}</p>
@ -133,24 +128,11 @@
<td class="align-middle white-space-nowrap customer_phone"> <td class="align-middle white-space-nowrap customer_phone">
<p class="mb-0 fs-9 text-body">{{tx.customer.phone_number}}</p> <p class="mb-0 fs-9 text-body">{{tx.customer.phone_number}}</p>
</td> </td>
<td class="align-middle time white-space-nowrap make">{{tx.info.make}}</td>
<td class="align-middle white-space-nowrap model">
<p class="mb-0 fs-9 text-body">{{tx.info.model}}</p>
</td>
<td class="align-middle white-space-nowrap vin">
<p class="mb-0 fs-9 text-body">{{tx.info.vin}}</p>
</td>
<td class="align-middle white-space-nowrap trim">
<p class="fw-bo text-body fs-9 mb-0">{{tx.info.trim}}</p>
</td>
<td class="align-middle white-space-nowrap price">
<p class="fw-bo text-body fs-9 mb-0">{{tx.finance.total}}</p>
</td>
<td class="align-middle white-space-nowrap quotation"> <td class="align-middle white-space-nowrap quotation">
{% if tx.estimate %} {% if tx.estimate %}
<p class="fw-bo text-body fs-9 mb-0"> <p class="fw-bo text-body fs-9 mb-0">
<a href="{% url 'estimate_detail' request.dealer.slug tx.estimate.uuid %}"> <a href="{% url 'estimate_detail' request.dealer.slug tx.estimate.uuid %}">
{{tx.estimate.estimate_number}} {{ tx.estimate.estimate_number}}
</a><br> </a><br>
{% if tx.estimate.status == "draft" %} {% if tx.estimate.status == "draft" %}
<span class="badge badge-phoenix badge-phoenix-warning">{{tx.estimate.status}}</span> <span class="badge badge-phoenix badge-phoenix-warning">{{tx.estimate.status}}</span>