This commit is contained in:
gitea 2025-02-26 12:25:46 +00:00
parent a266c99028
commit 219a3e9426
20 changed files with 307 additions and 145 deletions

View File

@ -930,4 +930,6 @@ class DealerSettingsForm(forms.ModelForm):
class Meta:
model = DealerSettings
fields = "__all__"
class LeadTransferForm(forms.Form):
transfer_to = forms.ModelChoiceField(label="to",queryset=Staff.objects.all())

View File

@ -0,0 +1,33 @@
# Generated by Django 4.2.17 on 2025-02-26 08:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0048_remove_dealersettings_bill_payable_account_and_more'),
]
operations = [
migrations.AlterField(
model_name='lead',
name='status',
field=models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('qualified', 'Qualified'), ('contacted', 'Contacted'), ('converted', 'Converted'), ('canceled', 'Canceled')], db_index=True, default='new', max_length=50, verbose_name='Status'),
),
migrations.AlterField(
model_name='leadstatushistory',
name='new_status',
field=models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('qualified', 'Qualified'), ('contacted', 'Contacted'), ('converted', 'Converted'), ('canceled', 'Canceled')], max_length=50, verbose_name='New Status'),
),
migrations.AlterField(
model_name='leadstatushistory',
name='old_status',
field=models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('qualified', 'Qualified'), ('contacted', 'Contacted'), ('converted', 'Converted'), ('canceled', 'Canceled')], max_length=50, verbose_name='Old Status'),
),
migrations.AlterField(
model_name='opportunity',
name='status',
field=models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('qualified', 'Qualified'), ('contacted', 'Contacted'), ('converted', 'Converted'), ('canceled', 'Canceled')], default='new', max_length=20, verbose_name='Status'),
),
]

View File

@ -15,6 +15,8 @@ from django_ledger.io.io_core import get_localdate
from django.core.exceptions import ValidationError
from phonenumber_field.modelfields import PhoneNumberField
from django.utils.timezone import now
from inventory.utils import get_user_type
from .mixins import LocalizedNameMixin
from django_ledger.models import EntityModel, ItemModel,EstimateModel,InvoiceModel,AccountModel
from django.contrib.contenttypes.fields import GenericForeignKey
@ -443,11 +445,12 @@ class Car(models.Model):
hash_object.update(f"{self.id_car_make.name}{self.id_car_model.name}{self.year}{self.id_car_serie.name}{self.id_car_trim.name}{color}".encode('utf-8'))
return hash_object.hexdigest()
def mark_as_sold(self,user):
def mark_as_sold(self,request):
dealer = get_user_type(request)
self.cancel_reservation()
self.status = CarStatusChoices.SOLD
self.status = CarStatusChoices.SOLD
self.save()
Activity.objects.create(content_object=self, notes="Car Sold",created_by=user,activity_type=ActionChoices.SALE_CAR)
Activity.objects.create(dealer=dealer,content_object=self, notes="Car Sold",created_by=request.user,activity_type=ActionChoices.SALE_CAR)
def cancel_reservation(self):
if self.reservations.exists():
@ -997,6 +1000,8 @@ class Status(models.TextChoices):
PENDING = "pending", _("Pending")
IN_PROGRESS = "in_progress", _("In Progress")
QUALIFIED = "qualified", _("Qualified")
CONTACTED = "contacted", _("Contacted")
CONVERTED = "converted", _("Converted")
CANCELED = "canceled", _("Canceled")
@ -1245,6 +1250,7 @@ class Lead(models.Model):
customer.additional_info = {}
customer.additional_info.update({"info":self.to_dict()})
customer.additional_info.update({"stage":"qualified"})
self.customer = customer
self.status = Status.QUALIFIED
customer.save()

View File

@ -670,16 +670,12 @@ def create_customer_user(sender, instance, created, **kwargs):
username=instance.email,
email=instance.email,
)
instance.additional_info["customer_info"] = to_dict(instance)
# customer_info = instance.additional_info.get("customer_info",None)
# user.first_name = customer_info.get("first_name", None) if customer_info else ""
# user.last_name = customer_info.get("last_name", None) if customer_info else ""
instance.additional_info.update({"user_info": to_dict(user)})
user.set_unusable_password()
user.save()
instance.user = user
instance.save()
instance.save()
# Create Item
@receiver(post_save, sender=models.Car)
@ -894,5 +890,5 @@ def check_users_quota(sender, instance, **kwargs):
if allowed_users is None:
raise ValidationError(_("The user quota for staff members is not defined. Please contact support."))
current_staff_count = instance.dealer.staff.count()
if current_staff_count == allowed_users:
if current_staff_count > allowed_users:
raise ValidationError(_("You have reached the maximum number of staff users allowed for your plan."))

View File

@ -121,6 +121,11 @@ urlpatterns = [
views.schedule_lead,
name="schedule_lead",
),
path(
"crm/leads/<int:pk>/transfer/",
views.lead_transfer,
name="lead_transfer",
),
path(
"crm/opportunities/<int:pk>/add_note/",
@ -364,7 +369,7 @@ urlpatterns = [
),
path(
"organizations/<uuid:pk>/delete/",
views.OrganizationDeleteView.as_view(),
views.OrganizationDeleteView,
name="organization_delete",
),
# Representative URLs

View File

@ -18,7 +18,7 @@ from django.db import transaction
from django.db.models import Func
from django.contrib import messages
from django.http import JsonResponse
from django.forms import HiddenInput
from django.forms import HiddenInput, ValidationError
from django.shortcuts import HttpResponse
from django.db.models import Sum, F, Count
from django.core.paginator import Paginator
@ -462,14 +462,12 @@ class AjaxHandlerView(LoginRequiredMixin, View):
)
manufacturer_name, model_name, year_model = result.values()
make = get_make(manufacturer_name)
model = get_model(model_name, make)
car_make = get_make(manufacturer_name)
car_model = get_model(model_name, car_make)
logger.info(
f"VIN decoded using {decoding_method}: Make={manufacturer_name}, Model={model_name}, Year={year_model}"
)
car_model = model
car_make = make
)
if not car_make:
return JsonResponse(
@ -1148,15 +1146,12 @@ class DealerDetailView(LoginRequiredMixin, DetailView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
dealer = self.object
# Fetch current staff count from the annotated queryset
staff_count = dealer.staff_count
# Get the quota value dynamically
quota_dict = get_user_quota(dealer.user)
allowed_users = quota_dict.get("Users", None) # Fetch quota or default to None
context["staff_count"] = staff_count
context["allowed_users"] = allowed_users
context["quota_display"] = f"{staff_count}/{allowed_users}" if allowed_users is not None else "N/A"
@ -1185,9 +1180,7 @@ class CustomerListView(LoginRequiredMixin, ListView):
def get_queryset(self):
query = self.request.GET.get("q")
dealer = get_user_type(self.request)
customers = dealer.entity.get_customers().filter(additional_info__type="customer")
return apply_search_filters(customers, query)
def get_context_data(self, **kwargs):
@ -1251,16 +1244,12 @@ def add_activity_to_customer(request, pk):
def CustomerCreateView(request):
form = forms.CustomerForm()
if request.method == "POST":
form = forms.CustomerForm(request.POST)
dealer = get_user_type(request)
if form.is_valid():
email = form.cleaned_data["email"]
# Check if customer with this email already exists
if dealer.entity.get_customers().filter(email=email).exists():
if form.is_valid():
if dealer.entity.get_customers().filter(email=form.cleaned_data["email"]).exists():
messages.error(request, _("Customer with this email already exists."))
else:
# Create customer name
@ -1269,7 +1258,7 @@ def CustomerCreateView(request):
f"{form.cleaned_data['middle_name']} "
f"{form.cleaned_data['last_name']}"
)
customer_dict = { x: request.POST[x] for x in request.POST if x != "csrfmiddlewaretoken"}
# Create customer instance
try:
customer = dealer.entity.create_customer(
@ -1278,19 +1267,12 @@ def CustomerCreateView(request):
"customer_name": customer_name,
"address_1": form.cleaned_data["address"],
"phone": form.cleaned_data["phone_number"],
"email": email,
"email": form.cleaned_data["email"],
}
)
customer.additional_info = {
"customer_info": {
"first_name": form.cleaned_data["first_name"],
"middle_name": form.cleaned_data["middle_name"],
"last_name": form.cleaned_data["last_name"],
},
"type": "customer",
}
customer.additional_info = {}
customer.additional_info["customer_info"] = customer_dict
customer.additional_info["type"] = "customer"
customer.save()
messages.success(request, _("Customer created successfully."))
@ -1328,7 +1310,16 @@ def CustomerUpdateView(request, pk):
instance.email = customer_dict["email"]
customer_dict["pk"] = str(instance.pk)
instance.additional_info["customer_info"] = customer_dict
instance.additional_info.update({"customer_info": customer_dict})
try:
user = User.objects.filter(pk=int(instance.additional_info['user_info']['id'])).first()
if user:
user.username = customer_dict["email"]
user.email = customer_dict["email"]
user.save()
except Exception as e:
raise Exception(e)
instance.save()
messages.success(request, _("Customer updated successfully."))
return redirect("customer_list")
@ -1653,7 +1644,9 @@ class OrganizationDetailView(DetailView):
def OrganizationCreateView(request):
if request.method == "POST":
form = forms.OrganizationForm(request.POST)
# upload logo
if CustomerModel.objects.filter(email=request.POST["email"]).exists():
messages.error(request, _("An organization with this email already exists."))
return redirect("organization_create")
organization_dict = {
x: request.POST[x] for x in request.POST if x != "csrfmiddlewaretoken"
@ -1686,13 +1679,13 @@ def OrganizationCreateView(request):
def OrganizationUpdateView(request,pk):
organization = get_object_or_404(CustomerModel, pk=pk)
organization = get_object_or_404(CustomerModel, pk=pk)
if request.method == "POST":
form = forms.OrganizationForm(request.POST)
organization_dict = {
x: request.POST[x] for x in request.POST if x != "csrfmiddlewaretoken"
}
}
dealer = get_user_type(request)
instance = dealer.entity.get_customers().get(
@ -1702,8 +1695,15 @@ def OrganizationUpdateView(request,pk):
instance.address_1 = organization_dict["address"]
instance.phone = organization_dict["phone_number"]
instance.email = organization_dict["email"]
organization_dict["logo"] = organization.additional_info["organization_info"]["logo"]
image = request.FILES.get("logo")
if image:
file_name = default_storage.save("images/{}".format(image.name), image)
file_url = default_storage.url(file_name)
organization_dict["logo"] = file_url
else:
organization_dict["logo"] = organization.additional_info["organization_info"]["logo"]
organization_dict["pk"] = str(instance.pk)
instance.additional_info["organization_info"] = organization_dict
instance.additional_info["type"] = "organization"
@ -1711,19 +1711,23 @@ def OrganizationUpdateView(request,pk):
messages.success(request, _("Organization created successfully."))
return redirect("organization_list")
else:
form = forms.OrganizationForm(
form = forms.OrganizationForm(
initial=organization.additional_info["organization_info"] or {}
)
form.fields.pop("logo", None)
# form.fields.pop("logo", None)
return render(request, "organizations/organization_form.html", {"form": form})
class OrganizationDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteView):
model = models.Organization
template_name = "organizations/organization_confirm_delete.html"
success_url = reverse_lazy("organization_list")
success_message = "Organization deleted successfully."
# class OrganizationDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteView):
# model = models.Organization
# template_name = "organizations/organization_confirm_delete.html"
# success_url = reverse_lazy("organization_list")
# success_message = "Organization deleted successfully."
def OrganizationDeleteView(request, pk):
organization = get_object_or_404(CustomerModel, pk=pk)
organization.delete()
messages.success(request, _("Organization deleted successfully."))
return redirect("organization_list")
class RepresentativeListView(LoginRequiredMixin, ListView):
model = models.Representative
@ -2119,7 +2123,7 @@ def create_estimate(request,pk=None):
form.initial['customer'] = customer
car_list = models.Car.objects.filter(dealer=dealer,colors__isnull=False,finances__isnull=False,status="available").annotate(color=F('colors__exterior__rgb'),color_name=F('colors__exterior__name')).values_list(
'id_car_make__name', 'id_car_model__name','id_car_serie__name','id_car_trim__name','color','color_name','hash').distinct()
'id_car_make__name', 'id_car_model__name','id_car_serie__name','id_car_trim__name','color','color_name','hash').annotate(hash_count=Count('hash')).distinct()
context = {
"form": form,
"items": [
@ -2130,7 +2134,8 @@ def create_estimate(request,pk=None):
'trim':x[3],
'color':x[4],
'color_name':x[5],
'hash': x[6]
'hash': x[6],
'hash_count': x[7]
}
for x in car_list
],
@ -2174,9 +2179,8 @@ def create_sale_order(request, pk):
item.item_model.additional_info['car_info']['status'] = 'sold'
item.item_model.save()
except KeyError:
pass
models.Car.objects.get(vin=item.item_model.name).mark_as_sold(user=request.user)
pass
models.Car.objects.get(vin=item.item_model.name).mark_as_sold(request)
messages.success(request, "Sale Order created successfully")
return redirect("estimate_detail", pk=pk)
@ -2586,11 +2590,14 @@ class LeadListView(ListView):
def get_queryset(self):
dealer = get_user_type(self.request)
staff = getattr(self.request.user, "staff", None)
if staff:
return models.Lead.objects.filter(dealer=dealer, staff=staff)
return models.Lead.objects.filter(dealer=dealer)
print(dealer.user)
staffmember = getattr(self.request.user, "staffmember", None)
if staffmember:
qs = models.Lead.objects.filter(dealer=dealer)
if staffmember.staff.staff_type == models.StaffTypes.MANAGER or self.request.user == dealer.user:
return qs
return qs.filter(staff=staffmember.staff)
return models.Lead.objects.none()
class LeadDetailView(DetailView):
@ -2615,6 +2622,7 @@ class LeadDetailView(DetailView):
context["status_history"] = models.LeadStatusHistory.objects.filter(
lead=self.object
)
context["transfer_form"] = forms.LeadTransferForm()
return context
@ -2638,10 +2646,11 @@ def lead_create(request):
instance = form.save(commit=False)
dealer = get_user_type(request)
instance.dealer = dealer
staff = None
if hasattr(request.user, "staffmember"):
staff = request.user.staffmember.staff
instance.staff = staff
# staff = None
# if hasattr(request.user, "staffmember"):
# staff = request.user.staffmember.staff
# instance.staff = staff
instance.staff = form.cleaned_data.get("staff")
# creating customer in ledger
customer = dealer.entity.get_customers().filter(email=instance.email).first()
@ -2655,6 +2664,8 @@ def lead_create(request):
"sales_tax_rate": 0.15,
}
)
customer.additional_info.update({'stage': 'lead'})
customer.save()
instance.customer = customer
instance.save()
messages.success(request, "Lead created successfully!")
@ -2769,24 +2780,27 @@ def schedule_lead(request, pk):
instance = form.save(commit=False)
instance.lead = lead
instance.scheduled_by = request.user
instance.save()
# Create AppointmentRequest
service = Service.objects.filter(name=instance.scheduled_type).first()
if not service:
messages.error(request, "Service not found!")
return redirect("lead_list")
# staff_member = StaffMember.objects.filter(user=request.user).first()
staff_member = request.user.staffmember
appointment_request = AppointmentRequest.objects.create(
date=instance.scheduled_at.date(),
start_time=instance.scheduled_at.time(),
end_time=(instance.scheduled_at + instance.duration).time(),
service=service,
staff_member=staff_member,
)
client = get_object_or_404(User, email=lead.email)
try:
appointment_request = AppointmentRequest.objects.create(
date=instance.scheduled_at.date(),
start_time=instance.scheduled_at.time(),
end_time=(instance.scheduled_at + instance.duration).time(),
service=service,
staff_member=staff_member,
)
except ValidationError as e:
messages.error(request, str(e))
return redirect("schedule_lead", pk=lead.pk)
client = get_object_or_404(User, email=lead.email)
# Create Appointment
Appointment.objects.create(
client=client,
@ -2795,6 +2809,7 @@ def schedule_lead(request, pk):
address=lead.address,
)
instance.save()
messages.success(request, "Lead scheduled and appointment created successfully!")
return redirect("lead_list")
else:
@ -2805,20 +2820,33 @@ def schedule_lead(request, pk):
return render(request, "crm/leads/schedule_lead.html", {"lead": lead, "form": form})
@login_required
def lead_transfer(request,pk):
lead = get_object_or_404(models.Lead, pk=pk)
if request.method == "POST":
form = forms.LeadTransferForm(request.POST)
if form.is_valid():
lead.staff = form.cleaned_data["transfer_to"]
lead.save()
messages.success(request, "Lead transferred successfully!")
else:
messages.error(request, f"Invalid form data: {str(form.errors)}")
return redirect("lead_list")
@login_required
def send_lead_email(request, pk,email_pk=None):
lead = get_object_or_404(models.Lead, pk=pk)
status = request.GET.get("status")
dealer = get_user_type(request)
if status == 'draft':
models.Email.objects.create(content_object=lead, created_by=request.user,from_email="manager@tenhal.com", to_email=request.GET.get("to"), subject=request.GET.get("subject"), message=request.GET.get("message"),status=models.EmailStatus.DRAFT)
models.Activity.objects.create(content_object=lead, notes="Email Draft",created_by=request.user,activity_type=models.ActionChoices.EMAIL)
models.Activity.objects.create(dealer=dealer,content_object=lead, notes="Email Draft",created_by=request.user,activity_type=models.ActionChoices.EMAIL)
messages.success(request, _("Email Draft successfully!"))
response = HttpResponse(redirect("lead_detail", pk=lead.pk))
response['HX-Redirect'] = reverse('lead_detail', args=[lead.pk])
return response
dealer = get_user_type(request)
lead.status = models.Status.IN_PROGRESS
lead.status = models.Status.CONTACTED
lead.save()
# lead.convert_to_customer(dealer.entity)
@ -3140,7 +3168,7 @@ class BillDetailView(LoginRequiredMixin, DetailView):
kwargs["transactions"] = transactions
kwargs["grand_total"] = grand_total
print(dir(txs[0]))
return super().get_context_data(**kwargs)

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 452 KiB

BIN
static/images/sold.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 654 KiB

View File

@ -105,8 +105,7 @@
<script src="{% static 'js/projectmanagement-dashboard.js' %}"></script>
<script src="{% static 'vendors/mapbox-gl/mapbox-gl.js' %}"></script>
<script src="https://unpkg.com/@turf/turf@6/turf.min.js"></script>
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
<script src="{% static 'vendors/swiper/swiper-bundle.min.js' %}"></script>
<script src="{% static 'vendors/flatpickr/flatpickr.min.js' %}"></script>

View File

@ -1,6 +1,15 @@
{% extends 'base.html' %}
{% load i18n static %}
{% block content %}
{% load crispy_forms_tags %}
{% block customCSS %}
<style>
.main-tab li:last-child {
margin-left: auto;
}
</style>
{% endblock customCSS %}
<div class="row g-3">
<div class="col-12">
@ -32,7 +41,7 @@
<div class="col-6 col-sm-auto flex-1">
<h3 class="fw-bolder mb-2">{{ lead.first_name }} {{ lead.last_name }}</h3>
{% if lead.staff %}
<p class="fs-8 mb-0 white-space-nowrap fw-bold">{{ _("Assigned to")}}: <span class="fw-light">{{ lead.staff.user.get_full_name }}</span></p>
<p class="fs-8 mb-0 white-space-nowrap fw-bold">{{ _("Assigned to")}}: <span class="fw-light">{{ lead.staff }}</span></p>
{% else %}
<p class="mb-0 fw-bold">{{ _("Not Assigned")}}</p>
{% endif %}
@ -123,12 +132,35 @@
</div>
</div>
<div class="col-md-7 col-lg-7 col-xl-8">
<ul class="nav nav-underline fs-9 deal-details scrollbar flex-nowrap w-100 pb-1 mb-6" id="myTab" role="tablist" style="overflow-y: hidden;">
<ul class="nav main-tab nav-underline fs-9 deal-details scrollbar flex-nowrap w-100 pb-1 mb-6 justify-content-end" id="myTab" role="tablist" style="overflow-y: hidden;">
<li class="nav-item text-nowrap me-2" role="presentation"><a class="nav-link active" id="activity-tab" data-bs-toggle="tab" href="#tab-activity" role="tab" aria-controls="tab-activity" aria-selected="false" tabindex="-1"> <span class="fa-solid fa-chart-line me-2 tab-icon-color fs-8"></span>{{ _("Activity") }}</a></li>
<li class="nav-item text-nowrap me-2" role="presentation"><a class="nav-link" id="notes-tab" data-bs-toggle="tab" href="#tab-notes" role="tab" aria-controls="tab-notes" aria-selected="false" tabindex="-1"> <span class="fa-solid fa-clipboard me-2 tab-icon-color fs-8"></span>{{ _("Notes") }}</a></li>
<li class="nav-item text-nowrap me-2" role="presentation"><a class="nav-link" id="emails-tab" data-bs-toggle="tab" href="#tab-emails" role="tab" aria-controls="tab-emails" aria-selected="true"> <span class="fa-solid fa-envelope me-2 tab-icon-color fs-8"></span>{{ _("Emails") }}</a></li>
<li class="nav-item text-nowrap ml-auto" role="presentation">
<button class="btn btn-primary" type="button" data-bs-toggle="modal" data-bs-target="#exampleModal">Reassign Lead</button>
<div class="modal fade" id="exampleModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<form class="modal-content" action="{% url 'lead_transfer' lead.pk %}" method="post">
{% csrf_token %}
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Reassign Lead To Another Employee</h5>
<button class="btn btn-close p-1" type="button" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
{{transfer_form|crispy}}
</div>
<div class="modal-footer">
<button class="btn btn-primary" type="submit">Save</button>
<button class="btn btn-outline-primary" type="button" data-bs-dismiss="modal">Cancel</button>
</div>
</form>
</div>
</div>
</div>
</li>
</ul>
<div class="tab-content" id="myTabContent">
<div class="tab-content" id="myTabContent">
<div class="tab-pane fade active show" id="tab-activity" role="tabpanel" aria-labelledby="activity-tab">
<div class="mb-1">
<h3 class="mb-4" id="scrollspyTask">{{ _("Activities") }} <span class="fw-light fs-7">({{ activities.count}})</span></h3>
@ -166,7 +198,7 @@
</div>
<p class="text-body-quaternary fs-9 mb-0 text-nowrap timeline-time"><span class="fa-regular fa-clock me-1"></span>{{ activity.created }}</p>
</div>
<h6 class="fs-10 fw-normal mb-3">{{ _("by") }} <a class="fw-semibold" href="#!">{{ activity.created_by.staff.user.get_full_name }}</a></h6>
<h6 class="fs-10 fw-normal mb-3">{{ _("by") }} <a class="fw-semibold" href="#!">{{ activity.created_by }}</a></h6>
<p class="fs-9 text-body-secondary w-sm-60 mb-5">{{ activity.notes }}</p>
</div>
</div>
@ -238,7 +270,6 @@
<ul class="nav nav-underline fs-9 flex-nowrap mb-1" id="emailTab" role="tablist">
<li class="nav-item me-3"><a class="nav-link text-nowrap border-0 active" id="mail-tab" data-bs-toggle="tab" href="#tab-mail" aria-controls="mail-tab" role="tab" aria-selected="true">Mails ({{emails.sent.count}})<span class="text-body-tertiary fw-normal"></span></a></li>
<li class="nav-item me-3"><a class="nav-link text-nowrap border-0" id="drafts-tab" data-bs-toggle="tab" href="#tab-drafts" aria-controls="drafts-tab" role="tab" aria-selected="true">Drafts ({{emails.draft.count}})<span class="text-body-tertiary fw-normal"></span></a></li>
<li class="nav-item me-3"><a class="nav-link text-nowrap border-0" id="schedule-tab" data-bs-toggle="tab" href="#tab-schedule" aria-controls="schedule-tab" role="tab" aria-selected="true">Scheduled (17)</a></li>
</ul>
</div>
<div class="tab-content" id="profileTabContent">

View File

@ -123,6 +123,8 @@
<span class="badge badge-phoenix badge-phoenix-info"><span class="badge-label">{{_("In Progress")}}</span><span class="fa fa-wrench ms-1"></span></span>
{% elif lead.status == "qualified" %}
<span class="badge badge-phoenix badge-phoenix-success"><span class="badge-label">{{_("Qualified")}}</span><span class="fa fa-check ms-1"></span></span>
{% elif lead.status == "contacted" %}
<span class="badge badge-phoenix badge-phoenix-info"><span class="badge-label">{{_("Contacted")}}</span><span class="fa fa-times ms-1"></span></span>
{% elif lead.status == "canceled" %}
<span class="badge badge-phoenix badge-phoenix-danger"><span class="badge-label">{{_("Canceled")}}</span><span class="fa fa-times ms-1"></span></span>
{% endif %}
@ -136,6 +138,7 @@
<td class="align-middle white-space-nowrap fw-semibold"><a class="text-body-highlight" href="tel:{{ lead.phone_number }}">{{ lead.phone_number }}</a></td>
<td class="align-middle white-space-nowrap fw-semibold">
{% if lead.get_latest_schedule %}
{{lead.get_latest_schedule.scheduled_type}} at <br>
<a href="{% url 'appointment:get_user_appointments' %}">
{% if lead.get_latest_schedule.scheduled_type == "Call" %}
<span class="badge badge-phoenix badge-phoenix-primary text-primary {% if lead.get_latest_schedule.schedule_past_date %}badge-phoenix-danger text-danger{% endif %} fw-semibold"><span class="text-primary {% if lead.get_latest_schedule.schedule_past_date %}text-danger{% endif %}" data-feather="phone"></span>
@ -175,29 +178,31 @@
<span class="badge badge-phoenix badge-phoenix-danger">{{ _("No") }}</span>
{% endif %}
</td>
<td class="align-middle white-space-nowrap text-end">
<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 'lead_update' lead.id %}" class="dropdown-item text-success-dark">{% trans "Edit" %}</a>
<a href="{% url 'send_lead_email' lead.id %}" class="dropdown-item text-success-dark">{% trans "Send Email" %}</a>
<a href="{% url 'schedule_lead' lead.id %}" class="dropdown-item text-success-dark">{% trans "Set Schedule" %}</a>
{% if not lead.opportunity %}
<a href="{% url 'lead_convert' lead.id %}" class="dropdown-item text-success-dark">{% trans "Convert To Opportunity" %}</a>
{% endif %}
<div class="dropdown-divider"></div>
<button class="dropdown-item text-danger" data-bs-toggle="modal" data-bs-target="#deleteModal">{% trans "Delete" %}</button>
<td class="align-middle white-space-nowrap text-end">
{% if user == lead.staff.user %}
<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 'lead_update' lead.id %}" class="dropdown-item text-success-dark">{% trans "Edit" %}</a>
<a href="{% url 'send_lead_email' lead.id %}" class="dropdown-item text-success-dark">{% trans "Send Email" %}</a>
<a href="{% url 'schedule_lead' lead.id %}" class="dropdown-item text-success-dark">{% trans "Schedule Event" %}</a>
{% if not lead.opportunity %}
<a href="{% url 'lead_convert' lead.id %}" class="dropdown-item text-success-dark">{% trans "Convert" %}</a>
{% endif %}
<div class="dropdown-divider"></div>
<button class="dropdown-item text-danger" data-bs-toggle="modal" data-bs-target="#deleteModal">{% trans "Delete" %}</button>
</div>
</div>
</div>
{% endif %}
</td>
</tr>
{% endfor %}

View File

@ -429,12 +429,8 @@
{% else %}
<span class="fa fa-user text-body-tertiary" style="width: 32px;"></span>
{% endif %}
</div>
{% if user.dealer %}
<h6 class="mt-2 text-body-emphasis">{{ user.dealer.get_local_name }}</h6>
{% else %}
<h6 class="mt-2 text-body-emphasis">{{ user.staff.get_local_name }}</h6>
{% endif %}
</div>
<h6 class="mt-2 text-body-emphasis">{{ user }}</h6>
</div>
</div>
<div class="overflow-auto scrollbar" style="height: 10rem;">

View File

@ -3,10 +3,16 @@
{% block title %}{{ _("Car Details") }}{% endblock %}
{% block customCSS %}
<style>
.transfer{
.disabled{
opacity: 0.5;
pointer-events: none;
}
img {
position: absolute;
top: 13%;
left: 90%;
transform: translate(-50%, -50%);
}
</style>
{% endblock customCSS %}
@ -42,10 +48,10 @@
{% endif %}
<!-- Main row -->
<div class="row-fluid">
<div class="row-fluid {% if car.status == 'sold' %}disabled{% endif %}">
<div class="row g-3 justify-content-between">
<div class="col-lg-12 col-xl-6">
<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 %}disabled{% endif %}">
<p class="card-header rounded-top fw-bold">{% trans 'Car Details' %}</p>
<div class="card-body">
<div class="table-responsive scrollbar mb-3">
@ -389,6 +395,9 @@
{% endif %}
</div>
</div>
{% if car.status == 'sold' %}
<img src="{% static 'images/sold.png' %}" width="200" height="100" alt="">
{% endif %}
</div>
<!-- Custom Card Modal -->
@ -450,8 +459,7 @@
<i class="fas fa-check"></i> {% trans 'Yes' %}
</button>
</div>
</div>
</div>
</form>
</div>
</div>
@ -470,7 +478,6 @@
</div>
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", function () {
@ -587,6 +594,15 @@
});
});
});
if('{{car.status}}' == "sold"){
document.querySelector(".row-fluid").querySelectorAll("button").forEach((button) => {
button.classList.add("d-none");
});
document.querySelector(".row-fluid").querySelectorAll("a").forEach((button) => {
button.classList.add("d-none");
});
}
</script>
{% endblock %}

View File

@ -110,6 +110,7 @@
<th class="align-middle" scope="col">{{ _("Model") }}</th>
<th class="align-middle" scope="col">{{ _("Year") }}</th>
<th class="align-middle" scope="col">{{ _("Trim") }}</th>
<th class="align-middle" scope="col">{{ _("Color") }}</th>
<th class="align-middle" scope="col">{{ _("Age") }}</th>
<th class="align-middle" scope="col">{{ _("Status") }}</th>
<th class="align-middle" scope="col"></th>
@ -133,6 +134,16 @@
<td class="align-middle white-space-nowrap">
<p class="fw-bold text-body mb-0">{{car.id_car_trim }}</p>
</td>
<td class="align-middle white-space-nowrap">
<div class="d-flex flex-row align-items-center">
<div class="d-flex flex-column align-items-center">
<span class="color-div" style="background: linear-gradient(90deg, rgba({{ car.colors.exterior.rgb }},1) 10%, rgba({{ car.colors.exterior.rgb }},0.10) 100%);" title="{{ car.colors.exterior.get_local_name }}"></span><span>{{ car.colors.exterior.get_local_name }}</span>
</div>
<div class="d-flex flex-column align-items-center">
<span class="color-div" style="background: linear-gradient(90deg, rgba({{ car.colors.interior.rgb }},1) 10%, rgba({{ car.colors.interior.rgb }},0.10) 100%);" title="{{ car.colors.interior.get_local_name }}"></span><span>{{ car.colors.interior.get_local_name }}</span>
</div>
</div>
</td>
<td class="align-middle white-space-nowrap">
<p class="fw-bold text-body mb-0">{{car.receiving_date|timesince}}</p>

View File

@ -111,12 +111,13 @@
<div><a class="fs-8 fw-bold" href="{% url 'organization_detail' org.pk %}">{{ org.customer_name }}</a>
<div class="d-flex align-items-center">
<p class="mb-0 text-body-highlight fw-semibold fs-9 me-2"></p><span class="badge badge-phoenix badge-phoenix-primary">{{ org.customer_name}}</span>
<img src="{{ org.additional_info.organization_info.logo }}" width="80" height="80" alt="">
</div>
</div>
</div>
</td>
<td class="email align-middle white-space-nowrap fw-semibold ps-4 border-end border-translucent">{{ org.additional_info.info.crn }}</td>
<td class="phone align-middle white-space-nowrap fw-semibold ps-4 border-end border-translucent">{{ org.additional_info.info.vrn }}</td>
<td class="email align-middle white-space-nowrap fw-semibold ps-4 border-end border-translucent">{{ org.additional_info.organization_info.crn }}</td>
<td class="phone align-middle white-space-nowrap fw-semibold ps-4 border-end border-translucent">{{ org.additional_info.organization_info.vrn }}</td>
<td class="phone align-middle white-space-nowrap ps-4 border-end border-translucent fw-semibold text-body-highlight"><a class="text-body-highlight" href="tel:{{ org.phone }}">{{ org.phone }}</a></td>
<td class="company align-middle white-space-nowrap text-body-tertiary text-opacity-85 ps-4 border-end border-translucent fw-semibold text-body-highlight">
{{ org.address_1 }}</td>

View File

@ -58,7 +58,22 @@
<section class="pt-5 pb-9 bg-body-emphasis dark__bg-gray-1200 border-top">
<div class="row-small mt-3">
<div class="d-flex justify-content-between align-items-end mb-4 mx-3">
<h2 class="mb-0"><i class="fa-regular fa-file-lines"></i> {% trans 'Quotation' %}</h2>
<div class="d-flex flex-row align-items-center gap-2">
<h2 class="mb-0"><i class="fa-regular fa-file-lines"></i> {% trans 'Quotation' %}</h2>
<div class="fs-9 text-body-secondary fw-semibold mb-0">
{% if estimate.status == 'draft' %}
<span class="badge text-bg-warning">{% trans "Draft" %}</span>
{% elif estimate.status == 'in_review' %}
<span class="badge text-bg-info">{% trans "In Review" %}</span>
{% elif estimate.status == 'approved' %}
<span class="badge text-bg-success">{% trans "Approved" %}</span>
{% elif estimate.status == 'completed' %}
<span class="badge text-bg-success">{% trans "completed" %}</span>
{% elif estimate.status == 'canceled' %}
<span class="badge text-bg-danger">{% trans "canceled" %}</span>
{% endif %}
</div>
</div>
<div class="d-flex align-items-center gap-2">
{% if estimate.invoicemodel_set.first %}
<a href="{% url 'invoice_detail' estimate.invoicemodel_set.first.pk %}" class="btn btn-primary btn-lg me-1 mb-1" type="button"><i class="fa-solid fa-receipt"></i> View Invoice</a>
@ -190,7 +205,7 @@
<td class="align-middle ps-4 fw-semibold text-body-highlight" colspan="7">{% trans "Additional Services" %}</td>
<td class="align-middle text-start fw-semibold">
{% for service in data.additionals %}
<small><span class="fw-semibold">+ {{service.name}} - {{service.total}}</span></small><br>
<small><span class="fw-semibold">+ {{service.name}} - {{service.price_}}</span></small><br>
{% endfor %}
</td>
</tr>

View File

@ -18,7 +18,7 @@
<div class="mb-2 col-sm-4">
<select class="form-control item" name="item[]" required>
{% for item in items %}
<option style="background-color: rgb({{ item.color }});" value="{{ item.hash }}">{{ item.make }} {{item.model}} {{item.serie}} {{item.trim}} {{item.color_name}}</option>
<option style="background-color: rgb({{ item.color }});" value="{{ item.hash }}">{{ item.make }} {{item.model}} {{item.serie}} {{item.trim}} {{item.color_name}} ({{item.hash_count}})</option>
{% empty %}
<option disabled>{% trans "No Cars Found" %}</option>
{% endfor %}

View File

@ -43,16 +43,12 @@
</td>
<td class="align-middle product white-space-nowrap">{{ estimate.get_status_action_date }}</td>
<td class="align-middle product white-space-nowrap">{{ estimate.created }}</td>
<td class="text-center">
<td class="align-middle product white-space-nowrap">
<a href="{% url 'estimate_detail' estimate.pk %}"
class="btn btn-sm btn-phoenix-success">
<i class="fa-regular fa-eye"></i>
{% trans "view"|capfirst %}
</a>
<a href="{% url 'estimate_detail' estimate.pk %}"
class="btn btn-sm btn-phoenix-success">
{% trans "pdf"|capfirst %}
</a>
</a>
</td>
</tr>
{% empty %}

View File

@ -2,7 +2,14 @@
{% load i18n %}
{% block title %}{{ _("View Estimate") }}{% endblock title %}
{% block customCSS %}
<style>
.disabled{
opacity: 0.5;
pointer-events: none;
}
</style>
{% endblock customCSS %}
{% block content %}
<div class="modal fade" id="confirmModal" tabindex="-1" aria-labelledby="confirmModalLabel" aria-hidden="true">
<div class="modal-dialog modal-sm">
@ -55,9 +62,24 @@
<!-- ============================================-->
<!-- <section> begin ============================-->
<section class="pt-5 pb-9 bg-body-emphasis dark__bg-gray-1200 border-top">
<div class="row-small mt-3 mx-3">
<div class="row-small mt-3 mx-3">
<div class="d-flex justify-content-between align-items-end mb-4 mx-3">
<h2 class="mb-0"><i class="fa-solid fa-receipt"></i> {% trans 'Invoice' %}</h2>
<div class="d-flex flex-row align-items-center gap-2">
<h2 class="mb-0"><i class="fa-solid fa-receipt"></i> {% trans 'Invoice' %}</h2>
<div class="fs-9 text-body-secondary fw-semibold mt-2">
{% if invoice.invoice_status == 'draft' %}
<span class="badge text-bg-warning">{% trans "Draft" %}</span>
{% elif invoice.invoice_status == 'in_review' %}
<span class="badge text-bg-info">{% trans "In Review" %}</span>
{% elif invoice.invoice_status == 'approved' %}
<span class="badge text-bg-info">{% trans "Approved" %}</span>
{% elif invoice.invoice_status == 'declined' %}
<span class="badge text-bg-danger">{% trans "Declined" %}</span>
{% elif invoice.invoice_status == 'paid' %}
<span class="badge text-bg-success">{% trans "Paid" %}</span>
{% endif %}
</div>
</div>
<div class="d-flex align-items-center gap-2">
{% if invoice.invoice_status == 'in_review' %}
<button id="accept_invoice" class="btn btn-phoenix-secondary" data-bs-toggle="modal" data-bs-target="#confirmModal"><span class="d-none d-sm-inline-block"><i class="fa-solid fa-check-double"></i> {% trans 'Accept' %}</span></button>
@ -66,14 +88,14 @@
<a href="{% url 'payment_create' invoice.pk %}" class="btn btn-phoenix-success"><span class="d-none d-sm-inline-block"><i class="fa-solid fa-money-bill"></i> {% trans 'Record Payment' %}</span></a>
{% endif %}
{% if not invoice.is_paid %}
<button id="mark_invoice_as_paid" class="btn btn-phoenix-secondary" data-bs-toggle="modal" data-bs-target="#mark_as_paid_Modal"><span class="d-none d-sm-inline-block"><i class="fa-solid fa-money-check-dollar"></i> {% trans 'Mark as Paid' %}</span></button>
<button {% if invoice.is_review %}disabled{% endif %} id="mark_invoice_as_paid" class="btn btn-phoenix-secondary" data-bs-toggle="modal" data-bs-target="#mark_as_paid_Modal"><span class="d-none d-sm-inline-block"><i class="fa-solid fa-money-check-dollar"></i> {% trans 'Mark as Paid' %}</span></button>
{% endif %}
<a href="{% url 'invoice_preview' invoice.pk %}" class="btn btn-phoenix-primary"><span class="d-none d-sm-inline-block"><i class="fa-regular fa-eye"></i> {% trans 'Preview' %}</span></a>
</div>
</div>
<!-- ============================================-->
<div class="card mb-5">
<div class="card mb-5 {% if invoice.is_review %}disabled{% endif %}">
<div class="card-body">
<div class="row g-4 g-xl-1 g-xxl-3 justify-content-between">
<div class="col-sm-auto">
@ -145,7 +167,7 @@
</div>
</div>
<!-- <section> begin ============================-->
<div class="bg-body dark__bg-gray-1100 p-4 mb-4 rounded-2 text-body-tertiary">
<div class="bg-body dark__bg-gray-1100 p-4 mb-4 rounded-2 text-body-tertiary {% if invoice.is_review %}disabled{% endif %}">
<div class="row g-4">
<div class="col-12 col-lg-3">
<div class="row g-4 g-lg-2">
@ -205,7 +227,7 @@
</div>
</div>
</div>
<div class="px-0">
<div class="px-0 {% if invoice.is_review %}disabled{% endif %}">
<div class="table-responsive scrollbar">
<table id="invoice-table" class="table fs-9 text-body mb-0">
<thead class="bg-body-secondary">
@ -249,7 +271,7 @@
<td class="align-middle ps-4 fw-semibold text-body-highlight" colspan="7">{% trans "Additional Services" %}</td>
<td class="align-middle text-start fw-bold">
{% for service in data.additionals %}
<small><span class="fw-bold">+ {{service.name}} - {{service.price}}</span></small><br>
<small><span class="fw-bold">+ {{service.name}} - {{service.price_}}</span></small><br>
{% endfor %}
</td>
</tr>