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: class Meta:
model = DealerSettings model = DealerSettings
fields = "__all__" 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 django.core.exceptions import ValidationError
from phonenumber_field.modelfields import PhoneNumberField from phonenumber_field.modelfields import PhoneNumberField
from django.utils.timezone import now from django.utils.timezone import now
from inventory.utils import get_user_type
from .mixins import LocalizedNameMixin from .mixins import LocalizedNameMixin
from django_ledger.models import EntityModel, ItemModel,EstimateModel,InvoiceModel,AccountModel from django_ledger.models import EntityModel, ItemModel,EstimateModel,InvoiceModel,AccountModel
from django.contrib.contenttypes.fields import GenericForeignKey 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')) 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() 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.cancel_reservation()
self.status = CarStatusChoices.SOLD self.status = CarStatusChoices.SOLD
self.save() 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): def cancel_reservation(self):
if self.reservations.exists(): if self.reservations.exists():
@ -997,6 +1000,8 @@ class Status(models.TextChoices):
PENDING = "pending", _("Pending") PENDING = "pending", _("Pending")
IN_PROGRESS = "in_progress", _("In Progress") IN_PROGRESS = "in_progress", _("In Progress")
QUALIFIED = "qualified", _("Qualified") QUALIFIED = "qualified", _("Qualified")
CONTACTED = "contacted", _("Contacted")
CONVERTED = "converted", _("Converted")
CANCELED = "canceled", _("Canceled") CANCELED = "canceled", _("Canceled")
@ -1245,6 +1250,7 @@ class Lead(models.Model):
customer.additional_info = {} customer.additional_info = {}
customer.additional_info.update({"info":self.to_dict()}) customer.additional_info.update({"info":self.to_dict()})
customer.additional_info.update({"stage":"qualified"})
self.customer = customer self.customer = customer
self.status = Status.QUALIFIED self.status = Status.QUALIFIED
customer.save() customer.save()

View File

@ -670,16 +670,12 @@ def create_customer_user(sender, instance, created, **kwargs):
username=instance.email, username=instance.email,
email=instance.email, email=instance.email,
) )
instance.additional_info["customer_info"] = to_dict(instance) instance.additional_info.update({"user_info": to_dict(user)})
# 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 ""
user.set_unusable_password() user.set_unusable_password()
user.save() user.save()
instance.user = user instance.user = user
instance.save() instance.save()
# Create Item # Create Item
@receiver(post_save, sender=models.Car) @receiver(post_save, sender=models.Car)
@ -894,5 +890,5 @@ def check_users_quota(sender, instance, **kwargs):
if allowed_users is None: if allowed_users is None:
raise ValidationError(_("The user quota for staff members is not defined. Please contact support.")) raise ValidationError(_("The user quota for staff members is not defined. Please contact support."))
current_staff_count = instance.dealer.staff.count() 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.")) 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, views.schedule_lead,
name="schedule_lead", name="schedule_lead",
), ),
path(
"crm/leads/<int:pk>/transfer/",
views.lead_transfer,
name="lead_transfer",
),
path( path(
"crm/opportunities/<int:pk>/add_note/", "crm/opportunities/<int:pk>/add_note/",
@ -364,7 +369,7 @@ urlpatterns = [
), ),
path( path(
"organizations/<uuid:pk>/delete/", "organizations/<uuid:pk>/delete/",
views.OrganizationDeleteView.as_view(), views.OrganizationDeleteView,
name="organization_delete", name="organization_delete",
), ),
# Representative URLs # Representative URLs

View File

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

View File

@ -1,6 +1,15 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load i18n static %} {% load i18n static %}
{% block content %} {% 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="row g-3">
<div class="col-12"> <div class="col-12">
@ -32,7 +41,7 @@
<div class="col-6 col-sm-auto flex-1"> <div class="col-6 col-sm-auto flex-1">
<h3 class="fw-bolder mb-2">{{ lead.first_name }} {{ lead.last_name }}</h3> <h3 class="fw-bolder mb-2">{{ lead.first_name }} {{ lead.last_name }}</h3>
{% if lead.staff %} {% 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 %} {% else %}
<p class="mb-0 fw-bold">{{ _("Not Assigned")}}</p> <p class="mb-0 fw-bold">{{ _("Not Assigned")}}</p>
{% endif %} {% endif %}
@ -123,12 +132,35 @@
</div> </div>
</div> </div>
<div class="col-md-7 col-lg-7 col-xl-8"> <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 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="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 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> </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="tab-pane fade active show" id="tab-activity" role="tabpanel" aria-labelledby="activity-tab">
<div class="mb-1"> <div class="mb-1">
<h3 class="mb-4" id="scrollspyTask">{{ _("Activities") }} <span class="fw-light fs-7">({{ activities.count}})</span></h3> <h3 class="mb-4" id="scrollspyTask">{{ _("Activities") }} <span class="fw-light fs-7">({{ activities.count}})</span></h3>
@ -166,7 +198,7 @@
</div> </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> <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> </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> <p class="fs-9 text-body-secondary w-sm-60 mb-5">{{ activity.notes }}</p>
</div> </div>
</div> </div>
@ -238,7 +270,6 @@
<ul class="nav nav-underline fs-9 flex-nowrap mb-1" id="emailTab" role="tablist"> <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 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="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> </ul>
</div> </div>
<div class="tab-content" id="profileTabContent"> <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> <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" %} {% 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> <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" %} {% 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> <span class="badge badge-phoenix badge-phoenix-danger"><span class="badge-label">{{_("Canceled")}}</span><span class="fa fa-times ms-1"></span></span>
{% endif %} {% 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"><a class="text-body-highlight" href="tel:{{ lead.phone_number }}">{{ lead.phone_number }}</a></td>
<td class="align-middle white-space-nowrap fw-semibold"> <td class="align-middle white-space-nowrap fw-semibold">
{% if lead.get_latest_schedule %} {% if lead.get_latest_schedule %}
{{lead.get_latest_schedule.scheduled_type}} at <br>
<a href="{% url 'appointment:get_user_appointments' %}"> <a href="{% url 'appointment:get_user_appointments' %}">
{% if lead.get_latest_schedule.scheduled_type == "Call" %} {% 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> <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> <span class="badge badge-phoenix badge-phoenix-danger">{{ _("No") }}</span>
{% endif %} {% endif %}
</td> </td>
<td class="align-middle white-space-nowrap text-end"> <td class="align-middle white-space-nowrap text-end">
<div class="btn-reveal-trigger position-static"> {% if user == lead.staff.user %}
<button <div class="btn-reveal-trigger position-static">
class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10" <button
type="button" class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10"
data-bs-toggle="dropdown" type="button"
data-boundary="window" data-bs-toggle="dropdown"
aria-haspopup="true" data-boundary="window"
aria-expanded="false" aria-haspopup="true"
data-bs-reference="parent"> aria-expanded="false"
<span class="fas fa-ellipsis-h fs-10"></span> data-bs-reference="parent">
</button> <span class="fas fa-ellipsis-h fs-10"></span>
<div class="dropdown-menu dropdown-menu-end py-2"> </button>
<a href="{% url 'lead_update' lead.id %}" class="dropdown-item text-success-dark">{% trans "Edit" %}</a> <div class="dropdown-menu dropdown-menu-end py-2">
<a href="{% url 'send_lead_email' lead.id %}" class="dropdown-item text-success-dark">{% trans "Send Email" %}</a> <a href="{% url 'lead_update' lead.id %}" class="dropdown-item text-success-dark">{% trans "Edit" %}</a>
<a href="{% url 'schedule_lead' lead.id %}" class="dropdown-item text-success-dark">{% trans "Set Schedule" %}</a> <a href="{% url 'send_lead_email' lead.id %}" class="dropdown-item text-success-dark">{% trans "Send Email" %}</a>
{% if not lead.opportunity %} <a href="{% url 'schedule_lead' lead.id %}" class="dropdown-item text-success-dark">{% trans "Schedule Event" %}</a>
<a href="{% url 'lead_convert' lead.id %}" class="dropdown-item text-success-dark">{% trans "Convert To Opportunity" %}</a> {% if not lead.opportunity %}
{% endif %} <a href="{% url 'lead_convert' lead.id %}" class="dropdown-item text-success-dark">{% trans "Convert" %}</a>
<div class="dropdown-divider"></div> {% endif %}
<button class="dropdown-item text-danger" data-bs-toggle="modal" data-bs-target="#deleteModal">{% trans "Delete" %}</button> <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>
</div> {% endif %}
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}

View File

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

View File

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

View File

@ -110,6 +110,7 @@
<th class="align-middle" scope="col">{{ _("Model") }}</th> <th class="align-middle" scope="col">{{ _("Model") }}</th>
<th class="align-middle" scope="col">{{ _("Year") }}</th> <th class="align-middle" scope="col">{{ _("Year") }}</th>
<th class="align-middle" scope="col">{{ _("Trim") }}</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">{{ _("Age") }}</th>
<th class="align-middle" scope="col">{{ _("Status") }}</th> <th class="align-middle" scope="col">{{ _("Status") }}</th>
<th class="align-middle" scope="col"></th> <th class="align-middle" scope="col"></th>
@ -133,6 +134,16 @@
<td class="align-middle white-space-nowrap"> <td class="align-middle white-space-nowrap">
<p class="fw-bold text-body mb-0">{{car.id_car_trim }}</p> <p class="fw-bold text-body mb-0">{{car.id_car_trim }}</p>
</td> </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"> <td class="align-middle white-space-nowrap">
<p class="fw-bold text-body mb-0">{{car.receiving_date|timesince}}</p> <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><a class="fs-8 fw-bold" href="{% url 'organization_detail' org.pk %}">{{ org.customer_name }}</a>
<div class="d-flex align-items-center"> <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> <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> </div>
</div> </div>
</td> </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="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.info.vrn }}</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="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"> <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> {{ 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"> <section class="pt-5 pb-9 bg-body-emphasis dark__bg-gray-1200 border-top">
<div class="row-small mt-3"> <div class="row-small mt-3">
<div class="d-flex justify-content-between align-items-end mb-4 mx-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"> <div class="d-flex align-items-center gap-2">
{% if estimate.invoicemodel_set.first %} {% 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> <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 ps-4 fw-semibold text-body-highlight" colspan="7">{% trans "Additional Services" %}</td>
<td class="align-middle text-start fw-semibold"> <td class="align-middle text-start fw-semibold">
{% for service in data.additionals %} {% 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 %} {% endfor %}
</td> </td>
</tr> </tr>

View File

@ -18,7 +18,7 @@
<div class="mb-2 col-sm-4"> <div class="mb-2 col-sm-4">
<select class="form-control item" name="item[]" required> <select class="form-control item" name="item[]" required>
{% for item in items %} {% 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 %} {% empty %}
<option disabled>{% trans "No Cars Found" %}</option> <option disabled>{% trans "No Cars Found" %}</option>
{% endfor %} {% endfor %}

View File

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

View File

@ -2,7 +2,14 @@
{% load i18n %} {% load i18n %}
{% block title %}{{ _("View Estimate") }}{% endblock title %} {% block title %}{{ _("View Estimate") }}{% endblock title %}
{% block customCSS %}
<style>
.disabled{
opacity: 0.5;
pointer-events: none;
}
</style>
{% endblock customCSS %}
{% block content %} {% block content %}
<div class="modal fade" id="confirmModal" tabindex="-1" aria-labelledby="confirmModalLabel" aria-hidden="true"> <div class="modal fade" id="confirmModal" tabindex="-1" aria-labelledby="confirmModalLabel" aria-hidden="true">
<div class="modal-dialog modal-sm"> <div class="modal-dialog modal-sm">
@ -55,9 +62,24 @@
<!-- ============================================--> <!-- ============================================-->
<!-- <section> begin ============================--> <!-- <section> begin ============================-->
<section class="pt-5 pb-9 bg-body-emphasis dark__bg-gray-1200 border-top"> <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"> <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"> <div class="d-flex align-items-center gap-2">
{% if invoice.invoice_status == 'in_review' %} {% 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> <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> <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 %} {% endif %}
{% if not invoice.is_paid %} {% 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 %} {% 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> <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> </div>
<!-- ============================================--> <!-- ============================================-->
<div class="card mb-5"> <div class="card mb-5 {% if invoice.is_review %}disabled{% endif %}">
<div class="card-body"> <div class="card-body">
<div class="row g-4 g-xl-1 g-xxl-3 justify-content-between"> <div class="row g-4 g-xl-1 g-xxl-3 justify-content-between">
<div class="col-sm-auto"> <div class="col-sm-auto">
@ -145,7 +167,7 @@
</div> </div>
</div> </div>
<!-- <section> begin ============================--> <!-- <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="row g-4">
<div class="col-12 col-lg-3"> <div class="col-12 col-lg-3">
<div class="row g-4 g-lg-2"> <div class="row g-4 g-lg-2">
@ -205,7 +227,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="px-0"> <div class="px-0 {% if invoice.is_review %}disabled{% endif %}">
<div class="table-responsive scrollbar"> <div class="table-responsive scrollbar">
<table id="invoice-table" class="table fs-9 text-body mb-0"> <table id="invoice-table" class="table fs-9 text-body mb-0">
<thead class="bg-body-secondary"> <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 ps-4 fw-semibold text-body-highlight" colspan="7">{% trans "Additional Services" %}</td>
<td class="align-middle text-start fw-bold"> <td class="align-middle text-start fw-bold">
{% for service in data.additionals %} {% 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 %} {% endfor %}
</td> </td>
</tr> </tr>