This commit is contained in:
gitea 2025-01-06 08:05:24 +00:00
commit 7ea259ccb9
43 changed files with 3268 additions and 3133 deletions

1
.idea/vcs.xml generated
View File

@ -2,5 +2,6 @@
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
<mapping directory="$PROJECT_DIR$/django-crm" vcs="Git" />
</component>
</project>

BIN
db.sqlite

Binary file not shown.

View File

@ -25,7 +25,7 @@ from .models import (
SaleQuotationCar,
AdditionalServices,
Staff,
Opportunity, DealStatus, Priority, DealSource,
Opportunity, DealStatus, Priority, Sources,
)
from django_ledger.models import ItemModel, InvoiceModel
from django.forms import ModelMultipleChoiceField, ValidationError
@ -572,11 +572,6 @@ class OpportunityForm(forms.ModelForm):
class Meta:
model = Opportunity
fields = [
'car', 'deal_name', 'deal_value', 'deal_status',
'priority', 'source'
'car', 'deal_name', 'deal_value',
]
widgets = {
'deal_status': forms.Select(choices=DealStatus.choices),
'priority': forms.Select(choices=Priority.choices),
'source': forms.Select(choices=DealSource.choices),
}

View File

@ -1,7 +1,8 @@
# Generated by Django 5.1.4 on 2025-01-02 23:56
# Generated by Django 5.1.4 on 2025-01-05 09:01
import django.db.models.deletion
import django.utils.timezone
from django.conf import settings
from django.db import migrations, models
@ -9,29 +10,30 @@ class Migration(migrations.Migration):
dependencies = [
('inventory', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AlterModelOptions(
name='subscription',
options={'verbose_name': 'Subscription', 'verbose_name_plural': 'Subscriptions'},
),
migrations.AlterModelOptions(
name='subscriptionplan',
options={'verbose_name': 'Subscription Plan', 'verbose_name_plural': 'Subscription Plans'},
),
migrations.AlterModelOptions(
name='subscriptionuser',
options={'verbose_name': 'Subscription User', 'verbose_name_plural': 'Subscription Users'},
),
migrations.RemoveField(
model_name='opportunity',
name='source',
),
migrations.RemoveField(
model_name='subscription',
name='max_users',
),
migrations.AddField(
model_name='opportunity',
name='assigned_at',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now, verbose_name='Assigned At'),
preserve_default=False,
),
migrations.AddField(
model_name='opportunity',
name='assigned_to',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='deals_assigned', to='inventory.staff'),
preserve_default=False,
),
migrations.AddField(
model_name='subscription',
name='billing_cycle',
@ -73,6 +75,27 @@ class Migration(migrations.Migration):
name='updated_at',
field=models.DateTimeField(auto_now=True),
),
migrations.AlterField(
model_name='notes',
name='created_by',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.DO_NOTHING, related_name='notes_created', to=settings.AUTH_USER_MODEL),
preserve_default=False,
),
migrations.AlterField(
model_name='opportunity',
name='deal_status',
field=models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('canceled', 'Canceled'), ('completed', 'Completed')], default='new', max_length=20, verbose_name='Deal Status'),
),
migrations.AlterField(
model_name='opportunity',
name='priority',
field=models.CharField(choices=[('low', 'Low'), ('medium', 'Medium'), ('high', 'High')], default='low', max_length=10, verbose_name='Priority'),
),
migrations.AlterField(
model_name='staff',
name='staff_type',
field=models.CharField(choices=[('manager', 'Manager'), ('inventory', 'Inventory'), ('accountant', 'Accountant'), ('sales', 'Sales'), ('coordinator', 'Coordinator'), ('receptionist', 'Receptionist'), ('agent', 'Agent')], max_length=255, verbose_name='Staff Type'),
),
migrations.AlterField(
model_name='subscription',
name='end_date',

View File

@ -632,13 +632,6 @@ class Dealer(models.Model, LocalizedNameMixin):
# def get_root_dealer(self):
# return self.parent_dealer if self.parent_dealer else self
class StaffTypes(models.TextChoices):
MANAGER = "manager", _("Manager")
INVENTORY = "inventory", _("Inventory")
ACCOUNTANT = "accountant", _("Accountant")
SALES = "sales", _("Sales")
##############################
# Additional staff types for later
@ -650,6 +643,17 @@ class StaffTypes(models.TextChoices):
##############################
class StaffTypes(models.TextChoices):
MANAGER = "manager", _("Manager")
INVENTORY = "inventory", _("Inventory")
ACCOUNTANT = "accountant", _("Accountant")
SALES = "sales", _("Sales")
COORDINATOR = "coordinator", _("Coordinator")
RECEPTIONIST = "receptionist", _("Receptionist")
AGENT = "agent", _("Agent")
class Staff(models.Model, LocalizedNameMixin):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="staff")
dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name="staff")
@ -669,33 +673,89 @@ class Staff(models.Model, LocalizedNameMixin):
return f"{self.name} - {self.get_staff_type_display()}"
class Vendor(models.Model, LocalizedNameMixin):
dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name="vendors")
crn = models.CharField(
max_length=10, unique=True, verbose_name=_("Commercial Registration Number")
)
vrn = models.CharField(
max_length=15, unique=True, verbose_name=_("VAT Registration Number")
)
arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name"))
name = models.CharField(max_length=255, verbose_name=_("English Name"))
contact_person = models.CharField(max_length=100, verbose_name=_("Contact Person"))
phone_number = PhoneNumberField(region="SA", verbose_name=_("Phone Number"))
email = models.EmailField(max_length=255, verbose_name=_("Email Address"))
address = models.CharField(
max_length=200, blank=True, null=True, verbose_name=_("Address")
)
logo = models.ImageField(
upload_to="logos/vendors", blank=True, null=True, verbose_name=_("Logo")
)
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created At"))
class ActionChoices(models.TextChoices):
CREATE = "create", _("Create")
UPDATE = "update", _("Update")
DELETE = "delete", _("Delete")
STATUS_CHANGE = "status_change", _("Status Change")
class Meta:
verbose_name = _("Vendor")
verbose_name_plural = _("Vendors")
def __str__(self):
return self.name
class DealStatus(models.TextChoices):
NEW = "new", _("New")
PENDING = "pending", _("Pending")
CANCELED = "canceled", _("Canceled")
COMPLETED = "completed", _("Completed")
class Priority(models.TextChoices):
LOW = "low", _("Low")
MEDIUM = "medium", _("Medium")
HIGH = "high", _("High")
class Sources(models.TextChoices):
REFERRALS = "referrals", _("Referrals")
WALK_IN = "walk_in", _("Walk In")
TOLL_FREE = "toll_free", _("Toll Free")
WHATSAPP = "whatsapp", _("WhatsApp")
SHOWROOM = "showroom", _("Showroom")
WEBSITE = "website", _("Website")
TIKTOK = "tiktok", _("TikTok")
INSTAGRAM = "instagram", _("Instagram")
X = "x", _("X")
FACEBOOK = "facebook", _("Facebook")
MOTORY = "motory", _("Motory")
INFLUENCERS = "influencers", _("Influencers")
YOUTUBE = "youtube", _("Youtube")
EMAIL = "email", _("Email")
class ContactStatus(models.TextChoices):
NEW = "new", _("New")
PENDING = "pending", _("Pending")
ASSIGNED = "assigned", _("Assigned")
CONTACTED = "contacted", _("Contacted")
ACCEPTED = "accepted", _("Accepted")
QUALIFIED = "qualified", _("Qualified")
CANCELED = "canceled", _("Canceled")
# class Contact(models.Model):
# AGE_RANGES = (
# ('18-30', '18 - 30'),
# ('31-40', '31 - 40'),
# ('41-50', '41 - 50'),
# ('51-60', '51 - 60'),
# ('61-70', '61 - 70'),
# ('71-80', '71 - 80'),
# ('81-90', '81 - 90'),
# )
#
# dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name="contacts")
# first_name = models.CharField(max_length=50, verbose_name=_("First Name"))
# last_name = models.CharField(max_length=50, verbose_name=_("Last Name"))
# age = models.CharField(choices=AGE_RANGES, max_length=20, verbose_name=_("Age"))
# gender = models.CharField(choices=[('m', _('Male')), ('f', _('Female'))], max_length=1, verbose_name=_("Gender"))
# phone_number = PhoneNumberField(region="SA", verbose_name=_("Phone Number"))
# email = models.EmailField(verbose_name=_("Email"))
# id_car_make = models.ForeignKey(CarMake, on_delete=models.DO_NOTHING, verbose_name=_("Make"))
# id_car_model = models.ForeignKey(CarModel, on_delete=models.DO_NOTHING, verbose_name=_("Model"))
# year = models.PositiveSmallIntegerField(verbose_name=_("Year"))
# status = models.CharField(choices=ContactStatus.choices, max_length=255, verbose_name=_("Status"), default=ContactStatus.NEW)
# created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created At"))
# updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated At"))
# enquiry_type = models.CharField(choices=[("quotation", _("Quote")),("testdrive", _("Test drive"))], max_length=50, verbose_name=_("Enquiry Type"))
# purchase_method = models.CharField(choices=[("c", _("Cash")),("f", _("Finance"))], max_length=1, verbose_name=_("Purchase Method"))
# source = models.CharField(max_length=100, choices=Sources.choices, verbose_name=_("Source"))
# salary = models.PositiveIntegerField(verbose_name=_("Salary"))
# obligations = models.PositiveIntegerField(verbose_name=_("Obligations"))
#
# class Meta:
# verbose_name = _("Contact")
# verbose_name_plural = _("Contacts")
#
# def __str__(self):
# return self.first_name + " " + self.last_name
class Customer(models.Model):
@ -733,42 +793,17 @@ class Customer(models.Model):
return f"{self.first_name} {self.middle_name} {self.last_name}"
class DealStatus(models.TextChoices):
NEW = "new", _("New")
PENDING = "pending", _("Pending")
CANCELED = "canceled", _("Canceled")
COMPLETED = "completed", _("Completed")
class Priority(models.TextChoices):
LOW = "low", _("Low")
MEDIUM = "medium", _("Medium")
HIGH = "high", _("High")
class DealSource(models.TextChoices):
REFERRALS = "referrals", _("Referrals")
WALK_IN = "walk_in", _("Walk In")
TOLL_FREE = "toll_free", _("Toll Free")
WHATSAPP = "whatsapp", _("Whatsapp")
SHOWROOMS = "showrooms", _("Showrooms")
WEBSITE = "website", _("Website")
OTHER = "other", _("Other")
class Opportunity(models.Model):
customer = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name="opportunities")
car = models.ForeignKey(Car, on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_("Car"))
deal_name = models.CharField(max_length=255, verbose_name=_("Deal Name"))
deal_value = models.DecimalField(max_digits=10, decimal_places=2, verbose_name=_("Deal Value"))
deal_status = models.CharField(max_length=50, choices=DealStatus.choices, default=DealStatus.NEW, verbose_name=_("Deal Status"))
priority = models.CharField(max_length=50, choices=Priority.choices, default=Priority.LOW, verbose_name=_("Priority"))
source = models.CharField(max_length=255, choices=DealSource.choices, default=DealSource.SHOWROOMS, verbose_name=_("Source"))
deal_status = models.CharField(max_length=20, choices=DealStatus.choices, default=DealStatus.NEW, verbose_name=_("Deal Status"))
priority = models.CharField(max_length=10, choices=Priority.choices, default=Priority.LOW, verbose_name=_("Priority"))
created_by = models.ForeignKey(Staff, on_delete=models.SET_NULL, null=True, related_name="deals_created")
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created At"))
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated At"))
assigned_to = models.ForeignKey(Staff, on_delete=models.CASCADE, related_name="deals_assigned")
assigned_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Assigned At"))
class Meta:
verbose_name = _("Opportunity")
@ -781,7 +816,7 @@ class Opportunity(models.Model):
class Notes(models.Model):
opportunity = models.ForeignKey(Opportunity, on_delete=models.CASCADE, related_name="notes")
note = models.TextField(verbose_name=_("Note"))
created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, related_name="notes_created")
created_by = models.ForeignKey(User, on_delete=models.DO_NOTHING, related_name="notes_created")
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created At"))
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated At"))
@ -790,13 +825,6 @@ class Notes(models.Model):
verbose_name_plural = _("Notes")
class ActionChoices(models.TextChoices):
CREATE = "create", _("Create")
UPDATE = "update", _("Update")
DELETE = "delete", _("Delete")
STATUS_CHANGE = "status_change", _("Status Change")
class OpportunityLog(models.Model):
opportunity = models.ForeignKey(Opportunity, on_delete=models.CASCADE, related_name="logs")
action = models.CharField(max_length=50, choices=ActionChoices.choices, verbose_name=_("Action"))
@ -830,6 +858,35 @@ class Notification(models.Model):
return self.message
class Vendor(models.Model, LocalizedNameMixin):
dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name="vendors")
crn = models.CharField(
max_length=10, unique=True, verbose_name=_("Commercial Registration Number")
)
vrn = models.CharField(
max_length=15, unique=True, verbose_name=_("VAT Registration Number")
)
arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name"))
name = models.CharField(max_length=255, verbose_name=_("English Name"))
contact_person = models.CharField(max_length=100, verbose_name=_("Contact Person"))
phone_number = PhoneNumberField(region="SA", verbose_name=_("Phone Number"))
email = models.EmailField(max_length=255, verbose_name=_("Email Address"))
address = models.CharField(
max_length=200, blank=True, null=True, verbose_name=_("Address")
)
logo = models.ImageField(
upload_to="logos/vendors", blank=True, null=True, verbose_name=_("Logo")
)
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created At"))
class Meta:
verbose_name = _("Vendor")
verbose_name_plural = _("Vendors")
def __str__(self):
return self.name
class Organization(models.Model, LocalizedNameMixin):
dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name='organizations')
name = models.CharField(max_length=255, verbose_name=_("Name"))

View File

@ -6,6 +6,7 @@ from django_ledger.io import roles
from django_ledger.models import EntityModel,AccountModel,ItemModel,ItemModelAbstract,UnitOfMeasureModel, VendorModel
from . import models
from .models import OpportunityLog
from .utils import get_dealer_from_instance
User = get_user_model()
@ -319,14 +320,14 @@ def create_item_model(sender, instance, created, **kwargs):
#
#
# # update price - CarFinance
# @receiver(post_save, sender=CarFinance)
# def update_item_model_cost(sender, instance, created, **kwargs):
#
# ItemModel.objects.filter(item_id=instance.car.vin).update(
# inventory_received_value=instance.cost_price,
# default_amount=instance.cost_price,
# )
# print(f"Inventory item updated with CarFinance data for Car: {instance.car}")
@receiver(post_save, sender=models.CarFinance)
def update_item_model_cost(sender, instance, created, **kwargs):
ItemModel.objects.filter(item_id=instance.car.vin).update(
inventory_received_value=instance.cost_price,
default_amount=instance.cost_price,
)
print(f"Inventory item updated with CarFinance data for Car: {instance.car}")

View File

@ -34,7 +34,7 @@ urlpatterns = [
path('dealers/activity/', views.UserActivityLogListView.as_view(), name='dealer_activity'),
# path('dealers/<int:pk>/delete/', views.DealerDeleteView.as_view(), name='dealer_delete'),
# Customer URLs
# CRM URLs
path('customers/', views.CustomerListView.as_view(), name='customer_list'),
path('customers/<int:pk>/', views.CustomerDetailView.as_view(), name='customer_detail'),
path('customers/create/', views.CustomerCreateView.as_view(), name='customer_create'),
@ -42,15 +42,17 @@ urlpatterns = [
path('customers/<int:pk>/delete/', views.delete_customer, name='customer_delete'),
path('customers/<int:pk>/create_lead/', views.create_lead, name='create_lead'),
path('customers/<int:customer_id>/opportunities/create/', views.OpportunityCreateView.as_view(), name='create_opportunity'),
# CRM URLs
path('opportunities/<int:pk>/', views.OpportunityDetailView.as_view(), name='opportunity_detail'),
path('opportunities/<int:pk>/edit/', views.OpportunityUpdateView.as_view(), name='update_opportunity'),
path('opportunities/', views.OpportunityListView.as_view(), name='opportunity_list'),
path('opportunities/<int:pk>/delete/', views.delete_opportunity, name='delete_opportunity'),
path('opportunities/<int:pk>/logs/', views.OpportunityLogsView.as_view(), name='opportunity_logs'),
path('notifications/', views.NotificationListView.as_view(), name='notifications_history'),
path('fetch_notifications/', views.fetch_notifications, name='fetch_notifications'),
path('notifications/<int:pk>/mark_as_read/', views.mark_notification_as_read, name='mark_notification_as_read'),
path('crm/leads/', views.LeadListView.as_view(), name='lead_list'),
path('crm/opportunities/<int:pk>/', views.OpportunityDetailView.as_view(), name='opportunity_detail'),
path('crm/opportunities/<int:pk>/edit/', views.OpportunityUpdateView.as_view(), name='update_opportunity'),
path('crm/opportunities/', views.OpportunityListView.as_view(), name='opportunity_list'),
path('crm/opportunities/<int:pk>/delete/', views.delete_opportunity, name='delete_opportunity'),
path('crm/opportunities/<int:pk>/logs/', views.OpportunityLogsView.as_view(), name='opportunity_logs'),
path('crm/notifications/', views.NotificationListView.as_view(), name='notifications_history'),
path('crm/fetch_notifications/', views.fetch_notifications, name='fetch_notifications'),
path('crm/notifications/<int:pk>/mark_as_read/', views.mark_notification_as_read, name='mark_notification_as_read'),
#Vendor URLs
path('vendors', views.VendorListView.as_view(), name='vendor_list'),
@ -71,8 +73,8 @@ urlpatterns = [
path('cars/add/', views.CarCreateView.as_view(), name='car_add'),
path('ajax/', views.AjaxHandlerView.as_view(), name='ajax_handler'),
path('cars/<int:car_pk>/add-color/', views.CarColorCreate.as_view(), name='add_color'),
path('car/<int:car_pk>/location/add/', views.CarLocationCreateView.as_view(), name='add_car_location'),
path('car/<int:pk>/location/update/', views.CarLocationUpdateView.as_view(), name='transfer'),
path('cars/<int:car_pk>/location/add/', views.CarLocationCreateView.as_view(), name='add_car_location'),
path('cars/<int:pk>/location/update/', views.CarLocationUpdateView.as_view(), name='transfer'),
# path('cars/<int:car_pk>/colors/<int:pk>/update/',views.CarColorUpdateView.as_view(),name='color_update'),
path('cars/reserve/<int:car_id>/', views.reserve_car_view, name='reserve_car'),

View File

@ -61,8 +61,15 @@ def send_email(from_, to_, subject, message):
def get_user_type(request):
dealer = ""
if hasattr(request.user, 'dealer'):
dealer = request.user.dealer
elif hasattr(request.user, 'staff'):
dealer = request.user.staff.dealer
return dealer
return dealer
def get_dealer_from_instance(instance):
if instance.dealer.staff:
return instance.dealer
else:
return instance.dealer

View File

@ -1,3 +1,4 @@
from django.core.exceptions import ObjectDoesNotExist
from django.core.paginator import Paginator
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
@ -180,6 +181,7 @@ class TestView(TemplateView):
template_name = "test.html"
class AccountingDashboard(LoginRequiredMixin, TemplateView):
template_name = "dashboards/accounting.html"
@ -233,7 +235,8 @@ class CarCreateView(LoginRequiredMixin, CreateView):
return reverse("inventory_stats")
def form_valid(self, form):
form.instance.dealer = self.request.user.dealer
dealer = get_user_type(self.request)
form.instance.dealer = dealer
form.save()
messages.success(self.request, "Car saved successfully.")
return super().form_valid(form)
@ -257,10 +260,13 @@ class AjaxHandlerView(LoginRequiredMixin, View):
def decode_vin(self, request):
vin_no = request.GET.get("vin_no")
car_existed = models.Car.objects.filter(vin=vin_no).exists()
if car_existed:
return JsonResponse({"error": _("VIN number exists")}, status=400)
if not vin_no or len(vin_no.strip()) != 17:
return JsonResponse(
{"success": False, "error": "Invalid VIN number provided."}, status=400
)
return JsonResponse({"success": False, "error": "Invalid VIN number provided."}, status=400)
vin_no = vin_no.strip()
vin_data = {}
@ -727,7 +733,7 @@ class CustomerListView(LoginRequiredMixin, ListView):
query = self.request.GET.get("q")
dealer = get_user_type(self.request)
customers = models.Customer.objects.filter(dealer=dealer)
customers = models.Customer.objects.filter(dealer=dealer, is_lead=False)
if query:
customers = customers.filter(
@ -761,7 +767,7 @@ class CustomerCreateView(
success_message = _("Customer created successfully.")
def form_valid(self, form):
form.instance.dealer = self.request.user.dealer
form.instance.dealer = get_user_type(self.request)
return super().form_valid(form)
@ -2236,6 +2242,7 @@ def send_email_view(request, pk):
)
# CRM RELATED VIEWS
def create_lead(request, pk):
customer = get_object_or_404(models.Customer, pk=pk)
if customer.is_lead:
@ -2247,6 +2254,31 @@ def create_lead(request, pk):
return redirect(reverse("customer_detail", kwargs={"pk": customer.pk}))
class LeadListView(ListView):
model = models.Customer
template_name = "crm/lead_list.html"
context_object_name ='customers'
def get_queryset(self):
query = self.request.GET.get("q")
dealer = get_user_type(self.request)
customers = models.Customer.objects.filter(dealer=dealer, is_lead=True)
if query:
customers = customers.filter(
Q(national_id__icontains=query) |
Q(first_name__icontains=query) |
Q(last_name__icontains=query)
)
return customers
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["query"] = self.request.GET.get("q", "")
return context
class OpportunityCreateView(CreateView):
model = models.Opportunity
form_class = forms.OpportunityForm

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,7 @@
aiohappyeyeballs==2.4.4
aiohttp==3.11.11
aiohttp-retry==2.8.3
aiosignal==1.3.2
alabaster==1.0.0
annotated-types==0.7.0
anyio==4.7.0
@ -45,6 +49,7 @@ django-phonenumber-field==8.0.0
django-prometheus==2.3.1
django-sekizai==4.1.0
django-silk==5.3.2
django-sms==0.7.0
django-sslserver==0.22
django-tables2==2.7.4
django-treebeard==4.7.1
@ -58,6 +63,7 @@ et_xmlfile==2.0.0
Faker==33.1.0
Flask==3.1.0
fonttools==4.55.3
frozenlist==1.5.0
gprof2dot==2024.6.6
graphqlclient==0.2.4
h11==0.14.0
@ -75,13 +81,16 @@ joblib==1.4.2
ledger==1.0.1
lxml==5.3.0
Markdown==3.7
markdown-it-py==3.0.0
MarkupSafe==3.0.2
marshmallow==3.23.2
mccabe==0.7.0
mdurl==0.1.2
MouseInfo==0.1.3
multidict==6.1.0
mypy-extensions==1.0.0
newrelic==10.4.0
libquadmath==2.2.1
numpy==2.2.1
oauthlib==3.2.2
ofxtools==0.9.5
openai==1.58.1
@ -94,6 +103,7 @@ phonenumbers==8.13.52
pillow==11.0.0
platformdirs==4.3.6
prometheus_client==0.21.1
propcache==0.2.1
psycopg==3.2.3
psycopg-binary==3.2.3
psycopg-c==3.2.3
@ -139,8 +149,8 @@ requests==2.32.3
requests-oauthlib==2.0.0
rich==13.9.4
rubicon-objc==0.4.9
libomp runtime library==1.6.0
libquadmath==1.14.1
scikit-learn==1.6.0
scipy==1.14.1
selenium==4.27.1
six==1.17.0
sniffio==1.3.1
@ -157,6 +167,7 @@ tomlkit==0.13.2
tqdm==4.67.1
trio==0.28.0
trio-websocket==0.11.1
twilio==9.4.1
typing-inspect==0.9.0
typing_extensions==4.12.2
tzdata==2024.2
@ -174,4 +185,5 @@ Werkzeug==3.1.3
wikipedia==1.4.0
wsproto==1.2.0
xmlsec==1.3.14
yarl==1.18.3
zopfli==0.2.3.post1

BIN
static/.DS_Store vendored

Binary file not shown.

View File

@ -2,9 +2,9 @@
width: 64px;
height: 16px;
padding: 2px 4px;
border-radius: 1px;
border-radius: 3px;
border: 1px outset #CBD0DDFd;
text-align: center;
vertical-align: middle;
line-height: 22px;
/*vertical-align: middle;*/
/*line-height: 22px;*/
}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.0 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.7 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,185 @@
{% extends 'base.html' %}
{% load i18n %}
{% load static %}
{% block title %}{{ _('Leads')|capfirst }}{% endblock title %}
{% block vendors %}<a class="nav-link active">{{ _("Leads")|capfirst }}</a>{% endblock %}
{% block content %}
<div class="container">
<h2 class="mb-4">{{ _("Leads")|capfirst }}</h2>
<div class="row g-3 justify-content-between mb-4">
<div class="col-auto">
<div class="d-md-flex justify-content-between">
<div>
<a href="{% url 'customer_create' %}" class="btn btn-primary me-4"><span class="fas fa-plus me-2"></span>{{ _("Add Customer") }}</a>
</div>
</div>
</div>
<div class="col-auto">
<div class="d-flex">
<div class="search-box me-2">
<form method="get" class="d-inline-block position-relative">
<div class="input-group">
<button type="submit" class="btn btn-phoenix-primary"><span class="fas fa-search search-box-icon"></span></button>
<input name="q" class="form-control search-input search" type="search" placeholder="{{ _('Enter customer name') }}" value="{{ request.GET.q }}"/>
{% if request.GET.q %}
<a href="{% url request.resolver_match.view_name %}" class="btn btn-close"></a>
</div>
{% endif %}
</form>
</div>
</div>
</div>
</div>
{% if page_obj.object_list %}
<div class="table-responsive scrollbar mx-n1 px-1">
<table class="table fs-9 mb-0 border-top border-translucent">
<thead>
<tr>
<th></th>
<th class="sort white-space-nowrap align-middle text-uppercase ps-0" scope="col" data-sort="name" style="width:25%;">{{ _("Name")|capfirst }}</th>
<th class="sort align-middle ps-4 pe-5 text-uppercase border-end border-translucent" scope="col" data-sort="email" style="width:15%;">
<div class="d-inline-flex flex-center">
<div class="d-flex align-items-center px-1 py-1 bg-success-subtle rounded me-2"><span class="text-success-dark" data-feather="mail"></span></div><span>{{ _("email")|capfirst }}</span>
</div>
</th>
<th class="sort align-middle ps-4 pe-5 text-uppercase border-end border-translucent" scope="col" data-sort="phone" style="width:15%; min-width: 180px;">
<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>{{ _("Phone Number") }}</span>
</div>
</th>
<th class="sort align-middle ps-4 pe-5 text-uppercase border-end border-translucent" scope="col" data-sort="contact" style="width:15%;">
<div class="d-inline-flex flex-center">
<div class="d-flex align-items-center px-1 py-1 bg-info-subtle rounded me-2"><span class="text-info-dark" data-feather="user"></span></div><span>{{ _("National ID")|capfirst }}</span>
</div>
</th>
<th class="sort align-middle ps-4 pe-5 text-uppercase border-end border-translucent" scope="col" data-sort="company" 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="grid"></span></div><span>{{ _("Address")|capfirst }}</span>
</div>
</th>
<th class="sort align-middle ps-4 pe-5 text-uppercase" scope="col" data-sort="date" style="width:15%;">
{{ _("Create date") }}</th>
<th class="sort text-end align-middle pe-0 ps-4" scope="col"></th>
</tr>
</thead>
<tbody class="list" id="lead-tables-body">
{% for customer in customers %}
<!-- Delete Modal -->
<div class="modal fade" id="deleteModal"
data-bs-backdrop="static"
data-bs-keyboard="false"
tabindex="-1"
aria-labelledby="deleteModalLabel"
aria-hidden="true">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="deleteModalLabel">
{% trans "Delete Customer" %}
<span data-feather="alert-circle"></span>
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body text-center">
<p class="mb-0 text-danger fw-bold">
{% trans "Are you sure you want to delete this customer?" %}
</p>
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">
{% trans "No" %}
</button>
<a type="button" class="btn btn-danger btn-sm" href="{% url 'customer_delete' customer.id %}">
{% trans "Yes" %}
</a>
</div>
</div>
</div>
</div>
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
<td>
<span class="badge badge-phoenix fs-10 badge-phoenix-success">
<span class="badge-label">{{ _("Lead") }}</span>
<span class="ms-1" data-feather="check" style="height:13px;width:13px;"></span>
</span>
</td>
<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 'customer_detail' customer.id %}">{{ customer.first_name }} {{ customer.middle_name }} {{ customer.last_name }}</a>
<div class="d-flex align-items-center">
</div>
</div>
</div>
</td>
<td class="email align-middle white-space-nowrap fw-semibold ps-4 border-end border-translucent"><a class="text-body-highlight" href="">{{ customer.email }}</a></td>
<td class="phone align-middle white-space-nowrap fw-semibold ps-4 border-end border-translucent"><a class="text-body-highlight" href="tel:{{ customer.phone_number }}">{{ customer.phone_number }}</a></td>
<td class="contact align-middle white-space-nowrap ps-4 border-end border-translucent fw-semibold text-body-highlight">{{ customer.national_id }}</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">
{{ customer.address }}</td>
<td class="date align-middle white-space-nowrap text-body-tertiary text-opacity-85 ps-4 text-body-tertiary">{{ customer.created|date }}</td>
<td class="align-middle white-space-nowrap text-end pe-0 ps-4">
<div class="btn-reveal-trigger position-static">
<button class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10" type="button" data-bs-toggle="dropdown" data-boundary="window" aria-haspopup="true" aria-expanded="false" data-bs-reference="parent"><span class="fas fa-ellipsis-h fs-10"></span></button>
<div class="dropdown-menu dropdown-menu-end py-2">
<a href="{% url 'customer_update' customer.id %}" class="dropdown-item text-success-dark">{% trans "Edit" %}</a>
<a href="{% url 'create_opportunity' customer.pk %}" class="dropdown-item text-warning-dark">{{_("Opportunity")}}</a>
<div class="dropdown-divider"></div><button class="dropdown-item text-danger" data-bs-toggle="modal" data-bs-target="#deleteModal">{% trans "Delete" %}</button>
</div>
</div>
</td>
</tr>
{% endfor %}
</tbody>
{% endif %}
</table>
</div>
<div class="row align-items-center justify-content-end py-4 pe-0 fs-9">
<!-- Optional: Pagination -->
{% if is_paginated %}
<nav aria-label="Page navigation">
<ul class="pagination pagination-sm justify-content-center">
{% if page_obj.has_previous %}
<li class="page-item py-0">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}" aria-label="Previous">
<span aria-hidden="true">&laquo;</span>
</a>
</li>
{% else %}
<li class="page-item disabled">
<a class="page-link" href="#" aria-label="Previous">
<span aria-hidden="true">&laquo;</span>
</a>
</li>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<li class="page-item active"><a class="page-link" href="?page={{ num }}">{{ num }}</a></li>
{% else %}
<li class="page-item"><a class="page-link" href="?page={{ num }}">{{ num }}</a></li>
{% endif %}
{% endfor %} {% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}" aria-label="Next">
<span aria-hidden="true">&raquo;</span>
</a>
</li>
{% else %}
<li class="page-item disabled">
<a class="page-link" href="#" aria-label="Next">
<span aria-hidden="true">&raquo;</span>
</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
</div>
</div>
{% endblock %}

View File

@ -65,7 +65,7 @@
<th class="sort text-end align-middle pe-0 ps-4" scope="col"></th>
</tr>
</thead>
<tbody class="list" id="leal-tables-body">
<tbody class="list" id="lead-tables-body">
{% for customer in customers %}
<!-- Delete Modal -->
@ -102,12 +102,7 @@
</div>
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
<td>
{% if customer.is_lead %}
<span class="badge badge-phoenix fs-10 badge-phoenix-success">
<span class="badge-label">{{ _("Lead") }}</span>
<span class="ms-1" data-feather="check" style="height:13px;width:13px;"></span>
</span>
{% endif %}
</td>
<td class="name align-middle white-space-nowrap ps-0">
<div class="d-flex align-items-center">
@ -128,11 +123,7 @@
<button class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10" type="button" data-bs-toggle="dropdown" data-boundary="window" aria-haspopup="true" aria-expanded="false" data-bs-reference="parent"><span class="fas fa-ellipsis-h fs-10"></span></button>
<div class="dropdown-menu dropdown-menu-end py-2">
<a href="{% url 'customer_update' customer.id %}" class="dropdown-item text-success-dark">{% trans "Edit" %}</a>
{% if customer.is_lead %}
<a href="{% url 'create_opportunity' customer.pk %}" class="dropdown-item text-warning-dark">{{_("Opportunity")}}</a>
{% else %}
<a href="{% url 'create_lead' customer.pk %}" class="dropdown-item text-success-dark">{{ _("Mark as Lead")}}</a>
{% endif %}
<a href="{% url 'create_lead' customer.pk %}" class="dropdown-item text-success-dark">{{ _("Mark as Lead")}}</a>
<div class="dropdown-divider"></div><button class="dropdown-item text-danger" data-bs-toggle="modal" data-bs-target="#deleteModal">{% trans "Delete" %}</button>
</div>
</div>

View File

@ -38,7 +38,8 @@
{% if dealer.logo %}
<img src="{{ dealer.logo.url }}" alt="{{ dealer.get_local_name }}" class="rounded-circle" style="max-width: 150px;">
{% else %}
<img src="{% static 'images/logos/logo.png' %}" alt="{{ dealer.get_local_name }}" class="rounded-circle" style="max-width: 150px;">
<span class="rounded-circle feather feather-user text-body-tertiary" style="max-width: 150px;"></span>
<img src="{% static 'images/logos/logo.png' %}" alt="{{ dealer.get_local_name }}" class="" style="max-width: 150px;">
{% endif %}
</label>
</div>
@ -65,7 +66,6 @@
{% else %}
<span class="badge badge-phoenix fs-10 badge-phoenix-danger"><span class="badge-label">{% trans 'Expired' %}</span><span class="ms-1" data-feather="times" style="height:12.8px;width:12.8px;"></span></span>
{% endif %}
</div>
</div>
</div>

View File

@ -77,11 +77,7 @@
</ul>
</div>
</div>
<!-- parent pages-->
<!-- parent pages-->
<!-- parent pages-->
<div class="nav-item-wrapper">
<a class="nav-link dropdown-indicator label-1" href="#nv-sales" role="button" data-bs-toggle="collapse" aria-expanded="false" aria-controls="nv-sales">
<div class="d-flex align-items-center">
@ -210,8 +206,7 @@
data-bs-toggle="tooltip"
data-bs-placement="left"
data-bs-title="Switch theme"
style="height: 32px; width: 32px;"
>
style="height: 32px; width: 32px;">
<span class="icon" data-feather="moon"></span>
</label>
<label
@ -221,8 +216,7 @@
data-bs-toggle="tooltip"
data-bs-placement="left"
data-bs-title="Switch theme"
style="height: 32px; width: 32px;"
>
style="height: 32px; width: 32px;">
<span class="icon" data-feather="sun"></span>
</label>
</div>
@ -261,6 +255,10 @@
<div class="avatar avatar-l">
{% if user.dealer.logo %}
<img class="rounded-circle" src="{{ user.dealer.logo.url }}" alt="" />
{% elif user.staff.dealer.logo %}
<img class="rounded-circle" src="{{ user.staff.dealer.logo.url }}" alt="" />
{% else %}
<span class="fa fa-user text-body-tertiary" style="width: 32px;"></span>
{% endif %}
</div>
</a>
@ -268,9 +266,14 @@
<div class="card position-relative border-0">
<div class="card-body p-0">
<div class="text-center pt-4 pb-3">
<div class="avatar avatar-xl">
{% if user.dealer.logo %}
{% if user.dealer.logo %}
<img class="rounded-circle" src="{{ user.dealer.logo.url }}" alt="" />
{% elif user.staff.dealer.logo %}
<img class="rounded-circle" src="{{ user.staff.dealer.logo.url }}" alt="" />
{% else %}
<span class="fa fa-user text-body-tertiary" style="width: 32px;"></span>
{% endif %}
</div>
<h6 class="mt-2 text-body-emphasis">{{ user.dealer.get_local_name }}</h6>
@ -294,18 +297,18 @@
<a class="nav-link px-3 d-block" href="{% url 'dealer_activity' %}"> <span class="me-2 text-body align-bottom" data-feather="lock"></span>{{ _("Activity") }}</a>
</li>
<li class="nav-item">
<a class="nav-link px-3 d-block" href="#!"> <span class="me-2 text-body align-bottom" data-feather="settings"></span>Settings &amp; Privacy </a>
<a class="nav-link px-3 d-block" href=""> <span class="me-2 text-body align-bottom" data-feather="settings"></span>Settings &amp; Privacy </a>
</li>
<li class="nav-item">
<a class="nav-link px-3 d-block" href="#!"> <span class="me-2 text-body align-bottom" data-feather="help-circle"></span>Help Center</a>
<a class="nav-link px-3 d-block" href=""> <span class="me-2 text-body align-bottom" data-feather="help-circle"></span>Help Center</a>
</li>
<li class="nav-item"><a class="nav-link px-3 d-block" href="#!"> Language</a></li>
<li class="nav-item"><a class="nav-link px-3 d-block" href=""> Language</a></li>
</ul>
</div>
<div class="card-footer p-0 border-top border-translucent">
<ul class="nav d-flex flex-column my-3">
<li class="nav-item">
<a class="nav-link px-3 d-block" href="#!"> <span class="me-2 text-body align-bottom" data-feather="user-plus"></span>Add another account</a>
<a class="nav-link px-3 d-block" href=""> <span class="me-2 text-body align-bottom" data-feather="user-plus"></span>Add another account</a>
</li>
</ul>
<hr />
@ -313,7 +316,7 @@
<a class="btn btn-phoenix-secondary d-flex flex-center w-100" href="{% url 'account_logout' %}"> <span class="me-2" data-feather="log-out"> </span>{% trans 'Sign Out' %}</a>
</div>
<div class="my-2 text-center fw-bold fs-10 text-body-quaternary">
<a class="text-body-quaternary me-1" href="#!">Privacy policy</a>&bull;<a class="text-body-quaternary mx-1" href="#!">Terms</a>&bull;<a class="text-body-quaternary ms-1" href="#!">Cookies</a>
<a class="text-body-quaternary me-1" href="">Privacy policy</a>&bull;<a class="text-body-quaternary mx-1" href="">Terms</a>&bull;<a class="text-body-quaternary ms-1" href="">Cookies</a>
</div>
{% else %}
<div class="px-3">

View File

@ -235,7 +235,7 @@
<tbody>
{% for reservation in car.reservations.all %}
<tr>
<td>{{ reservation.reserved_by.dealer.staff }}</td>
<td>{{ reservation.reserved_by.dealer }}</td>
<td>{{ reservation.reserved_until }}</td>
<td>
{% if reservation.is_active %}

View File

@ -206,30 +206,23 @@
</div>
<!-- Receiving Date Field -->
<div class="col-lg-4 col-xl-3">
<div class="card h-100">
<div class="card-body">
<div class="form-group">
<label for="{{ form.receiving_date.id_for_label }}" class="form-label">
{% trans 'Receiving Date' %}:
</label>
{{ form.receiving_date }}
<input
class="form-control form-control-sm datetimepicker flatpickr-input"
id="{{ form.receiving_date.id_for_label }}"
type="text"
placeholder="dd/mm/yyyy hour : minute"
data-options='{"enableTime":true,"dateFormat":"d/m/y H:i","disableMobile":false}'
readonly="readonly"
/>
{% if form.receiving_date.errors %}
<div class="text-danger small">
{{ form.receiving_date.errors|striptags }}
</div>
{% endif %}
</div>
</div>
</div>
</div>
<div class="card h-100 border-1 rounded shadow">
<div class="card-body">
<div class="form-group">
<label for="{{ form.receiving_date.id_for_label }}"
class="form-label">
{% trans 'Receiving Date' %}:
</label>
{{ form.receiving_date|add_class:"form-control form-control-sm" }}
{% if form.receiving_date.errors %}
<div class="text-danger small">
{{ form.receiving_date.errors|striptags }}
</div>
{% endif %}
</div>
</div>
</div>
</div>
<!-- Remarks Card -->
<div class="col-lg-4 col-xl-3">
<div class="card h-100">
@ -369,7 +362,7 @@
async function decodeVin() {
const vinNumber = vinInput.value.trim();
if (vinNumber.length !== 17) {
notify("error", "{% trans 'Please enter a valid VIN.' %}");
Swal.fire("error", "{% trans 'Please enter a valid VIN.' %}");
/*alert("{% trans 'Please enter a valid VIN.' %}");*/
return;
}
@ -387,12 +380,12 @@
await updateFields(data.data);
} else {
hideLoading();
notify("error", data.error);
Swal.fire("{% trans 'error' %}", data.error);
}
} catch (error) {
console.error("Error decoding VIN:", error);
hideLoading();
notify("error", "{% trans 'An error occurred while decoding the VIN.' %}");
Swal.fire("error", "{% trans 'An error occurred while decoding the VIN.' %}");
}
}

View File

@ -16,12 +16,12 @@
<div class="card border h-100 w-100 overflow-hidden">
<div class="bg-holder d-block bg-card" style="background-image:url({% static 'images/spot-illustrations/32.png' %});background-position: top right;">
</div>
<div class="d-dark-none">
<div class="bg-holder d-none d-sm-block d-xl-none d-xxl-block bg-card" style="background-image:url({% static 'images/spot-illustrations/dark_21.png' %});background-position: bottom right; background-size: auto;">
<div class="d-dark-none me-5">
<div class="bg-holder d-none d-sm-block d-xl-none d-xxl-block bg-card" style="background-image:url({% static 'images/spot-illustrations/dark_21.png' %}); background-position: bottom right; background-size: auto;">
</div>
</div>
<div class="d-light-none">
<div class="bg-holder d-none d-sm-block d-xl-none d-xxl-block bg-card" style="background-image:url({% static 'images/spot-illustrations/21.png' %});background-position: bottom right; background-size: auto;">
<div class="d-light-none me-5">
<div class="bg-holder d-none d-sm-block d-xl-none d-xxl-block bg-card" style="background-image:url({% static 'images/spot-illustrations/21.png' %}); background-position: bottom right; background-size: auto;">
</div>
</div>
<div class="card-body px-lg-5 position-relative">
@ -30,7 +30,9 @@
<div class="col-12 col-sm-auto ">
<div class="avatar avatar-3xl avatar-bordered mb-3">
{% if cars.first.id_car_make.logo %}
<img class="rounded-circle" src="{{ cars.first.id_car_make.logo.url }}" alt="">
<img class="rounded-circle" src="{{ cars.first.id_car_make.logo.url }}" alt="">
{% else %}
<img class="rounded-circle" src="{% static 'images/logos/car_make/sedan.svg' %}" alt="">
{% endif %}
</div>
</div>
@ -76,12 +78,12 @@
{% if car.colors.exists %}
<td class="align-middle white-space-nowrap text-body fs-9 text-start">
<div class="d-flex flex-column align-items-center">
<span class="color-div" style="background: linear-gradient(90deg, rgba({{ car.colors.first.exterior.rgb }},1) 40%, rgba({{ car.colors.first.exterior.rgb }},0.35) 100%);" title="{{ car.colors.first.exterior.get_local_name }}"></span><span>{{ car.colors.first.exterior.get_local_name }}</span>
<span class="color-div" style="background: linear-gradient(90deg, rgba({{ car.colors.first.exterior.rgb }},1) 10%, rgba({{ car.colors.first.exterior.rgb }},0.10) 100%);" title="{{ car.colors.first.exterior.get_local_name }}"></span><span>{{ car.colors.first.exterior.get_local_name }}</span>
</div>
</td>
<td class="align-middle white-space-nowrap text-body fs-9 text-start">
<div class="d-flex flex-column align-items-center">
<span class="color-div" style="background: linear-gradient(90deg, rgba({{ car.colors.first.interior.rgb }},1) 40%, rgba({{ car.colors.first.interior.rgb }},0.35) 100%);" title="{{ car.colors.first.interior.get_local_name }}"></span><span>{{ car.colors.first.interior.get_local_name }}</span>
<span class="color-div" style="background: linear-gradient(90deg, rgba({{ car.colors.first.interior.rgb }},1) 10%, rgba({{ car.colors.first.interior.rgb }},0.10) 100%);" title="{{ car.colors.first.interior.get_local_name }}"></span><span>{{ car.colors.first.interior.get_local_name }}</span>
</div>
</td>
{% else %}

View File

@ -22,31 +22,31 @@
</thead>
<tbody class="list">
{% for estimate in estimates %}
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
<td class="align-middle product white-space-nowrap py-0">{{ estimate.estimate_number }}</td>
<td class="align-middle product white-space-nowrap">{{ estimate.customer }}</td>
<td class="align-middle product white-space-nowrap">
{% if estimate.status == 'draft' %}
<span class="badge bg-warning text-light">{% trans "Draft" %}</span>
<span class="badge badge-phoenix badge-phoenix-warning">{% trans "Draft" %}</span>
{% elif estimate.status == 'in_review' %}
<span class="badge bg-info text-light">{% trans "In Review" %}</span>
<span class="badge badge-phoenix badge-phoenix-info">{% trans "In Review" %}</span>
{% elif estimate.status == 'approved' %}
<span class="badge bg-success text-light">{% trans "Approved" %}</span>
<span class="badge badge-phoenix badge-phoenix-success">{% trans "Approved" %}</span>
{% elif estimate.status == 'declined' %}
<span class="badge bg-danger text-light">{% trans "Declined" %}</span>
<span class="badge badge-phoenix badge-phoenix-danger">{% trans "Declined" %}</span>
{% elif estimate.status == 'canceled' %}
<span class="badge bg-danger text-light">{% trans "Canceled" %}</span>
<span class="badge badge-phoenix badge-phoenix-danger">{% trans "Canceled" %}</span>
{% elif estimate.status == 'completed' %}
<span class="badge bg-success text-light">{% trans "Completed" %}</span>
<span class="badge badge-phoenix badge-phoenix-success">{% trans "Completed" %}</span>
{% elif estimate.status == 'void' %}
<span class="badge bg-secondary text-light">{% trans "Void" %}</span>
<span class="badge badge-phoenix badge-phoenix-secondary">{% trans "Void" %}</span>
{% endif %}
</td>
<td class="align-middle product white-space-nowrap">{{ estimate.get_status_action_date }}</td>
<td class="align-middle product white-space-nowrap">{{ estimate.created }}</td>
<td class="text-center">
<a href="{% url 'estimate_detail' estimate.pk %}"
class="btn btn-sm btn-success">
class="btn btn-sm btn-phoenix-success">
{% trans "view" %}
</a>
</td>

View File

@ -56,7 +56,7 @@
<td class="align-middle product white-space-nowrap">{{ invoice.created }}</td>
<td class="text-center">
<a href="{% url 'invoice_detail' invoice.pk %}"
class="btn btn-sm btn-success">
class="btn btn-sm btn-phoenix-success">
{% trans "View" %}
</a>
</td>

View File

@ -1,11 +1,11 @@
{% extends "base.html" %}
{% load i18n static %}
{% block title %}{{ _("Tranactions") }}{% endblock title %}
{% block title %}{{ _("Transactions") }}{% endblock title %}
{% block content %}
<div class="container mt-4">
<h3 class="text-center">{% trans "Tranactions" %}</h3>
<h3 class="text-center">{% trans "Transactions" %}</h3>
<div class="mx-n4 px-4 mx-lg-n6 px-lg-6 bg-body-emphasis pt-7 border-y">
<div class="table-responsive mx-n1 px-1 scrollbar">
@ -34,7 +34,7 @@
</tr>
{% empty %}
<tr>
<td colspan="6" class="text-center">{% trans "No Tranactions Found" %}</td>
<td colspan="6" class="text-center">{% trans "No Transactions Found" %}</td>
</tr>
{% endfor %}
</tbody>

53
test_sms.py Normal file
View File

@ -0,0 +1,53 @@
# from django.conf import settings
# settings.configure()
#
# from sms import Message
#
# message = Message(
# 'Here is the message',
# '+12065550100',
# ['+441134960000']
# )
# print(message)
from twilio.rest import Client
account_sid = 'AC988fcc2cce8ae4f36ffc58bf897bcb1d'
auth_token = '8e5a85e1fe8d48d73b8247bdee3ec65a'
client = Client(account_sid, auth_token)
message = client.messages.create(
from_='+12313103856', #twilio phone number
body='أول رسالة من هيكل .. السلام عليكم',
to='+966535521547',
)
print(message.sid)
# from twilio.rest import Client
#
# account_sid = 'AC988fcc2cce8ae4f36ffc58bf897bcb1d'
# auth_token = '8e5a85e1fe8d48d73b8247bdee3ec65a'
# client = Client(account_sid, auth_token)
#
# message = client.messages.create(
# from_='+12313103856',
# to='+966535521547'
# )
#
# print(message.sid)
# from twilio.rest import Client
#
# account_sid = settings.TWILIO_ACCOUNT_SID
# auth_token = settings.TWILIO_AUTH_TOKEN
# client = Client(account_sid, auth_token)
#
# verification_check = client.verify \
# .v2 \
# .services('VA4a22a04749e04800cc89f687df8732d3') \
# .verification_checks \
# .create(to='+966535521547', code='[Code]')
#
# print(verification_check.status)