This commit is contained in:
Faheedkhan 2025-07-02 15:00:31 +03:00
commit 4f0b199dae
24 changed files with 7176 additions and 693 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@ -14,9 +14,10 @@
</component> </component>
<component name="NewModuleRootManager"> <component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$"> <content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.venv" />
<excludeFolder url="file://$MODULE_DIR$/venv" /> <excludeFolder url="file://$MODULE_DIR$/venv" />
</content> </content>
<orderEntry type="jdk" jdkName="Python 3.11 (car_inventory)" jdkType="Python SDK" /> <orderEntry type="jdk" jdkName="uv (car_inventory)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="jquery-3.5.1" level="application" /> <orderEntry type="library" name="jquery-3.5.1" level="application" />
<orderEntry type="library" name="sweetalert2" level="application" /> <orderEntry type="library" name="sweetalert2" level="application" />

5
.idea/misc.xml generated
View File

@ -3,8 +3,5 @@
<component name="Black"> <component name="Black">
<option name="sdkName" value="Python 3.11 (car_inventory)" /> <option name="sdkName" value="Python 3.11 (car_inventory)" />
</component> </component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.11 (car_inventory)" project-jdk-type="Python SDK" /> <component name="ProjectRootManager" version="2" project-jdk-name="uv (car_inventory)" project-jdk-type="Python SDK" />
<component name="PyPackaging">
<option name="earlyReleasesAsUpgrades" value="true" />
</component>
</project> </project>

View File

@ -1,31 +0,0 @@
# Generated by Django 5.2.1 on 2025-05-25 23:01
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name="CarVIN",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("vin", models.CharField(max_length=17, verbose_name="VIN")),
(
"created",
models.DateTimeField(auto_now_add=True, verbose_name="created"),
),
],
),
]

View File

@ -1,8 +1,5 @@
from django.conf import settings from django.conf import settings
from inventory.utils import get_user_type
def currency_context(request): def currency_context(request):
""" """
Provides a context dictionary containing the currency setting. This is typically Provides a context dictionary containing the currency setting. This is typically

View File

@ -114,6 +114,7 @@ class InjectDealerMiddleware:
# if request.user.is_authenticated and not request.session.get('otp_verified', False): # if request.user.is_authenticated and not request.session.get('otp_verified', False):
# return redirect(reverse('verify_otp')) # return redirect(reverse('verify_otp'))
# return self.get_response(request) # return self.get_response(request)
class DealerSlugMiddleware: class DealerSlugMiddleware:
def __init__(self, get_response): def __init__(self, get_response):
self.get_response = get_response self.get_response = get_response

View File

@ -971,7 +971,7 @@ def sale_order_created_notification(sender, instance, created, **kwargs):
user=recipient, user=recipient,
message=f""" message=f"""
New Sale Order has been added for estimate:{instance.estimate}. New Sale Order has been added for estimate:{instance.estimate}.
<a href="{reverse('estimate_detail',kwargs={'dealer_slug':instance.dealer.slug,'pk':instance.pk})}" target="_blank">View</a> <a href="{reverse('estimate_detail',kwargs={'dealer_slug':instance.dealer.slug,'pk':instance.estimate.pk})}" target="_blank">View</a>
""", """,
) )
@receiver(post_save, sender=models.Lead) @receiver(post_save, sender=models.Lead)

View File

@ -41,7 +41,7 @@ urlpatterns = [
path("dashboards/sales/", views.SalesDashboard.as_view(), name="sales_dashboard"), path("dashboards/sales/", views.SalesDashboard.as_view(), name="sales_dashboard"),
path("test/", views.TestView.as_view(), name="test"), path("test/", views.TestView.as_view(), name="test"),
path("cars/inventory/table/", views.CarListViewTable.as_view(), name="car_table"), path("cars/inventory/table/", views.CarListViewTable.as_view(), name="car_table"),
path("export/format/", TableExport, name="export"), path("export/format/", TableExport.export, name="export"),
# Dealer URLs # Dealer URLs
path( path(
"<slug:slug>/dealers/", views.DealerDetailView.as_view(), name="dealer_detail" "<slug:slug>/dealers/", views.DealerDetailView.as_view(), name="dealer_detail"
@ -440,63 +440,23 @@ urlpatterns = [
path("<slug:dealer_slug>/user/create/", views.UserCreateView.as_view(), name="user_create"), path("<slug:dealer_slug>/user/create/", views.UserCreateView.as_view(), name="user_create"),
path("<slug:dealer_slug>/user/<slug:slug>/", views.UserDetailView.as_view(), name="user_detail"), path("<slug:dealer_slug>/user/<slug:slug>/", views.UserDetailView.as_view(), name="user_detail"),
path("<slug:dealer_slug>/user/<slug:slug>/groups/", views.UserGroupView, name="user_groups"), path("<slug:dealer_slug>/user/<slug:slug>/groups/", views.UserGroupView, name="user_groups"),
path( path("<slug:dealer_slug>/user/<slug:slug>/update/", views.UserUpdateView.as_view(), name="user_update"),
"<slug:dealer_slug>/user/<slug:slug>/update/", views.UserUpdateView.as_view(), name="user_update"
),
path("<slug:dealer_slug>/user/<slug:slug>/confirm/", views.UserDeleteview, name="user_delete"), path("<slug:dealer_slug>/user/<slug:slug>/confirm/", views.UserDeleteview, name="user_delete"),
# Group URLs
path("<slug:dealer_slug>/group/create/", views.GroupCreateView.as_view(), name="group_create"), path("<slug:dealer_slug>/group/create/", views.GroupCreateView.as_view(), name="group_create"),
path( path("<slug:dealer_slug>/group/<int:pk>/update/", views.GroupUpdateView.as_view(), name="group_update"),
"<slug:dealer_slug>/group/<int:pk>/update/", views.GroupUpdateView.as_view(), name="group_update"
),
path("<slug:dealer_slug>/group/<int:pk>/", views.GroupDetailView.as_view(), name="group_detail"), path("<slug:dealer_slug>/group/<int:pk>/", views.GroupDetailView.as_view(), name="group_detail"),
path("<slug:dealer_slug>/group/", views.GroupListView.as_view(), name="group_list"), path("<slug:dealer_slug>/group/", views.GroupListView.as_view(), name="group_list"),
path("<slug:dealer_slug>/group/<int:pk>/confirm/", views.GroupDeleteview, name="group_delete"), path("<slug:dealer_slug>/group/<int:pk>/confirm/", views.GroupDeleteview, name="group_delete"),
path( path("<slug:dealer_slug>/group/<int:pk>/permission/", views.GroupPermissionView, name="group_permission"),
"<slug:dealer_slug>/group/<int:pk>/permission/", views.GroupPermissionView, name="group_permission" path("<slug:dealer_slug>/organizations/create/", views.OrganizationCreateView.as_view(), name="organization_create"),
), path("<slug:dealer_slug>/organizations/", views.OrganizationListView.as_view(), name="organization_list"),
# Organization URLs path("<slug:dealer_slug>/organizations/<slug:slug>/", views.OrganizationDetailView.as_view(), name="organization_detail"),
path( path("<slug:dealer_slug>/organizations/<slug:slug>/update/", views.OrganizationUpdateView.as_view(), name="organization_update"),
"<slug:dealer_slug>/organizations/create/", path("<slug:dealer_slug>/organizations/<slug:slug>/delete/", views.OrganizationDeleteView, name="organization_delete"),
views.OrganizationCreateView.as_view(), path("representatives/", views.RepresentativeListView.as_view(), name="representative_list"),
name="organization_create", path("representatives/<int:pk>/", views.RepresentativeDetailView.as_view(), name="representative_detail"),
), path("representatives/create/", views.RepresentativeCreateView.as_view(),name="representative_create"),
path( path("representatives/<int:pk>/update/",
"<slug:dealer_slug>/organizations/", views.OrganizationListView.as_view(), name="organization_list"
),
path(
"<slug:dealer_slug>/organizations/<slug:slug>/",
views.OrganizationDetailView.as_view(),
name="organization_detail",
),
path(
"<slug:dealer_slug>/organizations/<slug:slug>/update/",
views.OrganizationUpdateView.as_view(),
name="organization_update",
),
path(
"<slug:dealer_slug>/organizations/<slug:slug>/delete/",
views.OrganizationDeleteView,
name="organization_delete",
),
# Representative URLs
path(
"representatives/",
views.RepresentativeListView.as_view(),
name="representative_list",
),
path(
"representatives/<int:pk>/",
views.RepresentativeDetailView.as_view(),
name="representative_detail",
),
path(
"representatives/create/",
views.RepresentativeCreateView.as_view(),
name="representative_create",
),
path(
"representatives/<int:pk>/update/",
views.RepresentativeUpdateView.as_view(), views.RepresentativeUpdateView.as_view(),
name="representative_update", name="representative_update",
), ),
@ -505,9 +465,6 @@ urlpatterns = [
views.RepresentativeDeleteView.as_view(), views.RepresentativeDeleteView.as_view(),
name="representative_delete", name="representative_delete",
), ),
#####################################################################
# Ledger
#####################################################################
path("<slug:dealer_slug>/ledgers/", views.LedgerModelListView.as_view(), name="ledger_list"), path("<slug:dealer_slug>/ledgers/", views.LedgerModelListView.as_view(), name="ledger_list"),
path( path(
"<slug:dealer_slug>/ledgers/create/", views.LedgerModelCreateView.as_view(), name="ledger_create" "<slug:dealer_slug>/ledgers/create/", views.LedgerModelCreateView.as_view(), name="ledger_create"
@ -613,9 +570,6 @@ urlpatterns = [
views.LedgerModelDeleteView.as_view(), views.LedgerModelDeleteView.as_view(),
name="ledger-delete", name="ledger-delete",
), ),
##############################################################
# Bank Account
##############################################################
path( path(
"<slug:dealer_slug>/bank_accounts/", "<slug:dealer_slug>/bank_accounts/",
views.BankAccountListView.as_view(), views.BankAccountListView.as_view(),
@ -641,7 +595,6 @@ urlpatterns = [
views.bank_account_delete, views.bank_account_delete,
name="bank_account_delete", name="bank_account_delete",
), ),
# Account
path( path(
"<slug:dealer_slug>/coa_accounts/", "<slug:dealer_slug>/coa_accounts/",
views.AccountListView.as_view(), views.AccountListView.as_view(),

View File

@ -1005,6 +1005,7 @@ class CarFinanceCalculator:
car_finance = self._get_nested_value(item, self.CAR_FINANCE_KEY) car_finance = self._get_nested_value(item, self.CAR_FINANCE_KEY)
car_info = self._get_nested_value(item, self.CAR_INFO_KEY) car_info = self._get_nested_value(item, self.CAR_INFO_KEY)
unit_price = Decimal(car_finance.get("selling_price", 0)) unit_price = Decimal(car_finance.get("selling_price", 0))
print(item.item_model.car.finances)
return { return {
"item_number": item.item_model.item_number, "item_number": item.item_model.item_number,
"vin": car_info.get("vin"), "vin": car_info.get("vin"),

View File

@ -27,7 +27,7 @@ from django.views.decorators.http import require_http_methods
from django.db.models.deletion import RestrictedError from django.db.models.deletion import RestrictedError
from django.http.response import StreamingHttpResponse from django.http.response import StreamingHttpResponse
from django.core.exceptions import ImproperlyConfigured, ValidationError from django.core.exceptions import ImproperlyConfigured, ValidationError
from django.core.exceptions import PermissionDenied
# Django # Django
from django.db.models import Q from django.db.models import Q
from django.conf import settings from django.conf import settings
@ -624,8 +624,9 @@ class CarCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
return super().form_valid(form) return super().form_valid(form)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
dealer = get_user_type(self.request)
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context["vendor_exists"] = self.request.dealer.vendors.exists() context["vendor_exists"] = dealer.vendors.exists()
return context return context
@ -3636,7 +3637,7 @@ class BankAccountListView(LoginRequiredMixin, PermissionRequiredMixin, ListView)
template_name = "ledger/bank_accounts/bank_account_list.html" template_name = "ledger/bank_accounts/bank_account_list.html"
context_object_name = "bank_accounts" context_object_name = "bank_accounts"
paginate_by = 30 paginate_by = 30
permission_required = ["inventory.view_carfinance"] permission_required = ["django_ledger.view_bankaccountmodel"]
def get_queryset(self): def get_queryset(self):
query = self.request.GET.get("q") query = self.request.GET.get("q")
@ -4275,7 +4276,12 @@ def create_estimate(request, dealer_slug, slug=None):
# } # }
# ) # )
car_instance = models.Car.objects.filter( car_instance = models.Car.objects.filter(
hash=item.get("item_id"), finances__is_sold=False hash=item.get("item_id"),
finances__is_sold=False,
colors__isnull=False,
finances__isnull=False,
finances__selling_price__gt=1,
status="available",
).all() ).all()
for i in car_instance[: int(quantities[0])]: for i in car_instance[: int(quantities[0])]:
@ -4372,7 +4378,7 @@ def create_estimate(request, dealer_slug, slug=None):
dealer=dealer, dealer=dealer,
colors__isnull=False, colors__isnull=False,
finances__isnull=False, finances__isnull=False,
finances__selling_price__gt=0, finances__selling_price__gt=1,
status="available", status="available",
) )
.annotate( .annotate(
@ -4443,6 +4449,7 @@ class EstimateDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView
if estimate.get_itemtxs_data(): if estimate.get_itemtxs_data():
calculator = CarFinanceCalculator(estimate) calculator = CarFinanceCalculator(estimate)
finance_data = calculator.get_finance_data() finance_data = calculator.get_finance_data()
print(finance_data)
invoice_obj = InvoiceModel.objects.all().filter(ce_model=estimate).first() invoice_obj = InvoiceModel.objects.all().filter(ce_model=estimate).first()
kwargs["data"] = finance_data kwargs["data"] = finance_data
kwargs["invoice"] = invoice_obj kwargs["invoice"] = invoice_obj
@ -4643,7 +4650,6 @@ class EstimatePreviewView(LoginRequiredMixin, PermissionRequiredMixin, DetailVie
@login_required @login_required
@permission_required("django_ledger.change_estimatemodel", raise_exception=True)
def estimate_mark_as(request, dealer_slug, pk): def estimate_mark_as(request, dealer_slug, pk):
""" """
Marks an estimate with a specified status based on the requested action and Marks an estimate with a specified status based on the requested action and
@ -4660,6 +4666,11 @@ def estimate_mark_as(request, dealer_slug, pk):
:return: A redirect response to the estimate detail view. :return: A redirect response to the estimate detail view.
:rtype: HttpResponseRedirect :rtype: HttpResponseRedirect
""" """
if not (
request.user.has_perm("django_ledger.can_approve_estimatemodel") or
request.user.has_perm("django_ledger.change_estimatemodel")
):
raise PermissionDenied
dealer = get_object_or_404(models.Dealer, slug=dealer_slug) dealer = get_object_or_404(models.Dealer, slug=dealer_slug)
estimate = get_object_or_404(EstimateModel, pk=pk) estimate = get_object_or_404(EstimateModel, pk=pk)
mark = request.GET.get("mark") mark = request.GET.get("mark")
@ -4680,6 +4691,9 @@ def estimate_mark_as(request, dealer_slug, pk):
) )
estimate.mark_as_approved() estimate.mark_as_approved()
messages.success(request, _("Quotation approved successfully")) messages.success(request, _("Quotation approved successfully"))
return redirect(
"estimate_list", dealer_slug=dealer.slug
)
elif mark == "rejected": elif mark == "rejected":
if not estimate.can_cancel(): if not estimate.can_cancel():
messages.error(request, _("Quotation is not ready for rejection")) messages.error(request, _("Quotation is not ready for rejection"))
@ -5518,8 +5532,10 @@ def lead_create(request,dealer_slug):
is_sa_import=True, pk__in=dealer_make_list is_sa_import=True, pk__in=dealer_make_list
) )
form.fields["staff"].queryset = form.fields["staff"].queryset.filter( form.fields["staff"].queryset = form.fields["staff"].queryset.filter(
dealer=dealer, staff_type="sales" dealer=dealer,staff_member__user__groups__name__contains="Sales")
) # form.fields["staff"].queryset = form.fields["staff"].queryset.filter(
# dealer=dealer
# )
if hasattr(request.user.staffmember, "staff"): if hasattr(request.user.staffmember, "staff"):
form.initial["staff"] = request.user.staffmember.staff form.initial["staff"] = request.user.staffmember.staff

357
pyproject.toml Normal file
View File

@ -0,0 +1,357 @@
[project]
name = "car-inventory"
version = "0.1.0"
description = "Add your description here"
requires-python = ">=3.12"
dependencies = [
"aiohappyeyeballs>=2.6.1",
"aiohttp>=3.12.13",
"aiohttp-retry>=2.9.1",
"aiosignal>=1.3.2",
"alabaster>=1.0.0",
"albucore>=0.0.24",
"albumentations>=2.0.8",
"annotated-types>=0.7.0",
"anthropic>=0.55.0",
"anyio>=4.9.0",
"arabic-reshaper>=3.0.0",
"argcomplete>=3.6.2",
"arrow>=1.3.0",
"asgiref>=3.8.1",
"astor>=0.8.1",
"astroid>=3.3.10",
"attrs>=25.3.0",
"autopep8>=2.3.2",
"babel>=2.17.0",
"beautifulsoup4>=4.13.4",
"bleach>=6.2.0",
"blessed>=1.21.0",
"blinker>=1.9.0",
"boto3>=1.38.44",
"botocore>=1.38.44",
"brotli>=1.1.0",
"cachetools>=5.5.2",
"cattrs>=25.1.1",
"certifi>=2025.6.15",
"cffi>=1.17.1",
"chardet>=5.2.0",
"charset-normalizer>=3.4.2",
"click>=7.1.2",
"cohere>=5.15.0",
"colorama>=0.4.6",
"commonmark>=0.9.1",
"contourpy>=1.3.2",
"crispy-bootstrap5>=2025.6",
"cryptography>=45.0.4",
"cssselect2>=0.8.0",
"ctranslate2>=4.6.0",
"cycler>=0.12.1",
"cython>=3.1.2",
"dataclasses-json>=0.6.7",
"decorator>=5.2.1",
"defusedxml>=0.7.1",
"desert>=2020.11.18",
"diff-match-patch>=20241021",
"dill>=0.4.0",
"distro>=1.9.0",
"dj-rest-auth>=7.0.1",
"django>=5.2.3",
"django-allauth>=65.9.0",
"django-appointment>=3.6.0",
"django-autoslug>=1.9.9",
"django-background-tasks>=1.2.8",
"django-bootstrap5>=25.1",
"django-ckeditor>=6.7.3",
"django-classy-tags>=4.1.0",
"django-cors-headers>=4.7.0",
"django-countries>=7.6.1",
"django-crispy-forms>=2.4",
"django-debug-toolbar>=5.2.0",
"django-easy-audit>=1.3.7",
"django-extensions>=4.1",
"django-filter>=25.1",
"django-formtools>=2.5.1",
"django-import-export>=4.3.8",
"django-js-asset>=3.1.2",
"django-ledger==0.7.8",
"django-model-utils>=5.0.0",
"django-money>=3.5.4",
"django-next-url-mixin>=0.4.0",
"django-nine>=0.2.7",
"django-nonefield>=0.4",
"django-ordered-model>=3.7.4",
"django-pdf-actions>=0.1.52",
"django-phonenumber-field>=8.1.0",
"django-picklefield>=3.3",
"django-plans>=2.0.0",
"django-prometheus>=2.4.1",
"django-q2>=1.8.0",
"django-schema-graph>=3.1.0",
"django-sekizai>=4.1.0",
"django-sequences>=3.0",
"django-silk>=5.4.0",
"django-simple-history>=3.10.1",
"django-sms>=0.7.0",
"django-sslserver-v2>=1.0",
"django-tables2>=2.7.5",
"django-treebeard>=4.7.1",
"django-view-breadcrumbs>=2.5.1",
"django-widget-tweaks>=1.5.0",
"djangocms-admin-style>=3.3.1",
"djangorestframework>=3.16.0",
"djangorestframework-simplejwt>=5.5.0",
"djangoviz>=0.1.1",
"djhtml>=3.0.8",
"docopt>=0.6.2",
"docutils>=0.21.2",
"easy-thumbnails>=2.10",
"emoji>=2.14.1",
"et-xmlfile>=2.0.0",
"eval-type-backport>=0.2.2",
"executing>=2.2.0",
"faker>=37.4.0",
"fasta2a>=0.3.4",
"fastavro>=1.11.1",
"filelock>=3.18.0",
"fire>=0.7.0",
"fonttools>=4.58.4",
"fpdf>=1.7.2",
"fpdf2>=2.8.3",
"frozenlist>=1.7.0",
"fsspec>=2025.5.1",
"google-auth>=2.40.3",
"google-genai>=1.22.0",
"googleapis-common-protos>=1.70.0",
"gprof2dot>=2025.4.14",
"graphqlclient>=0.2.4",
"greenlet>=3.2.3",
"griffe>=1.7.3",
"groq>=0.29.0",
"h11>=0.16.0",
"h2>=4.2.0",
"hf-xet>=1.1.5",
"hpack>=4.1.0",
"hstspreload>=2025.1.1",
"httpcore>=1.0.9",
"httpx>=0.28.1",
"httpx-sse>=0.4.0",
"huggingface-hub>=0.33.1",
"hyperframe>=6.1.0",
"icalendar>=6.3.1",
"idna>=3.10",
"imageio>=2.37.0",
"imagesize>=1.4.1",
"imgaug>=0.4.0",
"importlib-metadata>=8.7.0",
"iso4217>=1.14.20250512",
"isodate>=0.7.2",
"isort>=6.0.1",
"itsdangerous>=2.2.0",
"jinja2>=3.1.6",
"jiter>=0.10.0",
"jmespath>=1.0.1",
"joblib>=1.5.1",
"jsonpatch>=1.33",
"jsonpointer>=3.0.0",
"jwt>=1.4.0",
"kiwisolver>=1.4.8",
"langchain>=0.3.26",
"langchain-community>=0.3.26",
"langchain-core>=0.3.66",
"langchain-ollama>=0.3.3",
"langchain-text-splitters>=0.3.8",
"langsmith>=0.4.2",
"lazy-loader>=0.4",
"ledger>=1.0.1",
"libretranslatepy>=2.1.4",
"lmdb>=1.6.2",
"logfire>=3.21.1",
"logfire-api>=3.21.1",
"luhnchecker>=0.0.12",
"lxml>=5.4.0",
"markdown>=3.8.2",
"markdown-it-py>=3.0.0",
"markupsafe>=3.0.2",
"marshmallow>=3.26.1",
"matplotlib>=3.10.3",
"mccabe>=0.7.0",
"mcp>=1.9.4",
"mdurl>=0.1.2",
"mistralai>=1.8.2",
"mouseinfo>=0.1.3",
"mpmath>=1.3.0",
"multidict>=6.5.1",
"mypy-extensions>=1.1.0",
"networkx>=3.5",
"newrelic>=10.14.0",
"nltk>=3.9.1",
"num2words>=0.5.14",
"numpy>=2.3.1",
"oauthlib>=3.3.1",
"ofxtools>=0.9.5",
"ollama>=0.5.1",
"openai>=1.91.0",
"opencv-contrib-python>=4.11.0.86",
"opencv-python>=4.11.0.86",
"opencv-python-headless>=4.11.0.86",
"openpyxl>=3.1.5",
"opentelemetry-api>=1.34.1",
"opentelemetry-exporter-otlp-proto-common>=1.34.1",
"opentelemetry-exporter-otlp-proto-http>=1.34.1",
"opentelemetry-instrumentation>=0.55b1",
"opentelemetry-proto>=1.34.1",
"opentelemetry-sdk>=1.34.1",
"opentelemetry-semantic-conventions>=0.55b1",
"opt-einsum>=3.4.0",
"orjson>=3.10.18",
"outcome>=1.3.0.post0",
"packaging>=24.2",
"pandas>=2.3.0",
"pango>=0.0.1",
"pdfkit>=1.0.0",
"phonenumbers>=9.0.8",
"pillow>=11.2.1",
"platformdirs>=4.3.8",
"prometheus-client>=0.22.1",
"prompt-toolkit>=3.0.51",
"propcache>=0.3.2",
"protobuf>=5.29.5",
"psycopg>=3.2.9",
"psycopg-binary>=3.2.9",
"psycopg-c>=3.2.9",
"psycopg2-binary>=2.9.10",
"py-moneyed>=3.0",
"pyasn1>=0.6.1",
"pyasn1-modules>=0.4.2",
"pyautogui>=0.9.54",
"pyclipper>=1.3.0.post6",
"pycodestyle>=2.14.0",
"pycparser>=2.22",
"pydantic>=2.11.7",
"pydantic-ai>=0.3.4",
"pydantic-ai-slim>=0.3.4",
"pydantic-core>=2.33.2",
"pydantic-evals>=0.3.4",
"pydantic-graph>=0.3.4",
"pydantic-settings>=2.10.1",
"pydotplus>=2.0.2",
"pydyf>=0.11.0",
"pygetwindow>=0.0.9",
"pygments>=2.19.2",
"pyjwt>=2.9.0",
"pylint>=3.3.7",
"pymsgbox>=1.0.9",
"pymysql>=1.1.1",
"pyobjc-core>=11.1",
"pyobjc-framework-cocoa>=11.1",
"pyobjc-framework-quartz>=11.1",
"pyparsing>=3.2.3",
"pypdf>=5.6.1",
"pyperclip>=1.9.0",
"pyphen>=0.17.2",
"pypng>=0.20220715.0",
"pyrect>=0.2.0",
"pyscreeze>=1.0.1",
"pyserial>=3.5",
"pysocks>=1.7.1",
"python-bidi>=0.6.6",
"python-dateutil>=2.9.0.post0",
"python-docx>=1.2.0",
"python-dotenv>=1.1.1",
"python-multipart>=0.0.20",
"python-openid>=2.2.5",
"python-slugify>=8.0.4",
"python-stdnum>=2.1",
"python3-saml>=1.16.0",
"pytweening>=1.2.0",
"pytz>=2025.2",
"pyvin>=0.0.2",
"pywa>=2.11.0",
"pywhat>=1.0.0",
"pywhatkit>=5.4",
"pyyaml>=6.0.2",
"pyzbar>=0.1.9",
"qrcode>=8.2",
"rapidfuzz>=3.13.0",
"redis>=6.2.0",
"regex>=2024.11.6",
"reportlab>=4.4.2",
"requests>=2.32.4",
"requests-oauthlib>=2.0.0",
"requests-toolbelt>=1.0.0",
"rfc3986>=2.0.0",
"rich>=14.0.0",
"rsa>=4.9.1",
"rubicon-objc>=0.5.1",
"s3transfer>=0.13.0",
"sacremoses>=0.1.1",
"safetensors>=0.5.3",
"scikit-image>=0.25.2",
"scikit-learn>=1.7.0",
"scipy>=1.16.0",
"selenium>=4.32.0",
"sentence-transformers>=4.1.0",
"sentencepiece>=0.2.0",
"shapely>=2.1.1",
"simsimd>=6.4.9",
"six>=1.17.0",
"slugify>=0.0.1",
"sniffio>=1.3.1",
"snowballstemmer>=3.0.1",
"sortedcontainers>=2.4.0",
"soupsieve>=2.7",
"sqlalchemy>=2.0.41",
"sqlparse>=0.5.3",
"sse-starlette>=2.3.6",
"stanza>=1.10.1",
"starlette>=0.47.1",
"stringzilla>=3.12.5",
"suds>=1.2.0",
"swapper>=1.3.0",
"sympy>=1.14.0",
"tablib>=3.8.0",
"tenacity>=8.5.0",
"termcolor>=3.1.0",
"text-unidecode>=1.3",
"threadpoolctl>=3.6.0",
"tifffile>=2025.6.11",
"tinycss2>=1.4.0",
"tinyhtml5>=2.0.0",
"tokenizers>=0.21.2",
"tomli>=2.2.1",
"tomlkit>=0.13.3",
"torch>=2.7.1",
"tqdm>=4.67.1",
"transformers>=4.52.4",
"trio>=0.30.0",
"trio-websocket>=0.12.2",
"twilio>=9.6.3",
"types-python-dateutil>=2.9.0.20250516",
"types-requests>=2.32.4.20250611",
"typing-extensions>=4.14.0",
"typing-inspect>=0.9.0",
"typing-inspection>=0.4.1",
"tzdata>=2025.2",
"unidecode>=1.4.0",
"upgrade-requirements>=1.7.0",
"urllib3>=2.5.0",
"uvicorn>=0.34.3",
"vin>=0.6.2",
"vininfo>=1.9.1",
"vishap>=0.1.5",
"vpic-api>=0.7.4",
"wcwidth>=0.2.13",
"weasyprint>=65.1",
"webencodings>=0.5.1",
"websocket-client>=1.8.0",
"websockets>=15.0.1",
"werkzeug>=3.1.3",
"wikipedia>=1.4.0",
"wrapt>=1.17.2",
"wsproto>=1.2.0",
"xmlsec>=1.3.15",
"yarl>=1.20.1",
"zipp>=3.23.0",
"zopfli>=0.2.3.post1",
"zstandard>=0.23.0",
]

View File

@ -1,350 +1,350 @@
aiohappyeyeballs==2.6.1 aiohappyeyeballs
aiohttp==3.12.0 aiohttp
aiohttp-retry==2.9.1 aiohttp-retry
aiosignal==1.3.2 aiosignal
alabaster==1.0.0 alabaster
albucore==0.0.24 albucore
albumentations==2.0.7 albumentations
annotated-types==0.7.0 annotated-types
anthropic==0.52.2 anthropic
anyio==4.9.0 anyio
arabic-reshaper==3.0.0 arabic-reshaper
argcomplete==3.6.2 argcomplete
arrow==1.3.0 arrow
asgiref==3.8.1 asgiref
astor==0.8.1 astor
astroid==3.3.10 astroid
attrs==25.3.0 attrs
autopep8==2.3.2 autopep8
Babel==2.15.0 Babel
beautifulsoup4==4.13.4 beautifulsoup4
bleach==6.2.0 bleach
blessed==1.21.0 blessed
blinker==1.9.0 blinker
boto3==1.38.29 boto3
botocore==1.38.29 botocore
Brotli==1.1.0 Brotli
cachetools==5.5.2 cachetools
cattrs==24.1.3 cattrs
certifi==2025.4.26 certifi
cffi==1.17.1 cffi
chardet==5.2.0 chardet
charset-normalizer==3.4.2 charset-normalizer
click==8.2.1 click
cohere==5.15.0 cohere
colorama==0.4.6 colorama
commonmark==0.9.1 commonmark
contourpy==1.3.2 contourpy
crispy-bootstrap5==2025.4 crispy-bootstrap5
cryptography==45.0.3 cryptography
cssselect2==0.8.0 cssselect2
ctranslate2==4.6.0 ctranslate2
cycler==0.12.1 cycler
Cython==3.1.1 Cython
dataclasses-json==0.6.7 dataclasses-json
decorator==5.2.1 decorator
defusedxml==0.7.1 defusedxml
desert==2020.11.18 desert
diff-match-patch==20241021 diff-match-patch
dill==0.4.0 dill
distro==1.9.0 distro
dj-rest-auth==7.0.1 dj-rest-auth
Django==5.2.1 Django
django-allauth==65.8.1 django-allauth
django-appointment==3.8.0 django-appointment
django-autoslug==1.9.9 django-autoslug
django-background-tasks==1.2.8 django-background-tasks
django-bootstrap5==25.1 django-bootstrap5
django-ckeditor==6.7.2 django-ckeditor
django-classy-tags==4.1.0 django-classy-tags
django-cors-headers==4.7.0 django-cors-headers
django-countries==7.6.1 django-countries
django-crispy-forms==2.4 django-crispy-forms
django-debug-toolbar==5.2.0 django-debug-toolbar
django-easy-audit==1.3.7 django-easy-audit
django-extensions==4.1 django-extensions
django-filter==25.1 django-filter
django-formtools==2.5.1 django-formtools
django-import-export==4.3.7 django-import-export
django-js-asset==3.1.2 django-js-asset
django-ledger==0.7.7 django-ledger
django-model-utils==5.0.0 django-model-utils
django-money==3.5.4 django-money
django-next-url-mixin==0.4.0 django-next-url-mixin
django-nine==0.2.7 django-nine
django-nonefield==0.4 django-nonefield
django-ordered-model==3.7.4 django-ordered-model
django-pdf-actions==0.1.49 django-pdf-actions
django-phonenumber-field==8.0.0 django-phonenumber-field
django-picklefield==3.3 django-picklefield
django-plans==2.0.0 django-plans
django-prometheus==2.3.1 django-prometheus
django-q2==1.8.0 django-q2
django-schema-graph==3.1.0 django-schema-graph
django-sekizai==4.1.0 django-sekizai
django-sequences==3.0 django-sequences
django-silk==5.3.2 django-silk
django-simple-history==3.8.0 django-simple-history
django-sms==0.7.0 django-sms
django-sslserver==0.22 django-sslserver
django-tables2==2.7.5 django-tables2
django-treebeard==4.7.1 django-treebeard
django-view-breadcrumbs==2.5.1 django-view-breadcrumbs
django-widget-tweaks==1.5.0 django-widget-tweaks
djangocms-admin-style==3.3.1 djangocms-admin-style
djangorestframework==3.16.0 djangorestframework
djangorestframework_simplejwt==5.5.0 djangorestframework_simplejwt
djangoviz==0.1.1 djangoviz
djhtml==3.0.8 djhtml
docopt==0.6.2 docopt
docutils==0.21.2 docutils
easy-thumbnails==2.10 easy-thumbnails
emoji==2.14.1 emoji
et_xmlfile==2.0.0 et_xmlfile
eval_type_backport==0.2.2 eval_type_backport
executing==2.2.0 executing
Faker==37.3.0 Faker
fasta2a==0.2.14 fasta2a
fastavro==1.11.1 fastavro
filelock==3.18.0 filelock
fire==0.7.0 fire
fonttools==4.58.0 fonttools
fpdf==1.7.2 fpdf
fpdf2==2.8.3 fpdf2
frozenlist==1.6.0 frozenlist
fsspec==2025.5.1 fsspec
google-auth==2.40.2 google-auth
google-genai==1.18.0 google-genai
googleapis-common-protos==1.70.0 googleapis-common-protos
gprof2dot==2025.4.14 gprof2dot
graphqlclient==0.2.4 graphqlclient
greenlet==3.2.2 greenlet
griffe==1.7.3 griffe
groq==0.26.0 groq
h11==0.16.0 h11
h2==4.2.0 h2
hf-xet==1.1.3 hf-xet
hpack==4.1.0 hpack
hstspreload==2025.1.1 hstspreload
httpcore==1.0.9 httpcore
httpx==0.28.1 httpx
httpx-sse==0.4.0 httpx-sse
huggingface-hub==0.32.4 huggingface-hub
hyperframe==6.1.0 hyperframe
icalendar==6.3.1 icalendar
idna==3.10 idna
imageio==2.37.0 imageio
imagesize==1.4.1 imagesize
imgaug==0.4.0 imgaug
importlib_metadata==8.7.0 importlib_metadata
iso4217==1.12.20240625 iso4217
isodate==0.7.2 isodate
isort==6.0.1 isort
itsdangerous==2.2.0 itsdangerous
Jinja2==3.1.6 Jinja2
jiter==0.10.0 jiter
jmespath==1.0.1 jmespath
joblib==1.5.1 joblib
jsonpatch==1.33 jsonpatch
jsonpointer==3.0.0 jsonpointer
jwt==1.3.1 jwt
kiwisolver==1.4.8 kiwisolver
langchain==0.3.25 langchain
langchain-community==0.3.24 langchain-community
langchain-core==0.3.61 langchain-core
langchain-ollama==0.3.3 langchain-ollama
langchain-text-splitters==0.3.8 langchain-text-splitters
langsmith==0.3.42 langsmith
lazy_loader==0.4 lazy_loader
ledger==1.0.1 ledger
libretranslatepy==2.1.4 libretranslatepy
lmdb==1.6.2 lmdb
logfire==3.18.0 logfire
logfire-api==3.17.0 logfire-api
luhnchecker==0.0.12 luhnchecker
lxml==5.4.0 lxml
Markdown==3.8 Markdown
markdown-it-py==3.0.0 markdown-it-py
MarkupSafe==3.0.2 MarkupSafe
marshmallow==3.26.1 marshmallow
matplotlib==3.10.3 matplotlib
mccabe==0.7.0 mccabe
mcp==1.9.2 mcp
mdurl==0.1.2 mdurl
mistralai==1.8.1 mistralai
MouseInfo==0.1.3 MouseInfo
mpmath==1.3.0 mpmath
multidict==6.4.4 multidict
mypy_extensions==1.1.0 mypy_extensions
networkx==3.4.2 networkx
newrelic==10.12.0 newrelic
nltk==3.9.1 nltk
num2words==0.5.14 num2words
numpy==2.2.6 numpy
oauthlib==3.2.2 oauthlib
ofxtools==0.9.5 ofxtools
ollama==0.4.8 ollama
openai==1.82.0 openai
opencv-contrib-python==4.11.0.86 opencv-contrib-python
opencv-python==4.11.0.86 opencv-python
opencv-python-headless==4.11.0.86 opencv-python-headless
openpyxl==3.1.5 openpyxl
opentelemetry-api==1.34.0 opentelemetry-api
opentelemetry-exporter-otlp-proto-common==1.34.0 opentelemetry-exporter-otlp-proto-common
opentelemetry-exporter-otlp-proto-http==1.34.0 opentelemetry-exporter-otlp-proto-http
opentelemetry-instrumentation==0.55b0 opentelemetry-instrumentation
opentelemetry-proto==1.34.0 opentelemetry-proto
opentelemetry-sdk==1.34.0 opentelemetry-sdk
opentelemetry-semantic-conventions==0.55b0 opentelemetry-semantic-conventions
opt_einsum==3.4.0 opt_einsum
orjson==3.10.18 orjson
outcome==1.3.0.post0 outcome
packaging==24.2 packaging
pandas==2.2.3 pandas
pango==0.0.1 pango
pdfkit==1.0.0 pdfkit
phonenumbers==8.13.42 phonenumbers
pillow==10.4.0 pillow
platformdirs==4.3.8 platformdirs
prometheus_client==0.22.0 prometheus_client
prompt_toolkit==3.0.51 prompt_toolkit
propcache==0.3.1 propcache
protobuf==5.29.5 protobuf
psycopg==3.2.9 psycopg
psycopg-binary==3.2.9 psycopg-binary
psycopg-c==3.2.9 psycopg-c
psycopg2-binary==2.9.10 psycopg2-binary
py-moneyed==3.0 py-moneyed
pyasn1==0.6.1 pyasn1
pyasn1_modules==0.4.2 pyasn1_modules
PyAutoGUI==0.9.54 PyAutoGUI
pyclipper==1.3.0.post6 pyclipper
pycodestyle==2.13.0 pycodestyle
pycparser==2.22 pycparser
pydantic==2.11.5 pydantic
pydantic-ai==0.2.14 pydantic-ai
pydantic-ai-slim==0.2.14 pydantic-ai-slim
pydantic-evals==0.2.14 pydantic-evals
pydantic-graph==0.2.14 pydantic-graph
pydantic-settings==2.9.1 pydantic-settings
pydantic_core==2.33.2 pydantic_core
pydotplus==2.0.2 pydotplus
pydyf==0.11.0 pydyf
PyGetWindow==0.0.9 PyGetWindow
Pygments==2.19.1 Pygments
PyJWT==2.10.1 PyJWT
pylint==3.3.7 pylint
PyMsgBox==1.0.9 PyMsgBox
PyMySQL==1.1.1 PyMySQL
pyobjc-core==11.0 pyobjc-core
pyobjc-framework-Cocoa==11.0 pyobjc-framework-Cocoa
pyobjc-framework-Quartz==11.0 pyobjc-framework-Quartz
pyparsing==3.2.3 pyparsing
pypdf==5.5.0 pypdf
pyperclip==1.9.0 pyperclip
pyphen==0.17.2 pyphen
pypng==0.20220715.0 pypng
PyRect==0.2.0 PyRect
PyScreeze==1.0.1 PyScreeze
pyserial==3.5 pyserial
PySocks==1.7.1 PySocks
python-bidi==0.6.6 python-bidi
python-dateutil==2.9.0.post0 python-dateutil
python-docx==1.1.2 python-docx
python-dotenv==1.1.0 python-dotenv
python-multipart==0.0.20 python-multipart
python-openid==2.2.5 python-openid
python-slugify==8.0.4 python-slugify
python-stdnum==2.1 python-stdnum
python3-saml==1.16.0 python3-saml
pytweening==1.2.0 pytweening
pytz==2025.2 pytz
pyvin==0.0.2 pyvin
pywa==2.10.0 pywa
pywhat==5.1.0 pywhat
pywhatkit==5.4 pywhatkit
PyYAML==6.0.2 PyYAML
pyzbar==0.1.9 pyzbar
qrcode==8.2 qrcode
RapidFuzz==3.13.0 RapidFuzz
redis==6.1.0 redis
regex==2024.11.6 regex
reportlab==4.4.1 reportlab
requests==2.32.3 requests
requests-oauthlib==2.0.0 requests-oauthlib
requests-toolbelt==1.0.0 requests-toolbelt
rfc3986==2.0.0 rfc3986
rich==14.0.0 rich
rsa==4.9.1 rsa
rubicon-objc==0.5.0 rubicon-objc
s3transfer==0.13.0 s3transfer
sacremoses==0.1.1 sacremoses
safetensors==0.5.3 safetensors
scikit-image==0.25.2 scikit-image
scikit-learn==1.6.1 scikit-learn
scipy==1.15.3 scipy
selenium==4.33.0 selenium
sentence-transformers==4.1.0 sentence-transformers
sentencepiece==0.2.0 sentencepiece
shapely==2.1.1 shapely
simsimd==6.2.1 simsimd
six==1.17.0 six
slugify==0.0.1 slugify
sniffio==1.3.1 sniffio
snowballstemmer==3.0.1 snowballstemmer
sortedcontainers==2.4.0 sortedcontainers
soupsieve==2.7 soupsieve
SQLAlchemy==2.0.41 SQLAlchemy
sqlparse==0.5.3 sqlparse
sse-starlette==2.3.6 sse-starlette
stanza==1.10.1 stanza
starlette==0.47.0 starlette
stringzilla==3.12.5 stringzilla
suds==1.2.0 suds
swapper==1.3.0 swapper
sympy==1.14.0 sympy
tablib==3.8.0 tablib
tenacity==9.1.2 tenacity
termcolor==3.1.0 termcolor
text-unidecode==1.3 text-unidecode
threadpoolctl==3.6.0 threadpoolctl
tifffile==2025.5.24 tifffile
tinycss2==1.4.0 tinycss2
tinyhtml5==2.0.0 tinyhtml5
tokenizers==0.21.1 tokenizers
tomli==2.2.1 tomli
tomlkit==0.13.2 tomlkit
torch==2.7.0 torch
tqdm==4.67.1 tqdm
transformers==4.52.4 transformers
trio==0.30.0 trio
trio-websocket==0.12.2 trio-websocket
twilio==9.6.1 twilio
types-python-dateutil==2.9.0.20250516 types-python-dateutil
types-requests==2.32.0.20250602 types-requests
typing-inspect==0.9.0 typing-inspect
typing-inspection==0.4.1 typing-inspection
typing_extensions==4.13.2 typing_extensions
tzdata==2025.2 tzdata
Unidecode==1.4.0 Unidecode
upgrade-requirements==1.7.0 upgrade-requirements
urllib3==2.4.0 urllib3
uvicorn==0.34.3 uvicorn
vin==0.6.2 vin
vininfo==1.8.0 vininfo
vishap==0.1.5 vishap
vpic-api==0.7.4 vpic-api
wcwidth==0.2.13 wcwidth
weasyprint==65.1 weasyprint
webencodings==0.5.1 webencodings
websocket-client==1.8.0 websocket-client
websockets==15.0.1 websockets
Werkzeug==3.1.3 Werkzeug
wikipedia==1.4.0 wikipedia
wrapt==1.17.2 wrapt
wsproto==1.2.0 wsproto
xmlsec==1.3.15 xmlsec
yarl==1.20.0 yarl
zipp==3.22.0 zipp
zopfli==0.2.3.post1 zopfli
zstandard==0.23.0 zstandard

BIN
static/.DS_Store vendored

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -19,6 +19,3 @@
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -1,90 +1,64 @@
{% load i18n static %} {% load i18n static %}
<div class="d-flex justify-content-between align-items-center mt-4 mb-3"> <div class="d-flex justify-content-between align-items-center mt-4 mb-3">
<div class="text-body-secondary"> <div class="text-body-secondary fw-bold fs-10">
{{ _("Showing") }} {{ page_obj.start_index }} {{ _("to") }} {{ page_obj.end_index }} {{ _("Showing") }} {{ page_obj.start_index }} {{ _("to") }} {{ page_obj.end_index }}
{{ _("of") }} {{ page_obj.paginator.count }} {{ _("results") }} {{ _("of") }} {{ page_obj.paginator.count }} {{ _("results") }}
</div> </div>
<nav aria-label="Page navigation"> <nav aria-label="Page navigation">
<ul class="pagination mb-0"> <ul class="pagination mb-0">
{# First Page Link #} {# First Page Link #}
{% if page_obj.has_previous %} {% if page_obj.has_previous %}
<li class="page-item rounded-md overflow-hidden"> <li class="page-item">
<a class="page-link px-3 py-2 border border-gray-300 bg-white text-blue-600 hover:bg-gray-100 transition-colors duration-200" href="?page=1{% if q %}&q={{q}}{% endif %}" aria-label="{% trans 'First' %}"> <a class="page-link" href="?page=1{% if q %}&q={{q}}{% endif %}">
<span class="fas fa-angle-double-left" aria-hidden="true"></span> <span class="fas fa-angle-double-{% if LANGUAGE_CODE == 'ar' %}right{% else %}left{% endif %}"> </span>
</a>
</a> </li>
</li> {% else %}
{% else %} <li class="page-item">
<li class="page-item disabled rounded-md overflow-hidden"> <span class="page-link">
<span class="page-link px-3 py-2 border border-gray-200 bg-gray-50 text-gray-400 cursor-not-allowed"> <span class="fas fa-chevron-{% if LANGUAGE_CODE == 'ar' %}right{% else %}left{% endif %}"> </span>
<span class="fas fa-angle-double-left" aria-hidden="true"></span> </span>
</li>
</span> {% endif %}
</li>
{% endif %}
{# Previous Page Link #} {# Previous Page Link #}
{% if page_obj.has_previous %} {% if page_obj.has_previous %}
<li class="page-item rounded-md overflow-hidden"> <li class="page-item">
<a class="page-link px-3 py-2 border border-gray-300 bg-white text-blue-600 hover:bg-gray-100 transition-colors duration-200" href="?page={{ page_obj.previous_page_number }}{% if q %}&q={{q}}{% endif %}" aria-label="{% trans 'Previous' %}"> <a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if q %}&q={{q}}{% endif %}">
<span class="fas fa-chevron-left" aria-hidden="true"></span> <span class="fas fa-chevron-{% if LANGUAGE_CODE == 'ar' %}right{% else %}left{% endif %}"></span>
</a>
</a> </li>
</li> {% endif %}
{% else %}
<li class="page-item disabled rounded-md overflow-hidden">
<span class="page-link px-3 py-2 border border-gray-200 bg-gray-50 text-gray-400 cursor-not-allowed">
<span class="fas fa-chevron-left" aria-hidden="true"></span>
</span>
</li>
{% endif %}
{# Page Numbers #} {# Page Numbers #}
{% for num in page_obj.paginator.page_range %} {% for num in page_obj.paginator.page_range %}
{% if num == 1 or num == page_obj.paginator.num_pages or num >= page_obj.number|add:-2 and num <= page_obj.number|add:2 %} {% if num == 1 or num == page_obj.paginator.num_pages or num >= page_obj.number|add:-2 and num <= page_obj.number|add:2 %}
<li class="page-item {% if num == page_obj.number %}active{% endif %}"> <li class="page-item {% if num == page_obj.number %}active{% endif %}">
<a class="page-link" {% if num == page_obj.number %}aria-current="page"{% endif %} <a class="page-link" {% if num == page_obj.number %}aria-current="page"{% endif %}
href="?page={{ num }}{% if q %}&q={{q}}{% endif %}"> href="?page={{ num }}{% if q %}&q={{q}}{% endif %}">
{{ num }} {{ num }}
</a> </a>
</li> </li>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{# Next Page Link #} {# Next Page Link #}
{% if page_obj.has_next %} {% if page_obj.has_next %}
<li class="page-item rounded-md overflow-hidden"> <li class="page-item">
<a class="page-link px-3 py-2 border border-gray-300 bg-white text-blue-600 hover:bg-gray-100 transition-colors duration-200" href="?page={{ page_obj.next_page_number }}{% if q %}&q={{q}}{% endif %}" aria-label="{% trans 'Next' %}"> <a class="page-link" href="?page={{ page_obj.next_page_number }}{% if q %}&q={{q}}{% endif %}">
<span class="fas fa-chevron-right" aria-hidden="true"></span> <span class="fas fa-chevron-{% if LANGUAGE_CODE == 'ar' %}left{% else %}right{% endif %}"></span>
</a>
</a> </li>
</li> {% endif %}
{% else %}
<li class="page-item disabled rounded-md overflow-hidden">
<span class="page-link px-3 py-2 border border-gray-200 bg-gray-50 text-gray-400 cursor-not-allowed">
<span class="fas fa-chevron-right" aria-hidden="true"></span>
</span>
</li>
{% endif %}
{# Last Page Link #} {# Last Page Link #}
{% if page_obj.has_next %} {% if page_obj.has_next %}
<li class="page-item rounded-md overflow-hidden"> <li class="page-item">
<a class="page-link px-3 py-2 border border-gray-300 bg-white text-blue-600 hover:bg-gray-100 transition-colors duration-200" href="?page={{ page_obj.paginator.num_pages }}{% if q %}&q={{q}}{% endif %}" aria-label="{% trans 'Last' %}"> <a class="page-link" href="?page={{ page_obj.paginator.num_pages }}{% if q %}&q={{q}}{% endif %}">
<span class="fas fa-angle-double-right" aria-hidden="true"></span> <span class="fas fa-angle-double-{% if LANGUAGE_CODE == 'ar' %}left{% else %}right{% endif %}"></span>
</a>
</a> </li>
</li> {% endif %}
{% else %} </ul>
<li class="page-item disabled rounded-md overflow-hidden"> </nav>
<span class="page-link px-3 py-2 border border-gray-200 bg-gray-50 text-gray-400 cursor-not-allowed">
<span class="fas fa-angle-double-right" aria-hidden="true"></span>
</span>
</li>
{% endif %}
</ul>
</nav>
</div> </div>

View File

@ -76,14 +76,16 @@
</div> </div>
<div class="d-flex align-items-center gap-2"> <div class="d-flex align-items-center gap-2">
{% if estimate.status == 'draft' %} {% if estimate.status == 'draft' %}
<a href="{% url 'send_email' request.dealer.slug estimate.pk %}" class="btn btn-phoenix-primary me-2"><span class="fa-regular fa-paper-plane me-sm-2"></span><span class="d-none d-sm-inline-block">{% trans 'Send Quotation' %}</span></a> {% if perms.django_ledger.change_estimatemodel %}
<button id="mark_as_sent_estimate" class="btn btn-phoenix-secondary" onclick="setFormAction('review')" data-bs-toggle="modal" data-bs-target="#confirmModal"><span class="d-none d-sm-inline-block"><i class="fa-solid fa-check-double"></i> {% trans 'Mark As Sent' %}</span></button> <button id="mark_as_sent_estimate" class="btn btn-phoenix-secondary" onclick="setFormAction('review')" data-bs-toggle="modal" data-bs-target="#confirmModal"><span class="d-none d-sm-inline-block"><i class="fa-solid fa-check-double"></i> {% trans 'Mark As Review' %}</span></button>
{% elif estimate.status == 'in_review' %} {% endif %}
{% if perms.django_ledger.can_approve_estimate %} {% elif estimate.status == 'in_review' %}
<button id="accept_estimate" onclick="setFormAction('approved')" class="btn btn-phoenix-secondary" data-bs-toggle="modal" data-bs-target="#confirmModal"><span class="d-none d-sm-inline-block"><i class="fa-solid fa-check-double"></i> {% trans 'Mark As Accept' %}</span></button> {% if perms.django_ledger.can_approve_estimatemodel %}
{% endif %} <button id="accept_estimate" onclick="setFormAction('approved')" class="btn btn-phoenix-secondary" data-bs-toggle="modal" data-bs-target="#confirmModal"><span class="d-none d-sm-inline-block"><i class="fa-solid fa-check-double"></i> {% trans 'Mark As Accept' %}</span></button>
{% endif %}
{% elif estimate.status == 'approved' %} {% elif estimate.status == 'approved' %}
<a href="{% url 'send_email' request.dealer.slug estimate.pk %}" class="btn btn-phoenix-primary me-2"><span class="fa-regular fa-paper-plane me-sm-2"></span><span class="d-none d-sm-inline-block">{% trans 'Send Quotation' %}</span></a>
{% if estimate.sale_orders.first %} {% if estimate.sale_orders.first %}
<!--if sale order exist--> <!--if sale order exist-->
@ -99,9 +101,9 @@
{% elif estimate.status == 'completed' %} {% elif estimate.status == 'completed' %}
<a href="{% url 'order_detail' request.dealer.slug estimate.sale_orders.first.pk %}" class="btn btn-phoenix-primary"><span class="d-none d-sm-inline-block">{{ _("Preview Sale Order") }}</span></a> <a href="{% url 'order_detail' request.dealer.slug estimate.sale_orders.first.pk %}" class="btn btn-phoenix-primary"><span class="d-none d-sm-inline-block">{{ _("Preview Sale Order") }}</span></a>
<a href="{% url 'invoice_detail' request.dealer.slug estimate.invoicemodel_set.first.pk %}" class="btn btn-phoenix-primary btn-sm" type="button"><i class="fa-solid fa-receipt"></i> <a href="{% url 'invoice_detail' request.dealer.slug estimate.invoicemodel_set.first.pk %}" class="btn btn-phoenix-primary btn-sm" type="button"><i class="fa-solid fa-receipt"></i>
{{ _("View Invoice")}}</a> {{ _("View Invoice")}}</a>
{% endif %} {% endif %}
{% if estimate.can_cancel %} {% if estimate.can_cancel %}

View File

@ -2,5 +2,5 @@ from django.contrib import admin
from . import models from . import models
# Register your models here. # Register your models here.
admin.site.register(models.Tour) # admin.site.register(models.Tour)
admin.site.register(models.TourCompletion) # admin.site.register(models.TourCompletion)

View File

@ -1,66 +0,0 @@
# Generated by Django 5.2.1 on 2025-06-06 14:05
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name="Tour",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=100)),
("description", models.TextField(blank=True)),
("slug", models.SlugField(unique=True)),
("tour_file", models.CharField(max_length=255)),
("is_active", models.BooleanField(default=True)),
],
),
migrations.CreateModel(
name="TourCompletion",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("completed_on", models.DateTimeField(auto_now_add=True)),
(
"tour",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="tours.tour"
),
),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"unique_together": {("tour", "user")},
},
),
]

View File

@ -1,21 +1,21 @@
from django.db import models from django.db import models
class Tour(models.Model): # class Tour(models.Model):
name = models.CharField(max_length=100) # name = models.CharField(max_length=100)
description = models.TextField(blank=True) # description = models.TextField(blank=True)
slug = models.SlugField(unique=True) # slug = models.SlugField(unique=True)
tour_file = models.CharField(max_length=255) # tour_file = models.CharField(max_length=255)
is_active = models.BooleanField(default=True) # is_active = models.BooleanField(default=True)
#
def __str__(self): # def __str__(self):
return self.name # return self.name
#
#
class TourCompletion(models.Model): # class TourCompletion(models.Model):
tour = models.ForeignKey(Tour, on_delete=models.CASCADE) # tour = models.ForeignKey(Tour, on_delete=models.CASCADE)
user = models.ForeignKey("auth.User", on_delete=models.CASCADE) # user = models.ForeignKey("auth.User", on_delete=models.CASCADE)
completed_on = models.DateTimeField(auto_now_add=True) # completed_on = models.DateTimeField(auto_now_add=True)
#
class Meta: # class Meta:
unique_together = ("tour", "user") # unique_together = ("tour", "user")

View File

@ -2,8 +2,8 @@ from django.urls import path
from . import views from . import views
urlpatterns = [ urlpatterns = [
path("", views.tour_list, name="tour_list"), # path("", views.tour_list, name="tour_list"),
path("data/<slug:slug>/", views.get_tour_data, name="get_tour_data"), # path("data/<slug:slug>/", views.get_tour_data, name="get_tour_data"),
path("complete/<slug:slug>/", views.mark_tour_completed, name="mark_tour_complete"), # path("complete/<slug:slug>/", views.mark_tour_completed, name="mark_tour_complete"),
path("start/<slug:slug>/", views.start_tour_view, name="start_tour"), # path("start/<slug:slug>/", views.start_tour_view, name="start_tour"),
] ]

View File

@ -1,54 +1,54 @@
import os # import os
import json # import json
from django.shortcuts import render, get_object_or_404 # from django.shortcuts import render, get_object_or_404
from django.http import JsonResponse # from django.http import JsonResponse
from django.contrib.auth.decorators import login_required # from django.contrib.auth.decorators import login_required
from django.conf import settings # from django.conf import settings
from .models import Tour, TourCompletion # from .models import Tour, TourCompletion
#
#
@login_required # @login_required
def tour_list(request): # def tour_list(request):
tours = Tour.objects.filter(is_active=True) # tours = Tour.objects.filter(is_active=True)
return render(request, "tours/tour_list.html", {"tours": tours}) # return render(request, "tours/tour_list.html", {"tours": tours})
#
#
@login_required # @login_required
def get_tour_data(request, slug): # def get_tour_data(request, slug):
tour = get_object_or_404(Tour, slug=slug, is_active=True) # tour = get_object_or_404(Tour, slug=slug, is_active=True)
#
# Check if user has already completed this tour # # Check if user has already completed this tour
completed = TourCompletion.objects.filter(tour=tour, user=request.user).exists() # completed = TourCompletion.objects.filter(tour=tour, user=request.user).exists()
#
# Load the tour data from JSON file # # Load the tour data from JSON file
tour_file_path = os.path.join( # tour_file_path = os.path.join(
settings.BASE_DIR, "static", "js", "tours", tour.tour_file # settings.BASE_DIR, "static", "js", "tours", tour.tour_file
) # )
#
try: # try:
with open(tour_file_path, "r") as f: # with open(tour_file_path, "r") as f:
tour_data = json.load(f) # tour_data = json.load(f)
except (FileNotFoundError, json.JSONDecodeError): # except (FileNotFoundError, json.JSONDecodeError):
return JsonResponse({"error": "Tour data not found or invalid"}, status=404) # return JsonResponse({"error": "Tour data not found or invalid"}, status=404)
#
return JsonResponse({"tour": tour_data, "completed": completed}) # return JsonResponse({"tour": tour_data, "completed": completed})
#
#
@login_required # @login_required
def mark_tour_completed(request, slug): # def mark_tour_completed(request, slug):
if request.method != "POST": # if request.method != "POST":
return JsonResponse({"error": "Method not allowed"}, status=405) # return JsonResponse({"error": "Method not allowed"}, status=405)
#
tour = get_object_or_404(Tour, slug=slug, is_active=True) # tour = get_object_or_404(Tour, slug=slug, is_active=True)
#
# Mark the tour as completed for this user # # Mark the tour as completed for this user
TourCompletion.objects.get_or_create(tour=tour, user=request.user) # TourCompletion.objects.get_or_create(tour=tour, user=request.user)
#
return JsonResponse({"status": "success"}) # return JsonResponse({"status": "success"})
#
#
@login_required # @login_required
def start_tour_view(request, slug): # def start_tour_view(request, slug):
tour = get_object_or_404(Tour, slug=slug, is_active=True) # tour = get_object_or_404(Tour, slug=slug, is_active=True)
# Redirect to the page where the tour should start # # Redirect to the page where the tour should start
return render(request, "tours/start_tour.html", {"tour": tour}) # return render(request, "tours/start_tour.html", {"tour": tour})

6284
uv.lock generated Normal file

File diff suppressed because it is too large Load Diff