update
This commit is contained in:
commit
3febd59d86
@ -5,4 +5,8 @@ class InventoryConfig(AppConfig):
|
||||
name = 'inventory'
|
||||
|
||||
def ready(self):
|
||||
import inventory.signals
|
||||
import inventory.signals
|
||||
from decimal import Decimal
|
||||
from inventory.models import VatRate
|
||||
VatRate.objects.get_or_create(rate=Decimal('0.15'), is_active=True)
|
||||
|
||||
|
||||
@ -17,6 +17,7 @@ from .models import (
|
||||
Dealer,
|
||||
# Branch,
|
||||
Vendor,
|
||||
Schedule,
|
||||
Customer,
|
||||
Car,
|
||||
CarTransfer,
|
||||
@ -38,7 +39,7 @@ from .models import (
|
||||
SaleOrder
|
||||
)
|
||||
from django_ledger.models import ItemModel, InvoiceModel, BillModel,VendorModel
|
||||
from django.forms import ModelMultipleChoiceField, ValidationError, DateInput
|
||||
from django.forms import ModelMultipleChoiceField, ValidationError, DateInput,DateTimeInput
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
import django_tables2 as tables
|
||||
from django.forms import formset_factory
|
||||
@ -651,8 +652,12 @@ class EmailForm(forms.Form):
|
||||
class LeadForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Lead
|
||||
fields = ['customer',
|
||||
'city',
|
||||
fields = [
|
||||
'first_name',
|
||||
'last_name',
|
||||
'email',
|
||||
'phone_number',
|
||||
'address',
|
||||
'id_car_make',
|
||||
'id_car_model',
|
||||
'year',
|
||||
@ -671,6 +676,11 @@ class LeadForm(forms.ModelForm):
|
||||
(obj.id_car_make, obj.get_local_name()) for obj in queryset
|
||||
]
|
||||
|
||||
class ScheduleForm(forms.ModelForm):
|
||||
scheduled_at = forms.DateTimeField(widget=DateTimeInput(attrs={'type': 'datetime-local'}))
|
||||
class Meta:
|
||||
model = Schedule
|
||||
fields = ['purpose','scheduled_type', 'scheduled_at', 'notes']
|
||||
|
||||
class NoteForm(forms.ModelForm):
|
||||
class Meta:
|
||||
|
||||
@ -4,6 +4,8 @@ from django.utils import timezone
|
||||
from django.shortcuts import redirect
|
||||
from django.urls import reverse
|
||||
|
||||
from inventory.utils import get_user_type
|
||||
|
||||
logger = logging.getLogger('user_activity')
|
||||
|
||||
|
||||
@ -37,7 +39,8 @@ class InjectParamsMiddleware:
|
||||
|
||||
def __call__(self, request):
|
||||
try:
|
||||
request.entity = request.user.dealer.entity
|
||||
# request.entity = request.user.dealer.entity
|
||||
request.dealer = get_user_type(request)
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
|
||||
21
inventory/migrations/0002_alter_lead_customer.py
Normal file
21
inventory/migrations/0002_alter_lead_customer.py
Normal file
@ -0,0 +1,21 @@
|
||||
# Generated by Django 4.2.17 on 2025-02-04 09:34
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.DJANGO_LEDGER_CUSTOMER_MODEL),
|
||||
('inventory', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='lead',
|
||||
name='customer',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='leads', to=settings.DJANGO_LEDGER_CUSTOMER_MODEL),
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,46 @@
|
||||
# Generated by Django 4.2.17 on 2025-02-04 11:38
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import phonenumber_field.modelfields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.DJANGO_LEDGER_CUSTOMER_MODEL),
|
||||
('inventory', '0002_alter_lead_customer'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='lead',
|
||||
name='email',
|
||||
field=models.EmailField(default='x@tenhal.sa', max_length=254, verbose_name='Email'),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='lead',
|
||||
name='first_name',
|
||||
field=models.CharField(default='test', max_length=50, verbose_name='First Name'),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='lead',
|
||||
name='last_name',
|
||||
field=models.CharField(default='test', max_length=50, verbose_name='Last Name'),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='lead',
|
||||
name='phone_number',
|
||||
field=phonenumber_field.modelfields.PhoneNumberField(default='056523656', max_length=128, region='SA', verbose_name='Phone Number'),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='lead',
|
||||
name='customer',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='leads', to=settings.DJANGO_LEDGER_CUSTOMER_MODEL),
|
||||
),
|
||||
]
|
||||
23
inventory/migrations/0004_remove_lead_city_lead_address.py
Normal file
23
inventory/migrations/0004_remove_lead_city_lead_address.py
Normal file
@ -0,0 +1,23 @@
|
||||
# Generated by Django 4.2.17 on 2025-02-04 14:21
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('inventory', '0003_lead_email_lead_first_name_lead_last_name_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='lead',
|
||||
name='city',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='lead',
|
||||
name='address',
|
||||
field=models.CharField(default='', max_length=50, verbose_name='address'),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
||||
33
inventory/migrations/0005_schedule.py
Normal file
33
inventory/migrations/0005_schedule.py
Normal file
@ -0,0 +1,33 @@
|
||||
# Generated by Django 4.2.17 on 2025-02-04 15:15
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.DJANGO_LEDGER_CUSTOMER_MODEL),
|
||||
('inventory', '0004_remove_lead_city_lead_address'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Schedule',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('purpose', models.CharField(max_length=200)),
|
||||
('scheduled_at', models.DateTimeField()),
|
||||
('notes', models.TextField(blank=True, null=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='schedules', to=settings.DJANGO_LEDGER_CUSTOMER_MODEL)),
|
||||
('lead', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='schedules', to='inventory.lead')),
|
||||
('scheduled_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.staff')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-scheduled_at'],
|
||||
},
|
||||
),
|
||||
]
|
||||
18
inventory/migrations/0006_alter_schedule_purpose.py
Normal file
18
inventory/migrations/0006_alter_schedule_purpose.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.2.17 on 2025-02-04 15:31
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('inventory', '0005_schedule'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='schedule',
|
||||
name='purpose',
|
||||
field=models.CharField(choices=[('Product Demo', 'Product Demo'), ('Follow-Up Call', 'Follow-Up Call'), ('Contract Discussion', 'Contract Discussion'), ('Sales Meeting', 'Sales Meeting'), ('Support Call', 'Support Call'), ('Other', 'Other')], max_length=200),
|
||||
),
|
||||
]
|
||||
18
inventory/migrations/0007_schedule_scheduled_type.py
Normal file
18
inventory/migrations/0007_schedule_scheduled_type.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.2.17 on 2025-02-04 15:36
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('inventory', '0006_alter_schedule_purpose'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='schedule',
|
||||
name='scheduled_type',
|
||||
field=models.CharField(choices=[('Call', 'Call'), ('Meeting', 'Meeting'), ('Email', 'Email')], default='Call', max_length=200),
|
||||
),
|
||||
]
|
||||
@ -1,3 +1,4 @@
|
||||
from datetime import timezone
|
||||
import itertools
|
||||
from uuid import uuid4
|
||||
from django.conf import settings
|
||||
@ -1087,8 +1088,13 @@ class Representative(models.Model, LocalizedNameMixin):
|
||||
|
||||
class Lead(models.Model):
|
||||
dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name="leads")
|
||||
first_name = models.CharField(max_length=50, verbose_name=_("First Name"))
|
||||
last_name = models.CharField(max_length=50, verbose_name=_("Last Name"))
|
||||
email = models.EmailField(verbose_name=_("Email"))
|
||||
phone_number = PhoneNumberField(region="SA", verbose_name=_("Phone Number"))
|
||||
customer = models.ForeignKey(
|
||||
Customer, on_delete=models.CASCADE, related_name="leads"
|
||||
CustomerModel, on_delete=models.CASCADE, related_name="leads",
|
||||
null=True,blank=True
|
||||
)
|
||||
id_car_make = models.ForeignKey(
|
||||
CarMake,
|
||||
@ -1113,7 +1119,7 @@ class Lead(models.Model):
|
||||
channel = models.CharField(
|
||||
max_length=50, choices=Channel.choices, verbose_name=_("Channel")
|
||||
)
|
||||
city = models.CharField(max_length=50, verbose_name=_("City"))
|
||||
address = models.CharField(max_length=50, verbose_name=_("address"))
|
||||
staff = models.ForeignKey(
|
||||
Staff,
|
||||
on_delete=models.SET_NULL,
|
||||
@ -1146,7 +1152,80 @@ class Lead(models.Model):
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.first_name} {self.last_name}"
|
||||
|
||||
@property
|
||||
def is_converted(self):
|
||||
return bool(self.customer)
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"first_name": str(self.first_name),
|
||||
"last_name": str(self.last_name),
|
||||
"email": str(self.email),
|
||||
"address": str(self.address),
|
||||
"phone_number": str(self.phone_number),
|
||||
"id_car_make": str(self.id_car_make.name),
|
||||
"id_car_model": str(self.id_car_model.name),
|
||||
"year": str(self.year),
|
||||
"created_at": str(self.created.strftime("%Y-%m-%d")),
|
||||
}
|
||||
@property
|
||||
def full_name(self):
|
||||
return f"{self.first_name} {self.last_name}"
|
||||
def convert_to_customer(self,entity):
|
||||
if entity and not CustomerModel.objects.filter(email=self.email).exists():
|
||||
customer = entity.create_customer(
|
||||
customer_model_kwargs={
|
||||
"customer_name": self.full_name,
|
||||
"address_1": self.address,
|
||||
"phone": self.phone_number,
|
||||
"email": self.email,
|
||||
}
|
||||
)
|
||||
customer.additional_info = {}
|
||||
customer.additional_info.update({"info":self.to_dict()})
|
||||
customer.save()
|
||||
self.customer = customer
|
||||
self.save()
|
||||
|
||||
def get_latest_schedule(self):
|
||||
return self.schedules.order_by('-scheduled_at').first()
|
||||
|
||||
class Schedule(models.Model):
|
||||
PURPOSE_CHOICES = [
|
||||
('Product Demo', 'Product Demo'),
|
||||
('Follow-Up Call', 'Follow-Up Call'),
|
||||
('Contract Discussion', 'Contract Discussion'),
|
||||
('Sales Meeting', 'Sales Meeting'),
|
||||
('Support Call', 'Support Call'),
|
||||
('Other', 'Other'),
|
||||
]
|
||||
ScheduledType = [
|
||||
('Call', 'Call'),
|
||||
('Meeting', 'Meeting'),
|
||||
('Email', 'Email'),
|
||||
]
|
||||
lead = models.ForeignKey(Lead, on_delete=models.CASCADE, related_name='schedules')
|
||||
customer = models.ForeignKey(CustomerModel, on_delete=models.CASCADE, related_name='schedules')
|
||||
scheduled_by = models.ForeignKey(Staff, on_delete=models.CASCADE)
|
||||
purpose = models.CharField(max_length=200, choices=PURPOSE_CHOICES)
|
||||
scheduled_at = models.DateTimeField()
|
||||
scheduled_type = models.CharField(max_length=200, choices=ScheduledType,default='Call')
|
||||
notes = models.TextField(blank=True, null=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"Scheduled {self.purpose} with {self.customer.customer_name} on {self.scheduled_at}"
|
||||
|
||||
def schedule_past_date(self):
|
||||
if self.scheduled_at < timezone.now():
|
||||
return True
|
||||
return False
|
||||
|
||||
class Meta:
|
||||
ordering = ['-scheduled_at']
|
||||
|
||||
|
||||
class LeadStatusHistory(models.Model):
|
||||
lead = models.ForeignKey(
|
||||
@ -1181,7 +1260,7 @@ class Opportunity(models.Model):
|
||||
Dealer, on_delete=models.CASCADE, related_name="opportunities"
|
||||
)
|
||||
customer = models.ForeignKey(
|
||||
Customer, on_delete=models.CASCADE, related_name="opportunities"
|
||||
Customer, on_delete=models.CASCADE, related_name="opportunities"
|
||||
)
|
||||
car = models.ForeignKey(
|
||||
Car, on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_("Car")
|
||||
@ -1213,7 +1292,7 @@ class Opportunity(models.Model):
|
||||
verbose_name_plural = _("Opportunities")
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.car.id_car_make.name} - {self.car.id_car_model.name} : {self.customer.get_full_name}"
|
||||
return f"{self.car.id_car_make.name} - {self.car.id_car_model.name} : {self.customer}"
|
||||
|
||||
|
||||
class Notes(models.Model):
|
||||
|
||||
@ -811,7 +811,7 @@ def notify_assigned_staff(sender, instance, created, **kwargs):
|
||||
if instance.staff: # Check if the lead is assigned
|
||||
models.Notification.objects.create(
|
||||
user=instance.staff.user,
|
||||
message=f"You have been assigned a new lead: {instance.customer.get_full_name}."
|
||||
message=f"You have been assigned a new lead: {instance.full_name}."
|
||||
)
|
||||
|
||||
|
||||
|
||||
@ -92,15 +92,25 @@ urlpatterns = [
|
||||
path(
|
||||
"crm/leads/<int:pk>/update/", views.LeadUpdateView.as_view(), name="lead_update"
|
||||
),
|
||||
path(
|
||||
"crm/leads/<int:pk>/delete/", views.LeadDeleteView.as_view(), name="lead_delete"
|
||||
),
|
||||
path("crm/leads/<int:pk>/delete/", views.LeadDeleteView, name="lead_delete"),
|
||||
path("crm/leads/<int:pk>/add-note/", views.add_note_to_lead, name="add_note"),
|
||||
path("crm/leads/<int:pk>/lead-convert/", views.lead_convert, name="lead_convert"),
|
||||
path(
|
||||
"crm/leads/<int:pk>/add-activity/",
|
||||
views.add_activity_to_lead,
|
||||
name="add_activity",
|
||||
),
|
||||
path(
|
||||
"crm/leads/<int:pk>/send_lead_email/",
|
||||
views.send_lead_email,
|
||||
name="send_lead_email",
|
||||
),
|
||||
path(
|
||||
"crm/leads/<int:pk>/schedule/",
|
||||
views.schedule_lead,
|
||||
name="schedule_lead",
|
||||
),
|
||||
|
||||
path(
|
||||
"crm/opportunities/create/",
|
||||
views.OpportunityCreateView.as_view(),
|
||||
|
||||
@ -350,7 +350,7 @@ def set_bill_payment(dealer, entity, bill, amount, payment_method):
|
||||
account=cash_account, # Debit Cash
|
||||
amount=amount, # Payment amount
|
||||
tx_type="debit",
|
||||
description="Payment Received",
|
||||
description="Bill Payment Received",
|
||||
)
|
||||
|
||||
TransactionModel.objects.create(
|
||||
@ -358,7 +358,7 @@ def set_bill_payment(dealer, entity, bill, amount, payment_method):
|
||||
account=account_payable, # Credit Accounts Receivable
|
||||
amount=amount, # Payment amount
|
||||
tx_type="credit",
|
||||
description="Payment Received",
|
||||
description="Bill Payment Received",
|
||||
)
|
||||
|
||||
bill.make_payment(amount)
|
||||
|
||||
@ -1804,7 +1804,7 @@ class OrganizationListView(LoginRequiredMixin, ListView):
|
||||
|
||||
|
||||
class OrganizationDetailView(DetailView):
|
||||
model = models.Organization
|
||||
model = CustomerModel
|
||||
template_name = "organizations/organization_detail.html"
|
||||
context_object_name = "organization"
|
||||
|
||||
@ -1812,12 +1812,8 @@ class OrganizationDetailView(DetailView):
|
||||
def OrganizationCreateView(request):
|
||||
if request.method == "POST":
|
||||
form = forms.OrganizationForm(request.POST)
|
||||
|
||||
# upload logo
|
||||
image = request.FILES.get("logo")
|
||||
file_name = default_storage.save("images/{}".format(image.name), image)
|
||||
file_url = default_storage.url(file_name)
|
||||
|
||||
|
||||
organization_dict = {
|
||||
x: request.POST[x] for x in request.POST if x != "csrfmiddlewaretoken"
|
||||
}
|
||||
@ -1831,7 +1827,12 @@ def OrganizationCreateView(request):
|
||||
"email": organization_dict["email"],
|
||||
}
|
||||
)
|
||||
organization_dict["logo"] = file_url
|
||||
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
|
||||
organization_dict["pk"] = str(instance.pk)
|
||||
instance.additional_info["organization_info"] = organization_dict
|
||||
instance.additional_info["type"] = "organization"
|
||||
@ -2475,6 +2476,7 @@ class EstimateDetailView(LoginRequiredMixin, DetailView):
|
||||
if estimate.get_itemtxs_data():
|
||||
calculator = CarFinanceCalculator(estimate)
|
||||
finance_data = calculator.get_finance_data()
|
||||
print(finance_data.get("cars"))
|
||||
kwargs["data"] = finance_data
|
||||
kwargs["invoice"] = (
|
||||
InvoiceModel.objects.all().filter(ce_model=estimate).first()
|
||||
@ -2609,11 +2611,7 @@ class InvoiceDetailView(LoginRequiredMixin, DetailView):
|
||||
|
||||
if invoice.get_itemtxs_data():
|
||||
calculator = CarFinanceCalculator(invoice)
|
||||
finance_data = calculator.get_finance_data()
|
||||
print(
|
||||
(finance_data["total_vat_amount"] + finance_data["total_price"])
|
||||
== finance_data["grand_total"]
|
||||
)
|
||||
finance_data = calculator.get_finance_data()
|
||||
kwargs["data"] = finance_data
|
||||
kwargs["payments"] = JournalEntryModel.objects.filter(
|
||||
ledger=invoice.ledger
|
||||
@ -2954,11 +2952,10 @@ class LeadCreateView(CreateView, SuccessMessageMixin, LoginRequiredMixin):
|
||||
model = models.Lead
|
||||
form_class = forms.LeadForm
|
||||
template_name = "crm/leads/lead_form.html"
|
||||
# success_message = "Lead created successfully!"
|
||||
success_message = "Lead created successfully!"
|
||||
success_url = reverse_lazy("lead_list")
|
||||
|
||||
def form_valid(self, form):
|
||||
print("Form data:", form.cleaned_data) # Debug form data
|
||||
def form_valid(self, form):
|
||||
dealer = get_user_type(self.request)
|
||||
form.instance.dealer = dealer
|
||||
return super().form_valid(form)
|
||||
@ -2981,10 +2978,11 @@ class LeadUpdateView(UpdateView):
|
||||
success_url = reverse_lazy("lead_list")
|
||||
|
||||
|
||||
class LeadDeleteView(DeleteView):
|
||||
model = models.Lead
|
||||
template_name = "crm/leads/lead_confirm_delete.html"
|
||||
success_url = reverse_lazy("lead_list")
|
||||
def LeadDeleteView(request,pk):
|
||||
lead = get_object_or_404(models.Lead, pk=pk)
|
||||
lead.delete()
|
||||
messages.success(request, "Lead deleted successfully!")
|
||||
return redirect("lead_list")
|
||||
|
||||
|
||||
def add_note_to_lead(request, pk):
|
||||
@ -3002,6 +3000,74 @@ def add_note_to_lead(request, pk):
|
||||
form = forms.NoteForm()
|
||||
return render(request, "crm/add_note.html", {"form": form, "lead": lead})
|
||||
|
||||
def lead_convert(request, pk):
|
||||
lead = get_object_or_404(models.Lead, pk=pk)
|
||||
dealer = get_user_type(request)
|
||||
lead.convert_to_customer(dealer.entity)
|
||||
messages.success(request, "Lead converted to customer successfully!")
|
||||
return redirect("lead_list")
|
||||
|
||||
def schedule_lead(request, pk):
|
||||
lead = get_object_or_404(models.Lead, pk=pk)
|
||||
if request.method == "POST":
|
||||
form = forms.ScheduleForm(request.POST)
|
||||
if form.is_valid():
|
||||
instance = form.save(commit=False)
|
||||
instance.lead = lead
|
||||
instance.customer = lead.customer
|
||||
if hasattr(request.user, "staff"):
|
||||
instance.scheduled_by = request.user.staff
|
||||
instance.save()
|
||||
messages.success(request, "Lead scheduled successfully!")
|
||||
return redirect("lead_list")
|
||||
else:
|
||||
messages.error(request, f"Invalid form data: {str(form.errors)}")
|
||||
return redirect("lead_list")
|
||||
|
||||
form = forms.ScheduleForm()
|
||||
return render(request, "crm/leads/schedule_lead.html", {"lead": lead, "form": form})
|
||||
|
||||
def send_lead_email(request, pk):
|
||||
lead = get_object_or_404(models.Lead, pk=pk)
|
||||
dealer = get_user_type(request)
|
||||
lead.convert_to_customer(dealer.entity)
|
||||
|
||||
if request.method == "POST":
|
||||
send_email(
|
||||
"manager@tenhal.com",
|
||||
request.POST.get("to"),
|
||||
request.POST.get("subject"),
|
||||
request.POST.get("message"),
|
||||
)
|
||||
messages.success(request, "Email sent successfully!")
|
||||
return redirect("lead_list")
|
||||
msg = f"""
|
||||
السلام عليكم
|
||||
Dear {lead.full_name},
|
||||
|
||||
أود أن أشارككم تقدير المشروع الذي ناقشناه. يرجى العثور على الوثيقة التفصيلية للمقترح المرفقة.
|
||||
|
||||
I hope this email finds you well. I wanted to share with you the estimate for the project we discussed. Please find the detailed estimate document attached.
|
||||
|
||||
يرجى مراجعة المقترح وإعلامي إذا كانت لديك أي أسئلة أو مخاوف. إذا كانت كل شيء يبدو جيدًا، يمكننا المضي قدمًا في المشروع.
|
||||
|
||||
Please review the estimate and let me know if you have any questions or concerns. If everything looks good, we can proceed with the project.
|
||||
|
||||
شكراً لاهتمامكم بهذا الأمر.
|
||||
Thank you for your attention to this matter.
|
||||
|
||||
تحياتي,
|
||||
Best regards,
|
||||
[Your Name]
|
||||
[Your Position]
|
||||
[Your Company]
|
||||
[Your Contact Information]
|
||||
"""
|
||||
return render(
|
||||
request,
|
||||
"crm/leads/lead_send.html",
|
||||
{"lead": lead, "message": msg},
|
||||
)
|
||||
|
||||
def add_activity_to_lead(request, pk):
|
||||
lead = get_object_or_404(models.Lead, pk=pk)
|
||||
@ -3244,29 +3310,23 @@ class BillDetailView(LoginRequiredMixin, DetailView):
|
||||
if bill.get_itemtxs_data():
|
||||
txs = bill.get_itemtxs_data()[0]
|
||||
|
||||
car_and_item_info = [
|
||||
transactions = [
|
||||
{
|
||||
"car": models.Car.objects.get(vin=x.item_model.name),
|
||||
"total": models.Car.objects.get(
|
||||
vin=x.item_model.name
|
||||
).finances.cost_price
|
||||
* Decimal(x.quantity),
|
||||
"itemmodel": x,
|
||||
"item": x,
|
||||
"total": Decimal(x.unit_cost) * Decimal(x.quantity),
|
||||
}
|
||||
for x in txs
|
||||
]
|
||||
grand_total = sum(
|
||||
Decimal(
|
||||
models.Car.objects.get(vin=x.item_model.name).finances.cost_price
|
||||
)
|
||||
* Decimal(x.quantity)
|
||||
Decimal(x.unit_cost) * Decimal(x.quantity)
|
||||
for x in txs
|
||||
)
|
||||
vat = models.VatRate.objects.filter(is_active=True).first()
|
||||
if vat:
|
||||
grand_total += round(Decimal(grand_total) * Decimal(vat.rate), 2)
|
||||
kwargs["car_and_item_info"] = car_and_item_info
|
||||
# vat = models.VatRate.objects.filter(is_active=True).first()
|
||||
# if vat:
|
||||
# grand_total += round(Decimal(grand_total) * Decimal(vat.rate), 2)
|
||||
kwargs["transactions"] = transactions
|
||||
kwargs["grand_total"] = grand_total
|
||||
print(dir(txs[0]))
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
@ -3345,7 +3405,7 @@ def bill_mark_as_paid(request, pk):
|
||||
bill.mark_as_paid(user_model=dealer.entity.admin)
|
||||
bill.save()
|
||||
bill.ledger.lock_journal_entries()
|
||||
bill.ledger.post_journal_entries()
|
||||
bill.ledger.post_journal_entries()
|
||||
bill.ledger.post()
|
||||
bill.ledger.save()
|
||||
messages.success(request, _("Bill marked as paid successfully."))
|
||||
@ -3488,9 +3548,7 @@ def bill_create(request):
|
||||
),
|
||||
}
|
||||
)
|
||||
car_list = models.Car.objects.filter(
|
||||
dealer=dealer, finances__selling_price__gt=0, status="available"
|
||||
)
|
||||
car_list = models.Car.objects.filter(dealer=dealer)
|
||||
context = {
|
||||
"form": form,
|
||||
"items": [
|
||||
|
||||
@ -52,7 +52,6 @@
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"
|
||||
integrity="sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g=="
|
||||
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
|
||||
|
||||
|
||||
{% block customCSS %}
|
||||
@ -135,10 +134,8 @@ function notify(tag,msg){
|
||||
<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>
|
||||
<script>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
{% if entity_slug %}
|
||||
let entitySlug = "{{ view.kwargs.entity_slug }}"
|
||||
{% endif %}
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n static %}
|
||||
{% block content %}
|
||||
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-12">
|
||||
<div class="row align-items-center justify-content-between g-3 mb-3">
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n static crispy_forms_filters %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{% if object %}Update{% else %}Create{% endif %}</h1>
|
||||
<form method="post">
|
||||
|
||||
@ -36,13 +36,12 @@
|
||||
<table class="table fs-9 mb-0 border-top border-translucent">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="align-middle white-space-nowrap text-uppercase" scope="col" style="width: 10%;">{{ _("Status")|capfirst }}</th>
|
||||
<th class="align-middle white-space-nowrap text-uppercase" scope="col" style="width: 25%;">
|
||||
<th class="align-middle white-space-nowrap text-uppercase" scope="col" style="width: 20%;">{{ _("Lead Name")|capfirst }}</th>
|
||||
<th class="align-middle white-space-nowrap text-uppercase" scope="col" style="width: 15%;">
|
||||
<div class="d-inline-flex flex-center">
|
||||
<div class="d-flex align-items-center px-1 py-1 bg-warning-subtle rounded me-2"><span class="text-warning-dark" data-feather="user"></span></div>
|
||||
<span>{{ _("Name")|capfirst }}</span>
|
||||
<div class="d-flex align-items-center px-1 py-1 bg-success-subtle rounded me-2"><i class="text-success-dark fas fa-car"></i></div>
|
||||
<span>{{ _("Car")|capfirst }}</span>
|
||||
</div>
|
||||
|
||||
</th>
|
||||
<th class="align-middle white-space-nowrap text-uppercase" scope="col" style="width: 15%;">
|
||||
<div class="d-inline-flex flex-center">
|
||||
@ -56,12 +55,24 @@
|
||||
<span>{{ _("Phone Number") }}</span>
|
||||
</div>
|
||||
</th>
|
||||
<th class="align-middle white-space-nowrap text-uppercase" scope="col" style="width: 15%;">
|
||||
<div class="d-inline-flex flex-center">
|
||||
<div class="d-flex align-items-center px-1 py-1 bg-primary-subtle rounded me-2"><span class="text-primary-dark" data-feather="phone"></span></div>
|
||||
<span>{{ _("Schedule") }}</span>
|
||||
</div>
|
||||
</th>
|
||||
<th class="align-middle white-space-nowrap text-uppercase" scope="col" style="width: 10%;">
|
||||
<div class="d-inline-flex flex-center">
|
||||
<div class="d-flex align-items-center bg-info-subtle rounded me-2"><span class="text-info-dark" data-feather="database"></span></div>
|
||||
<span>{{ _("Assigned To")|capfirst }}</span>
|
||||
</div>
|
||||
</th>
|
||||
<th class="align-middle white-space-nowrap text-uppercase" scope="col" style="width: 10%;">
|
||||
<div class="d-inline-flex flex-center">
|
||||
<div class="d-flex align-items-center bg-info-subtle rounded me-2"><span class="text-info-dark" data-feather="database"></span></div>
|
||||
<span>{{ _("Source")|capfirst }}</span>
|
||||
</div>
|
||||
</th>
|
||||
</th>``
|
||||
<th class="align-middle white-space-nowrap text-uppercase" scope="col" style="width: 10%;">
|
||||
<div class="d-inline-flex flex-center">
|
||||
<div class="d-flex align-items-center bg-warning-subtle rounded me-2"><span class="text-warning-dark" data-feather="grid"></span></div>
|
||||
@ -101,29 +112,39 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
||||
<td class="align-middle white-space-nowrap fw-semibold">
|
||||
<div class="d-flex align-items-center">
|
||||
{% if lead.status == "new" %}
|
||||
<span class="badge badge-phoenix badge-phoenix-primary"><span class="badge-label">{{_("New")}}</span><span class="fa fa-bell ms-1"></span></span>
|
||||
{% elif lead.status == "pending" %}
|
||||
<span class="badge badge-phoenix badge-phoenix-warning"><span class="badge-label">{{_("Pending")}}</span><span class="fa fa-clock-o ms-1"></span></span>
|
||||
{% elif lead.status == "in_progress" %}
|
||||
<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 == "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 %}
|
||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
||||
<td class="name align-middle white-space-nowrap ps-0">
|
||||
<div class="d-flex align-items-center"><a href="#!">
|
||||
</a>
|
||||
<div><a class="fs-8 fw-bold" href="{% url 'lead_detail' lead.pk %}">{{lead.full_name}}</a>
|
||||
<div class="d-flex align-items-center">
|
||||
<p class="mb-0 text-body-highlight fw-semibold fs-9 me-2"></p>
|
||||
{% if lead.status == "new" %}
|
||||
<span class="badge badge-phoenix badge-phoenix-primary"><span class="badge-label">{{_("New")}}</span><span class="fa fa-bell ms-1"></span></span>
|
||||
{% elif lead.status == "pending" %}
|
||||
<span class="badge badge-phoenix badge-phoenix-warning"><span class="badge-label">{{_("Pending")}}</span><span class="fa fa-clock-o ms-1"></span></span>
|
||||
{% elif lead.status == "in_progress" %}
|
||||
<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 == "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 %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</td>
|
||||
|
||||
<td class="align-middle white-space-nowrap fw-semibold"><a class="text-body-highlight" href="">{{ lead.id_car_make.get_local_name }} - {{ lead.id_car_model.get_local_name }} {{ lead.year }}</a></td>
|
||||
<td class="align-middle white-space-nowrap fw-semibold"><a class="text-body-highlight" href="">{{ lead.email }}</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">
|
||||
<div class="d-flex align-items-center">
|
||||
<a class="fs-8 fw-bold" href="{% url 'lead_detail' lead.pk %}">{{ lead.customer.get_full_name }}</a>
|
||||
</div>
|
||||
</td>
|
||||
<td class="align-middle white-space-nowrap fw-semibold"><a class="text-body-highlight" href="">{{ lead.customer.email }}</a></td>
|
||||
<td class="align-middle white-space-nowrap fw-semibold"><a class="text-body-highlight" href="tel:{{ lead.customer.phone_number }}">{{ lead.customer.phone_number }}</a></td>
|
||||
{% if lead.get_latest_schedule %}
|
||||
<span class="text-success" data-feather="calendar"></span> <span class="badge badge-phoenix badge-phoenix-primary text-success fw-semibold">{{ lead.get_latest_schedule.scheduled_type }}</span> <br>
|
||||
<span class="text-success" data-feather="clock"></span> <span class="badge badge-phoenix badge-phoenix-primary text-success fw-semibold">{{ lead.get_latest_schedule.scheduled_at }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="align-middle white-space-nowrap text-body-tertiary text-opacity-85 fw-semibold text-body-highlight">{{ lead.staff|upper }}</td>
|
||||
<td class="align-middle white-space-nowrap fw-semibold text-body-highlight">{{ lead.source|upper }}</td>
|
||||
<td class="align-middle white-space-nowrap text-body-tertiary text-opacity-85 fw-semibold text-body-highlight">{{ lead.channel|upper }}</td>
|
||||
<td class="align-middle white-space-nowrap text-body-tertiary text-opacity-85 text-body-tertiary">{{ lead.created|date }}</td>
|
||||
@ -141,6 +162,11 @@
|
||||
</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.is_converted %}
|
||||
<a href="{% url 'lead_convert' lead.id %}" class="dropdown-item text-success-dark">{% trans "Convert To Customer" %}</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>
|
||||
|
||||
36
templates/crm/leads/lead_send.html
Normal file
36
templates/crm/leads/lead_send.html
Normal file
@ -0,0 +1,36 @@
|
||||
{% extends "base.html" %}
|
||||
{% load crispy_forms_filters %}
|
||||
{% load i18n static %}
|
||||
|
||||
{% block title %}{{ _("Leads") }}{% endblock title %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card email-content">
|
||||
<h5 class="card-header">Send Mail</h5>
|
||||
<div class="card-body">
|
||||
<form class="d-flex flex-column h-100" action="{% url 'send_lead_email' lead.pk %}" method="post">
|
||||
{% csrf_token %}
|
||||
<div class="row g-3 mb-2">
|
||||
<div class="col-12">
|
||||
<input class="form-control" name="to" type="text" placeholder="To" value="{{lead.email}}" />
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<input class="form-control" name="subject" type="text" placeholder="Subject" value="" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3 flex-1">
|
||||
<textarea class="form-control" name="message" rows="15" placeholder="Message">{{message}}</textarea>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div class="d-flex">
|
||||
<a href="{% url 'lead_detail' lead.pk %}" class="btn btn-link text-body fs-10 text-decoration-none">Discard</a>
|
||||
<button class="btn btn-primary fs-10" type="submit">Send<span class="fa-solid fa-paper-plane ms-1"></span></button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock content %}
|
||||
21
templates/crm/leads/schedule_lead.html
Normal file
21
templates/crm/leads/schedule_lead.html
Normal file
@ -0,0 +1,21 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n static crispy_forms_filters %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{% if object %}Update{% else %}Create{% endif %}</h1>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{{ form|crispy }}
|
||||
<button
|
||||
type="submit"
|
||||
name="schedule_lead"
|
||||
id="lead-save"
|
||||
class="btn btn-phoenix-primary"
|
||||
>
|
||||
{{ _("Save") }}
|
||||
</button>
|
||||
<a href="{% url 'lead_list' %}" class="btn btn-phoenix-secondary">
|
||||
{{ _("Cancel") }}
|
||||
</a>
|
||||
</form>
|
||||
{% endblock %}
|
||||
@ -56,7 +56,7 @@
|
||||
<!-- ============================================-->
|
||||
<!-- <section> begin ============================-->
|
||||
<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 mx-3">
|
||||
<div class="d-flex justify-content-between align-items-end mb-4">
|
||||
<h2 class="mb-0">{% trans 'Bill' %}</h2>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
@ -217,13 +217,13 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in car_and_item_info %}
|
||||
{% for transaction in transactions %}
|
||||
<tr>
|
||||
<td class="">{{forloop.counter}}</td>
|
||||
<td class="">{{item.car.id_car_model}}</td>
|
||||
<td class="align-middle">{{item.itemmodel.quantity}}</td>
|
||||
<td class="align-middle ps-5">{{item.car.finances.cost_price}}</td>
|
||||
<td class="align-middle text-body-tertiary fw-semibold">{{item.total}}</td>
|
||||
<td class=""></td>
|
||||
<td class="">{{transaction.item.item_model.name}}</td>
|
||||
<td class="align-middle">{{transaction.item.quantity}}</td>
|
||||
<td class="align-middle ps-5">{{transaction.item.unit_cost}}</td>
|
||||
<td class="align-middle text-body-tertiary fw-semibold">{{transaction.total}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<tr class="bg-body-secondary total-sum">
|
||||
@ -235,7 +235,7 @@
|
||||
<tr class="bg-body-secondary total-sum">
|
||||
<td class="align-middle ps-4 fw-bolder text-body-highlight" colspan="4">{% trans "Grand Total" %}</td>
|
||||
<td class="align-middle text-start fw-bolder">
|
||||
<span id="grand-total">{{bill.additional_info.car_finance.total_vat}}</span>
|
||||
<span id="grand-total">{{grand_total}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@ -27,11 +27,6 @@
|
||||
<span class="fas fa-chevron-right"> </span>
|
||||
<span class="fas fa-chevron-right"> </span>
|
||||
</a>
|
||||
|
||||
<span class="fw-bold">{% trans 'Year' %}:</span>
|
||||
<a href="{{ previous_year_url }}" class="text-decoration-none me-2"><< {{ previous_year }}</a>
|
||||
<a href="{{ current_year_url }}" class="text-decoration-none me-2">{{ year }}</a>
|
||||
<a href="{{ next_year_url }}" class="text-decoration-none">{{ next_year }} >></a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@ -5,14 +5,14 @@
|
||||
<div class="row my-4">
|
||||
<h2>{{ organization.get_local_name }}</h2>
|
||||
<ul class="list-group mb-4">
|
||||
<li class="list-group-item"><strong>{% trans "CRN" %}:</strong> {{ organization.crn }}</li>
|
||||
<li class="list-group-item"><strong>{% trans "VRN" %}:</strong> {{ organization.vrn }}</li>
|
||||
<li class="list-group-item"><strong>{% trans "Phone" %}:</strong> {{ organization.phone_number }}</li>
|
||||
<li class="list-group-item"><strong>{% trans "Address" %}:</strong> {{ organization.address }}</li>
|
||||
<li class="list-group-item"><strong>{% trans "CRN" %}:</strong> {{ organization.additional_info.organization_info.crn }}</li>
|
||||
<li class="list-group-item"><strong>{% trans "VRN" %}:</strong> {{ organization.additional_info.organization_info.vrn }}</li>
|
||||
<li class="list-group-item"><strong>{% trans "Phone" %}:</strong> {{ organization.additional_info.organization_info.phone_number }}</li>
|
||||
<li class="list-group-item"><strong>{% trans "Address" %}:</strong> {{ organization.additional_info.organization_info.address }}</li>
|
||||
</ul>
|
||||
<div class="d-flex">
|
||||
<a href="{% url 'organization_update' organization.id %}" class="btn btn-sm btn-warning me-2">{% trans "Edit" %}</a>
|
||||
<form method="post" action="{% url 'organization_delete' organization.id %}">
|
||||
<a href="{% url 'organization_update' organization.pk %}" class="btn btn-sm btn-warning me-2">{% trans "Edit" %}</a>
|
||||
<form method="post" action="{% url 'organization_delete' organization.pk %}">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn btn-sm btn-danger">{% trans "Delete" %}</button>
|
||||
</form>
|
||||
|
||||
@ -108,9 +108,9 @@
|
||||
|
||||
<td class="name align-middle white-space-nowrap ps-0">
|
||||
<div class="d-flex align-items-center">
|
||||
<div><a class="fs-8 fw-bold" href="{% url 'organization_detail' org.pk %}">{{ org.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">
|
||||
<p class="mb-0 text-body-highlight fw-semibold fs-9 me-2">{{ org.arabic_name }}</p><span class="badge badge-phoenix badge-phoenix-primary">{{ org.pk}}</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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -33,7 +33,7 @@
|
||||
<!-- <section> begin ============================-->
|
||||
<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">
|
||||
<div class="d-flex justify-content-between align-items-end mb-4 mx-3">
|
||||
<h2 class="mb-0">{% trans 'Estimate' %}</h2>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
{% if estimate.status == 'draft' %}
|
||||
@ -46,7 +46,7 @@
|
||||
<a href="{% url 'invoice_create' estimate.pk %}" class="btn btn-phoenix-primary"><span class="d-none d-sm-inline-block">{% trans 'Create Invoice' %}</span></a>
|
||||
{% else %}
|
||||
<a href="{% url 'create_sale_order' estimate.pk %}" class="btn btn-phoenix-primary"><span class="d-none d-sm-inline-block">{% trans 'Create Sale Order' %}</span></a>
|
||||
<a href="{% url 'preview_sale_order' estimate.pk %}" class="btn btn-phoenix-primary"><span class="d-none d-sm-inline-block">{% trans 'Preview Sale Order' %}</span></a>
|
||||
{% comment %} <a href="{% url 'preview_sale_order' estimate.pk %}" class="btn btn-phoenix-primary"><span class="d-none d-sm-inline-block">{% trans 'Preview Sale Order' %}</span></a> {% endcomment %}
|
||||
{% endif %}
|
||||
{% elif estimate.status == 'in_review' %}
|
||||
<a href="{% url 'estimate_preview' estimate.pk %}" class="btn btn-phoenix-primary"><span class="d-none d-sm-inline-block">{% trans 'Preview' %}</span></a>
|
||||
@ -83,7 +83,7 @@
|
||||
<div class="row align-items-center g-0">
|
||||
<div class="col-auto col-lg-6 col-xl-5">
|
||||
<h6 class="mb-2 me-3">{% trans 'Customer' %} :</h6>
|
||||
<p class="fs-9 text-body-secondary fw-semibold mb-0">{{estimate.customer.customer_name}}</p>
|
||||
<p class="fs-9 text-body-secondary fw-semibold mb-0">{{estimate.customer.customer_name}}</p>
|
||||
</div>
|
||||
<div class="col-12 col-lg-4">
|
||||
<h6 class="mb-2"> {% trans 'Email' %} :</h6>
|
||||
@ -119,7 +119,9 @@
|
||||
<thead class="bg-body-secondary">
|
||||
<tr>
|
||||
<th scope="col" style="width: 24px;">#</th>
|
||||
<th scope="col" style="min-width: 260px;">{% trans "Item" %}</th>
|
||||
<th scope="col" style="min-width: 260px;">{% trans "Make" %}</th>
|
||||
<th scope="col" style="min-width: 260px;">{% trans "Model" %}</th>
|
||||
<th scope="col" style="min-width: 260px;">{% trans "Year" %}</th>
|
||||
<th scope="col" style="min-width: 60px;">{% trans "Quantity" %}</th>
|
||||
<th scope="col" style="min-width: 60px;">{% trans "Unit Price" %}</th>
|
||||
<th scope="col" style="min-width: 60px;">{% trans "Total" %}</th>
|
||||
@ -129,27 +131,29 @@
|
||||
<tbody>
|
||||
{% for item in data.cars %}
|
||||
<tr>
|
||||
<td class="">{{forloop.counter}}</td>
|
||||
<td class="">{{item.make}}</td>
|
||||
<td class="align-middle"></td>
|
||||
<td class="align-middle">{{item.make}}</td>
|
||||
<td class="align-middle">{{item.model}}</td>
|
||||
<td class="align-middle">{{item.year}}</td>
|
||||
<td class="align-middle">{{item.quantity}}</td>
|
||||
<td class="align-middle ps-5">{{item.unit_price}}</td>
|
||||
<td class="align-middle text-body-tertiary fw-semibold">{{item.total}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<tr class="bg-body-secondary total-sum">
|
||||
<td class="align-middle ps-4 fw-semibold text-body-highlight" colspan="4">{% trans "Vat" %} ({{data.vat}}%)</td>
|
||||
<td class="align-middle ps-4 fw-semibold text-body-highlight" colspan="6">{% trans "Vat" %} ({{data.vat}}%)</td>
|
||||
<td class="align-middle text-start fw-semibold">
|
||||
<span id="grand-total">+ {{data.total_vat_amount}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="bg-body-secondary total-sum">
|
||||
<td class="align-middle ps-4 fw-semibold text-body-highlight" colspan="4">{% trans "Discount Amount" %}</td>
|
||||
<td class="align-middle ps-4 fw-semibold text-body-highlight" colspan="6">{% trans "Discount Amount" %}</td>
|
||||
<td class="align-middle text-start text-danger fw-semibold ">
|
||||
<span id="grand-total">- {{data.total_discount}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="bg-body-secondary total-sum">
|
||||
<td class="align-middle ps-4 fw-semibold text-body-highlight" colspan="4">{% trans "Additional Services" %}</td>
|
||||
<td class="align-middle ps-4 fw-semibold text-body-highlight" colspan="6">{% 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>
|
||||
@ -157,7 +161,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="bg-body-secondary total-sum">
|
||||
<td class="align-middle ps-4 fw-bolder text-body-highlight" colspan="4">{% trans "Grand Total" %}</td>
|
||||
<td class="align-middle ps-4 fw-bolder text-body-highlight" colspan="6">{% trans "Grand Total" %}</td>
|
||||
<td class="align-middle text-start fw-bolder">
|
||||
<span id="grand-total">{{data.grand_total}}</span>
|
||||
</td>
|
||||
|
||||
@ -55,9 +55,9 @@
|
||||
</div>
|
||||
<!-- ============================================-->
|
||||
<!-- <section> begin ============================-->
|
||||
<section class="pt-5 pb-9">
|
||||
<div class="row-small mt-3">
|
||||
<div class="d-flex justify-content-between align-items-end mb-4">
|
||||
<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="d-flex justify-content-between align-items-end mb-4 mx-3">
|
||||
<h2 class="mb-0">{% trans 'Invoice' %}</h2>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
{% if invoice.invoice_status == 'in_review' %}
|
||||
@ -212,46 +212,50 @@
|
||||
<thead class="bg-body-secondary">
|
||||
<tr>
|
||||
<th scope="col" style="width: 24px;">#</th>
|
||||
<th scope="col" style="min-width: 260px;">{% trans "Item" %}</th>
|
||||
<th scope="col" style="min-width: 260px;">{% trans "Make" %}</th>
|
||||
<th scope="col" style="min-width: 260px;">{% trans "Model" %}</th>
|
||||
<th scope="col" style="min-width: 260px;">{% trans "Year" %}</th>
|
||||
<th scope="col" style="min-width: 60px;">{% trans "Quantity" %}</th>
|
||||
<th scope="col" style="min-width: 60px;">{% trans "Unit Price" %}</th>
|
||||
<th scope="col" style="min-width: 60px;">{% trans "Total" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in car_and_item_info %}
|
||||
{% for item in data.cars %}
|
||||
<tr>
|
||||
<td class="">{{forloop.counter}}</td>
|
||||
<td class="">{{item.info.make}}</td>
|
||||
<td class="align-middle"></td>
|
||||
<td class="align-middle">{{item.make}}</td>
|
||||
<td class="align-middle">{{item.model}}</td>
|
||||
<td class="align-middle">{{item.year}}</td>
|
||||
<td class="align-middle">{{item.quantity}}</td>
|
||||
<td class="align-middle ps-5">{{item.finances.selling_price}}</td>
|
||||
<td class="align-middle ps-5">{{item.total}}</td>
|
||||
<td class="align-middle text-body-tertiary fw-semibold">{{item.total}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<tr class="bg-body-secondary total-sum">
|
||||
<td class="align-middle ps-4 fw-semibold text-body-highlight" colspan="4">{% trans "Discount Amount" %}</td>
|
||||
<td class="align-middle ps-4 fw-semibold text-body-highlight" colspan="6">{% trans "Discount Amount" %}</td>
|
||||
<td class="align-middle text-start fw-semibold">
|
||||
<span id="grand-total">- {{discount_amount}}</span>
|
||||
<span id="grand-total">- {{data.total_discount}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="bg-body-secondary total-sum">
|
||||
<td class="align-middle ps-4 fw-semibold text-body-highlight" colspan="4">{% trans "VAT" %} ({{vat}}%)</td>
|
||||
<td class="align-middle ps-4 fw-semibold text-body-highlight" colspan="6">{% trans "VAT" %} ({{data.vat}}%)</td>
|
||||
<td class="align-middle text-start fw-semibold">
|
||||
<span id="grand-total">+ {{data.total_vat_amount}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="bg-body-secondary total-sum">
|
||||
<td class="align-middle ps-4 fw-semibold text-body-highlight" colspan="4">{% trans "Additional Services" %}</td>
|
||||
<td class="align-middle ps-4 fw-semibold text-body-highlight" colspan="6">{% trans "Additional Services" %}</td>
|
||||
<td class="align-middle text-start fw-bold">
|
||||
{% for service in additional_services %}
|
||||
{% for service in data.additionals %}
|
||||
<small><span class="fw-bold">+ {{service.name}} - {{service.price}}</span></small><br>
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="bg-body-secondary total-sum">
|
||||
<td class="align-middle ps-4 fw-bolder text-body-highlight" colspan="4">{% trans "Grand Total" %}</td>
|
||||
<td class="align-middle ps-4 fw-bolder text-body-highlight" colspan="6">{% trans "Grand Total" %}</td>
|
||||
<td class="align-middle text-start fw-bolder">
|
||||
<span id="grand-total">{{total}}</span>
|
||||
<span id="grand-total">{{data.grand_total}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user