Compare commits

...

6 Commits

Author SHA1 Message Date
85efc27bf1 update 2025-07-06 12:54:14 +03:00
8a073d8df8 update 2025-07-06 12:52:32 +03:00
ae4d981d37 update the signup view and other perms changes 2025-07-06 12:47:33 +03:00
7a9b9901d5 update 2025-07-06 12:47:20 +03:00
d6f4ebfd4e update the saleorder 2025-07-03 15:57:09 +03:00
37661aa50e Merge pull request 'Permissions' (#106) from frontend into main
Reviewed-on: #106
2025-07-03 15:56:08 +03:00
22 changed files with 1323 additions and 436 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
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})"
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

@ -24,6 +24,10 @@ from django.utils.timezone import now
from django.db import transaction
from django_q.tasks import async_task
#logging
import logging
logger=logging.getLogger(__name__)
User = get_user_model()
# @receiver(post_save, sender=models.SaleQuotation)
@ -88,7 +92,15 @@ def create_car_location(sender, instance, created, **kwargs):
"""
try:
if created:
# Log that the signal was triggered for a new car
logger.debug(f"Post-save signal triggered for new Car (VIN: {instance.vin}). Attempting to create CarLocation.")
if instance.dealer is None:
# Log the critical data integrity error before raising
logger.error(
f"Attempted to create CarLocation for car (VIN: {instance.vin}) "
f"but 'dealer' field is missing. This indicates a data integrity issue or unhandled logic."
)
raise ValueError(
f"Cannot create CarLocation for car {instance.vin}: dealer is missing."
)
@ -99,7 +111,18 @@ def create_car_location(sender, instance, created, **kwargs):
showroom=instance.dealer,
description=f"Initial location set for car {instance.vin}.",
)
# Log successful CarLocation creation
logger.info(
f"Successfully created CarLocation for new car (VIN: {instance.vin}) "
f"with owner '{instance.dealer.name}' (ID: {instance.dealer.pk})."
)
except Exception as e:
# --- Single-line log for general error during CarLocation creation ---
logger.error(
f"Failed to create CarLocation for car (VIN: {instance.vin}). "
f"An unexpected error occurred: {e}",
exc_info=True
)
print(f"Failed to create CarLocation for car {instance.vin}: {e}")
@ -517,6 +540,8 @@ def track_lead_status_change(sender, instance, **kwargs):
:return: None
"""
if instance.pk: # Ensure the instance is being updated, not created
# Log that a lead update is being checked for status changes
logger.debug(f"Checking for status change on Lead ID: {instance.pk}.")
try:
old_lead = models.Lead.objects.get(pk=instance.pk)
if old_lead.status != instance.status: # Check if status has changed
@ -526,7 +551,17 @@ def track_lead_status_change(sender, instance, **kwargs):
new_status=instance.status,
changed_by=instance.staff, # Assuming the assigned staff made the change
)
# --- Single-line log for successful status change and history creation ---
logger.info(
f"Lead ID: {instance.pk} status changed from '{old_lead.status}' to '{instance.status}'. "
f"LeadStatusHistory recorded by Staff: {instance.staff.username if instance.staff else 'N/A'}."
)
except models.Lead.DoesNotExist:
# --- Single-line log for expected Lead.DoesNotExist (e.g., during initial object creation) ---
logger.debug(
f"Lead ID: {instance.pk} not found in database when checking for status change. "
f"This might occur during initial object creation. Skipping status history tracking."
)
pass # Ignore if the lead doesn't exist (e.g., during initial creation)

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

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>
</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 ">
<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">
@ -37,21 +49,101 @@
</ul>
</div>
<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">
<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 class="needs-validation" id="wizardValidationForm1" novalidate="novalidate" data-wizard-form="1" data-ref-f1>
<div class="mb-3">
<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>
</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 class="needs-validation" id="wizardValidationForm2" novalidate="novalidate" data-wizard-form="2" data-ref-f2>
<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>
</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 class="needs-validation" id="wizardValidationForm3" novalidate="novalidate" data-wizard-form="3" data-ref-f3>
<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>
</div>
<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="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>
<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 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">
<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>
<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>
@ -84,167 +176,43 @@
</section>
<section class="pt-lg-0 pt-xl-8">
{% include 'footer.html' %}
</section>
<script src="{% static 'js/phoenix.js' %}"></script>
{% endblock content %}
</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>
{% block customJS %}
<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>
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) {
function validatePassword(password, confirmPassword) {
return password === confirmPassword && password.length > 7 && password !== '';
}
function validateEmail(email) {
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
return emailRegex.test(email) && email !== '';
}
function validateform2(name,arabic_name,phone_number) {
if (name === '' || arabic_name === '' || phone_number === '' || phone_number.length < 10 || !phone_number.startsWith('056')) {
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);
}
});
return true
}
function validate_sa_phone_number(phone_number) {
const phone_numberRegex = /^056[0-9]{7}$/;
return phone_numberRegex.test(phone_number) && phone_numberRegex !== '';
}
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;
const forms = document.querySelectorAll('.needs-validation');
const formData = {};
forms.forEach(form => {
const fields = form.querySelectorAll('input,textarea,select');
fields.forEach(field => {
formData[field.name] = field.value;
});
});
return formData;
}
function showLoading() {
@ -268,7 +236,7 @@
titleText: msg
});
}
function getCookie(name) {
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== "") {
const cookies = document.cookie.split(";");
@ -282,6 +250,33 @@
}
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>
{% endblock customJS %}

View File

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

View File

@ -63,10 +63,12 @@
class="btn btn-sm btn-phoenix-primary me-md-2">
{% trans 'View' %}
</a>
{% if perms.django_ledger.change_billmodel %}
<a href="{% url 'django_ledger:bill-update' entity_slug=entity_slug bill_pk=bill.uuid %}"
class="btn btn-sm btn-phoenix-warning me-md-2">
{% trans 'Update' %}
</a>
{% if bill.can_pay %}
<button onclick="djLedger.toggleModal('{{ bill.get_html_id }}')"
@ -80,6 +82,7 @@
{% trans 'Cancel' %}
</button>
{% endif %}
{% endif %}
</div>
</div>
</div>
@ -201,9 +204,10 @@
<div class="card-footer p-0">
<div class="d-flex flex-wrap gap-2 mt-2">
<!-- Update Button -->
{% if perms.django_ledger.change_billmodel%}
<a href="{% url 'bill-update' dealer_slug=dealer_slug entity_slug=entity_slug bill_pk=bill.uuid %}" class="btn btn-phoenix-primary">
<i class="fas fa-edit me-2"></i>{% trans 'Update' %}
</a>
<!-- Mark as Draft -->
{% if bill.can_draft %}
<button class="btn btn-phoenix-success"
@ -247,6 +251,7 @@
</button>
{% modal_action_v2 bill bill.get_mark_as_canceled_url bill.get_mark_as_canceled_message bill.get_mark_as_canceled_html_id %}
{% endif %}
{% endif %}
</div>
</div>
</div>
@ -254,6 +259,7 @@
{% endif %}
{% else %}
<!-- Create Bill Card -->
{% if perms.django_ledger.add_billmodel%}
<div class=" bg-light">
<div class="card-body text-center p-5">
<a href="{% url 'django_ledger:bill-create' entity_slug=entity_slug %}"
@ -263,6 +269,7 @@
</a>
</div>
</div>
{% endif %}
{% endif %}
</div>

View File

@ -5,125 +5,60 @@
<div class="collapse navbar-collapse" id="navbarVerticalCollapse">
<div class="navbar-vertical-content">
<ul class="navbar-nav flex-column" id="navbarVerticalNav">
<li class="nav-item">
<p class="navbar-vertical-label">Apps</p>
<hr class="navbar-vertical-line" />
{% if perms.inventory.can_view_inventory %}
<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">
<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="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">
<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>
<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-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>
<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>
</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%}
{% if perms.inventory.view_car%}
<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">
<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>
</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>
{% 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>
{% 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 %}
{% 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>
</ul>
</div>
</div>
</div>
{% endif %}
{% if perms.inventory.can_view_crm %}
@ -200,6 +135,69 @@
</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>
<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 %}
<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">
@ -225,11 +223,11 @@
<a class="nav-link" href="{% url 'bank_account_list' request.dealer.slug %}">
<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>
</div>
</div>
</a>
</li>
{% endif %}
{% if perms.django_ledger.view_journalentrymodel %}
<li class="nav-item">
<a class="nav-link" href="{% url 'ledger_list' request.dealer.slug %}">
@ -290,7 +288,7 @@
</ul>
</div>
</div>
{% endif %}
{% endif %}
{% if perms.django_ledger.can_view_reports %}
<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">

View File

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

View File

@ -9,7 +9,9 @@
<div class="d-flex justify-content-between mb-2">
<h3 class="">{% trans "Expenses" %}</h3>
{% if perms.django_ledger.add_itemmodel %}
<a href="{% url 'item_expense_create' request.dealer.slug %}" class="btn btn-md btn-phoenix-primary"><i class="fa fa-plus me-2"></i>{% trans "Add Expense" %}</a>
{% endif %}
</div>
{% include "partials/search_box.html" %}
{% if page_obj.object_list %}
@ -39,10 +41,12 @@
{{ expense.uom }}
</td>
<td class="align-middle product white-space-nowrap">
{% if perms.django_ledger.change_itemmodel %}
<a href="{% url 'item_expense_update' request.dealer.slug expense.pk %}"
class="btn btn-sm btn-phoenix-success">
{% trans "Update" %}
</a>
{% endif %}
</td>
<td class="align-middle product white-space-nowrap">

View File

@ -8,7 +8,9 @@
<div class="d-flex justify-content-between mb-2">
<h3 class="">{% trans "Services" %}</h3>
{% if perms.inventory.add_additionalservices %}
<a href="{% url 'item_service_create' request.dealer.slug %}" class="btn btn-md btn-phoenix-primary"><i class="fa fa-plus me-2"></i>{% trans "Add Service" %}</a>
{% endif %}
</div>
{% include "partials/search_box.html" %}
{% if page_obj.object_list %}
@ -46,10 +48,12 @@
{{ service.item.co }}
</td>
<td class="align-middle white-space-nowrap text-start">
{% if perms.inventory.add_additionalservices %}
<a href="{% url 'item_service_update' request.dealer.slug service.pk %}"
class="btn btn-sm btn-phoenix-success">
{% trans "Update" %}
</a>
{% endif %}
</td>
</tr>

View File

@ -8,7 +8,9 @@
<div class="d-flex justify-content-between mb-2">
<h3 class="">{% trans "Bank Accounts" %}</h3>
{% if perms.django_ledger.add_bankaccountmodel%}
<a href="{% url 'bank_account_create' request.dealer.slug %}" class="btn btn-md btn-phoenix-primary"><i class="fa fa-plus me-2"></i>{% trans "Add Bank Account" %}</a>
{% endif %}
</div>
{% include "partials/search_box.html" %}
{% if page_obj.object_list %}
@ -38,10 +40,12 @@
{{ bank.account_type|capfirst }}
</td>
<td class="align-middle product white-space-nowrap">
{% if perms.django_ledger.change_bankaccountmodel%}
<a href="{% url 'bank_account_update' request.dealer.slug bank.pk %}"
class="btn btn-sm btn-phoenix-success">
{% trans "Update" %}
</a>
{% endif %}
</td>
</tr>

View File

@ -14,7 +14,9 @@
<div class="row mt-4">
<div class="d-flex justify-content-between mb-2">
<h3 class="">{% trans "Bills" %}</h3>
{% if perms.django_ledger.add_billmodel %}
<a href="{% url 'bill-create' request.dealer.slug entity.slug %}" class="btn btn-md btn-phoenix-primary"><i class="fa fa-plus me-2"></i>{% trans 'New Bill' %}</a>
{% endif %}
</div>
<div class="col-12">
@ -78,12 +80,14 @@
{{bill.vendor.vendor_name}}
</td>
<td class="align-middle product white-space-nowrap">
{% if perms.django_ledger.view_billmodel %}
<div class="btn-reveal-trigger position-static">
<button class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10" type="button" data-bs-toggle="dropdown" data-boundary="window" aria-haspopup="true" aria-expanded="false" data-bs-reference="parent"><span class="fas fa-ellipsis-h fs-10"></span></button>
<div class="dropdown-menu dropdown-menu-end py-2">
<a href="{% url 'bill-detail' dealer_slug=request.dealer.slug entity_slug=entity.slug bill_pk=bill.pk %}" class="dropdown-item text-success-dark">{% trans 'View' %}</a>
</div>
</div>
{% endif %}
</td>
</tr>
{% empty %}

View File

@ -19,7 +19,7 @@
<div class="card-body">
<!-- Basic Information -->
<div class="row mb-4">
<div class="col-md-6">
<div class="col-md-4">
<h4>{% trans "Customer Information" %}</h4>
<p>
<strong>{% trans "Name" %}:</strong> {{ sale_order.full_name }}<br>
@ -30,7 +30,7 @@
</p>
</div>
<div class="col-md-6">
<div class="col-md-4">
<h4>{% trans "Order Details" %}</h4>
<p>
<strong>{% trans "Order Date" %}:</strong> {{ sale_order.order_date|date }}<br>
@ -38,6 +38,30 @@
<strong>{% trans "Created By" %}:</strong> {{ sale_order.created_by }}
</p>
</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>
<!-- Estimate Information -->

View File

@ -109,11 +109,6 @@
{{ _("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_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="invoice" style="width:7%;">{{ _("Invoice") }}</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 %}
<tr class="position-static">
<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 class="align-middle white-space-nowrap customer_address">
<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">
<p class="mb-0 fs-9 text-body">{{tx.customer.phone_number}}</p>
</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">
{% if tx.estimate %}
<p class="fw-bo text-body fs-9 mb-0">
<a href="{% url 'estimate_detail' request.dealer.slug tx.estimate.uuid %}">
{{tx.estimate.estimate_number}}
{{ tx.estimate.estimate_number}}
</a><br>
{% if tx.estimate.status == "draft" %}
<span class="badge badge-phoenix badge-phoenix-warning">{{tx.estimate.status}}</span>

View File

@ -11,8 +11,11 @@
<h3 class="">
{{ _("Vendors") |capfirst }}
</h2>
<a href="{% url 'vendor_create' request.dealer.slug %}"
class="btn btn-md btn-phoenix-primary"><i class="fa fa-plus me-2"></i>{{ _("Add Vendor") }}</a>
{% if perms.django_ledger.add_vendormodel %}
<a href="{% url 'vendor_create' request.dealer.slug %}" class="btn btn-md btn-phoenix-primary">
<i class="fa fa-plus me-2"></i>{{ _("Add Vendor") }}
</a>
{% endif %}
</div>
{% include "partials/search_box.html" %}
{% if page_obj.object_list %}
@ -121,14 +124,21 @@
<span class="fas fa-ellipsis-h fs-10"></span>
</button>
<div class="dropdown-menu dropdown-menu-end py-2">
{% if perms.django_ledger.change_vendormodel %}
<a href="{% url 'vendor_update' request.dealer.slug vendor.slug %}"
class="dropdown-item text-success-dark">{% trans "Edit" %}</a>
class="dropdown-item text-success-dark">{% trans "Edit" %}
</a>
{% endif %}
{% if perms.django_ledger.delete_vendormodel %}
<div class="dropdown-divider"></div>
<button class="delete-btn dropdown-item text-danger"
data-url="{% url 'vendor_delete' request.dealer.slug vendor.slug %}"
data-message="{{ _("Are you sure you want to delete this vendor") }}?"
data-bs-toggle="modal"
data-bs-target="#deleteModal">{{ _("Delete") }}</button>
data-bs-target="#deleteModal">{{ _("Delete") }}
</button>
{% endif %}
</div>
</div>
</td>

View File

@ -28,10 +28,14 @@
</ul>
</div>
<div class="card-footer d-flex">
{% if perms.django_ledger.change_vendormodel %}
<a class="btn btn-sm btn-phoenix-primary me-1" href="{% url 'vendor_update' request.dealer.slug vendor.slug %}">
{% trans "Edit" %}
<i class="fa fa-pencil"></i>
</a>
{% endif %}
{% if perms.django_ledger.delete_vendormodel %}
<button class="btn btn-phoenix-danger btn-sm delete-btn"
data-url="{% url 'vendor_delete' request.dealer.slug vendor.slug %}"
data-message="{{ _("Are you sure you want to delete this vendor")}}?"
@ -39,6 +43,7 @@
{{ _("Delete") }}
<i class="fas fa-trash"></i>
</button>
{% endif %}
</div>
</div>
</div>