Merge branch 'main' of http://10.10.1.136:3000/ismail/haikal into frontend
This commit is contained in:
commit
91db23e063
@ -279,6 +279,11 @@ urlpatterns = [
|
|||||||
views.CarFinanceUpdateView.as_view(),
|
views.CarFinanceUpdateView.as_view(),
|
||||||
name="car_finance_update",
|
name="car_finance_update",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"htmx/cars/bulk_update_car_price/",
|
||||||
|
views.bulk_update_car_price,
|
||||||
|
name="bulk_update_car_price",
|
||||||
|
),
|
||||||
path("ajax/", views.AjaxHandlerView.as_view(), name="ajax_handler"),
|
path("ajax/", views.AjaxHandlerView.as_view(), name="ajax_handler"),
|
||||||
path(
|
path(
|
||||||
"cars/<slug:slug>/add-color/", views.CarColorCreate.as_view(), name="add_color"
|
"cars/<slug:slug>/add-color/", views.CarColorCreate.as_view(), name="add_color"
|
||||||
|
|||||||
@ -23,6 +23,7 @@ from inventory.models import Status as LeadStatus
|
|||||||
from django.db import IntegrityError
|
from django.db import IntegrityError
|
||||||
from background_task.models import Task
|
from background_task.models import Task
|
||||||
from django.views.generic import FormView
|
from django.views.generic import FormView
|
||||||
|
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
|
||||||
@ -9287,4 +9288,27 @@ def upload_cars(request,pk=None):
|
|||||||
|
|
||||||
return render(request, 'csv_upload.html',{"make_data":models.CarMake.objects.all(),"form":form,"item":item})
|
return render(request, 'csv_upload.html',{"make_data":models.CarMake.objects.all(),"form":form,"item":item})
|
||||||
###############################################################
|
###############################################################
|
||||||
###############################################################
|
###############################################################
|
||||||
|
|
||||||
|
@require_http_methods(["POST"])
|
||||||
|
def bulk_update_car_price(request):
|
||||||
|
if request.method == "POST":
|
||||||
|
cars = request.POST.getlist('car')
|
||||||
|
price = request.POST.get('price')
|
||||||
|
|
||||||
|
if not price or int(price) <= 0:
|
||||||
|
messages.error(request, "Please enter a valid price")
|
||||||
|
elif not cars:
|
||||||
|
messages.error(request, "No cars selected for price update")
|
||||||
|
else:
|
||||||
|
for car_pk in cars:
|
||||||
|
car_finance , created = models.CarFinance.objects.get_or_create(car__pk=car_pk, cost_price=Decimal(price),selling_price=0)
|
||||||
|
if not created:
|
||||||
|
car_finance.cost_price = Decimal(price)
|
||||||
|
car_finance.selling_price = 0
|
||||||
|
car_finance.save()
|
||||||
|
messages.success(request, "Price updated successfully")
|
||||||
|
|
||||||
|
response = HttpResponse()
|
||||||
|
response['HX-Redirect'] = reverse('car_list')
|
||||||
|
return response
|
||||||
|
|||||||
@ -41,7 +41,7 @@
|
|||||||
<div class="tab-pane active" role="tabpanel" aria-labelledby="bootstrap-wizard-validation-tab1" id="bootstrap-wizard-validation-tab1">
|
<div class="tab-pane active" role="tabpanel" aria-labelledby="bootstrap-wizard-validation-tab1" id="bootstrap-wizard-validation-tab1">
|
||||||
<form class="needs-validation" id="wizardValidationForm1" novalidate="novalidate" data-wizard-form="1">
|
<form class="needs-validation" id="wizardValidationForm1" novalidate="novalidate" data-wizard-form="1">
|
||||||
{{form1|crispy}}
|
{{form1|crispy}}
|
||||||
<a class="fs-10 text-decoration-none" href="{% url 'terms_and_privacy' %}" target="_blank">{{ _("Read Terms of Service and Privacy Policy")}}</a>
|
<a class="fs-10 text-decoration-none" href="{% url 'terms_and_privacy' %}" target="_blank">{{ _("Read Terms of Service and Privacy Policy")}}</a>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-pane" role="tabpanel" aria-labelledby="bootstrap-wizard-validation-tab2" id="bootstrap-wizard-validation-tab2">
|
<div class="tab-pane" role="tabpanel" aria-labelledby="bootstrap-wizard-validation-tab2" id="bootstrap-wizard-validation-tab2">
|
||||||
|
|||||||
@ -1,74 +1,74 @@
|
|||||||
|
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load i18n custom_filters %}
|
{% load i18n custom_filters %}
|
||||||
{% block title %}{% trans "Accounts" %}{% endblock title %}
|
{% block title %}{% trans "Accounts" %}{% endblock title %}
|
||||||
{% block accounts %}
|
{% block accounts %}
|
||||||
<a class="nav-link active fw-bold">
|
<a class="nav-link active fw-bold">
|
||||||
{% trans "Accounts"|capfirst %}
|
{% trans "Accounts"|capfirst %}
|
||||||
<span class="visually-hidden">(current)</span>
|
<span class="visually-hidden">(current)</span>
|
||||||
</a>
|
</a>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row mt-4">
|
<div class="row mt-4">
|
||||||
|
|
||||||
<div class="d-flex justify-content-between mb-2">
|
<div class="d-flex justify-content-between mb-2">
|
||||||
<h3 class=""><i class="fas fa-right-to-bracket me-2"></i> {% trans "Audit Log Dashboard" %}</h3>
|
<h3 class=""><i class="fas fa-right-to-bracket me-2"></i> {% trans "Audit Log Dashboard" %}</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Log Type Tabs -->
|
<!-- Log Type Tabs -->
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
{% include 'admin_management/nav.html' %}
|
{% include 'admin_management/nav.html' %}
|
||||||
|
|
||||||
<div class="tab-content p-3 border border-top-0 rounded-bottom" id="accountTypeTabsContent">
|
<div class="tab-content p-3 border border-top-0 rounded-bottom" id="accountTypeTabsContent">
|
||||||
<!-- modellogs Tab -->
|
<!-- modellogs Tab -->
|
||||||
|
|
||||||
{% if page_obj %}
|
{% if page_obj %}
|
||||||
<div class="table-responsive px-1 scrollbar mt-3">
|
<div class="table-responsive px-1 scrollbar mt-3">
|
||||||
<table class= "table align-items-center table-flush table-hover">
|
<table class= "table align-items-center table-flush table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="bg-body-highlight">
|
<tr class="bg-body-highlight">
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{{ _("Timestamp") |capfirst }}</th>
|
<th class="sort white-space-nowrap align-middle" scope="col">{{ _("Timestamp") |capfirst }}</th>
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{{ _("User") |capfirst }}</th>
|
<th class="sort white-space-nowrap align-middle" scope="col">{{ _("User") |capfirst }}</th>
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{{ _("Event Type") }}</th>
|
<th class="sort white-space-nowrap align-middle" scope="col">{{ _("Event Type") }}</th>
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{{ _("username") |capfirst }}</th>
|
<th class="sort white-space-nowrap align-middle" scope="col">{{ _("username") |capfirst }}</th>
|
||||||
<th class="sort white-space-nowrap align-middle"scope="col">{{ _("IP Address") |capfirst }}</th>
|
<th class="sort white-space-nowrap align-middle"scope="col">{{ _("IP Address") |capfirst }}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="list">
|
<tbody class="list">
|
||||||
{% for event in page_obj.object_list %}
|
{% for event in page_obj.object_list %}
|
||||||
<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">{{event.datetime}}</td>
|
<td class="align-middle product white-space-nowrap">{{event.datetime}}</td>
|
||||||
<td class="align-middle product white-space-nowrap">{{ event.user.username|default:"N/A" }}</td>
|
<td class="align-middle product white-space-nowrap">{{ event.user.username|default:"N/A" }}</td>
|
||||||
<td class="align-middle product white-space-nowrap">{{ event.get_login_type_display}}</td>
|
<td class="align-middle product white-space-nowrap">{{ event.get_login_type_display}}</td>
|
||||||
<td class="align-middle product white-space-nowrap">{{ event.username}}</td>
|
<td class="align-middle product white-space-nowrap">{{ event.username}}</td>
|
||||||
<td class="align-middle product white-space-nowrap">{{ event.remote_ip}}</td>
|
<td class="align-middle product white-space-nowrap">{{ event.remote_ip}}</td>
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="d-flex justify-content-end mt-3">
|
<div class="d-flex justify-content-end mt-3">
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
{% include 'partials/pagination.html' with q='loginEvents' %}
|
{% include 'partials/pagination.html' with q='loginEvents' %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>No authentication audit events found.</p>
|
<p>No authentication audit events found.</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|||||||
@ -2,28 +2,28 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{%block title%} {%trans 'Admin Management' %} {%endblock%}
|
{%block title%} {%trans 'Admin Management' %} {%endblock%}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row row-cols-1 row-cols-sm-2 row-cols-md-4 g-4 mt-10">
|
<div class="row row-cols-1 row-cols-sm-2 row-cols-md-4 g-4 mt-10">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<a href="{% url 'user_management' %}">
|
<a href="{% url 'user_management' %}">
|
||||||
<div class="card h-100">
|
<div class="card h-100">
|
||||||
<div class="card-header text-center">
|
<div class="card-header text-center">
|
||||||
<h5 class="card-title">{{ _("User Management")}}</h5>
|
<h5 class="card-title">{{ _("User Management")}}</h5>
|
||||||
<span class="me-2"><i class="fas fa-user fa-2x"></i></span>
|
<span class="me-2"><i class="fas fa-user fa-2x"></i></span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</a>
|
||||||
</a>
|
</div>
|
||||||
</div>
|
<div class="col">
|
||||||
<div class="col">
|
<a href="{% url 'audit_log_dashboard' %}">
|
||||||
<a href="{% url 'audit_log_dashboard' %}">
|
<div class="card h-100">
|
||||||
<div class="card h-100">
|
<div class="card-header text-center">
|
||||||
<div class="card-header text-center">
|
<h5 class="card-title">{{ _("Audit Log Dashboard")}}</h5>
|
||||||
<h5 class="card-title">{{ _("Audit Log Dashboard")}}</h5>
|
<span class="me-2"><i class="fas fa-user fa-2x"></i></span>
|
||||||
<span class="me-2"><i class="fas fa-user fa-2x"></i></span>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</a>
|
||||||
</a>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
@ -1,133 +1,133 @@
|
|||||||
|
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load i18n custom_filters %}
|
{% load i18n custom_filters %}
|
||||||
{% block title %}{% trans "Accounts" %}{% endblock title %}
|
{% block title %}{% trans "Accounts" %}{% endblock title %}
|
||||||
{% block accounts %}
|
{% block accounts %}
|
||||||
<a class="nav-link active fw-bold">
|
<a class="nav-link active fw-bold">
|
||||||
{% trans "Accounts"|capfirst %}
|
{% trans "Accounts"|capfirst %}
|
||||||
<span class="visually-hidden">(current)</span>
|
<span class="visually-hidden">(current)</span>
|
||||||
</a>
|
</a>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row mt-4">
|
<div class="row mt-4">
|
||||||
|
|
||||||
<div class="d-flex justify-content-between mb-2">
|
<div class="d-flex justify-content-between mb-2">
|
||||||
<h3 class=""><i class="fas fa-history me-2"></i>{% trans "Audit Log Dashboard" %}</h3>
|
<h3 class=""><i class="fas fa-history me-2"></i>{% trans "Audit Log Dashboard" %}</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Log Type Tabs -->
|
<!-- Log Type Tabs -->
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
{% include 'admin_management/nav.html' %}
|
{% include 'admin_management/nav.html' %}
|
||||||
|
|
||||||
<div class="tab-content p-3 border border-top-0 rounded-bottom" id="accountTypeTabsContent">
|
<div class="tab-content p-3 border border-top-0 rounded-bottom" id="accountTypeTabsContent">
|
||||||
<!-- modellogs Tab -->
|
<!-- modellogs Tab -->
|
||||||
|
|
||||||
{% if page_obj %}
|
{% if page_obj %}
|
||||||
<div class="table-responsive px-1 scrollbar mt-3">
|
<div class="table-responsive px-1 scrollbar mt-3">
|
||||||
<table class="table align-items-center table-flush table-hover mt-3">
|
<table class="table align-items-center table-flush table-hover mt-3">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="bg-body-highlight">
|
<tr class="bg-body-highlight">
|
||||||
<th>{% trans "Timestamp" %}</th>
|
<th>{% trans "Timestamp" %}</th>
|
||||||
<th>{% trans "User" %}</th>
|
<th>{% trans "User" %}</th>
|
||||||
<th>{% trans "Action" %}</th>
|
<th>{% trans "Action" %}</th>
|
||||||
<th>{% trans "Model" %}</th>
|
<th>{% trans "Model" %}</th>
|
||||||
<th>{% trans "Object ID" %}</th>
|
<th>{% trans "Object ID" %}</th>
|
||||||
<th>{% trans "Object Representation" %}</th>
|
<th>{% trans "Object Representation" %}</th>
|
||||||
<th>{% trans "Field" %}</th> {# Dedicated column for field name #}
|
<th>{% trans "Field" %}</th> {# Dedicated column for field name #}
|
||||||
<th>{% trans "Old Value" %}</th> {# Dedicated column for old value #}
|
<th>{% trans "Old Value" %}</th> {# Dedicated column for old value #}
|
||||||
<th>{% trans "New Value" %}</th> {# Dedicated column for new value #}
|
<th>{% trans "New Value" %}</th> {# Dedicated column for new value #}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for event in page_obj.object_list %}
|
{% for event in page_obj.object_list %}
|
||||||
{% if event.field_changes %}
|
{% if event.field_changes %}
|
||||||
{# Loop through each individual field change for this event #}
|
{# Loop through each individual field change for this event #}
|
||||||
{% for change in event.field_changes %}
|
{% for change in event.field_changes %}
|
||||||
<tr>
|
<tr>
|
||||||
{# Display common event details using rowspan for the first change #}
|
{# Display common event details using rowspan for the first change #}
|
||||||
{% if forloop.first %}
|
{% if forloop.first %}
|
||||||
<td rowspan="{{ event.field_changes|length }}">
|
<td rowspan="{{ event.field_changes|length }}">
|
||||||
{{ event.datetime|date:"Y-m-d H:i:s" }}
|
{{ event.datetime|date:"Y-m-d H:i:s" }}
|
||||||
</td>
|
</td>
|
||||||
<td rowspan="{{ event.field_changes|length }}">
|
<td rowspan="{{ event.field_changes|length }}">
|
||||||
{{ event.user.username|default:"Anonymous" }}
|
{{ event.user.username|default:"Anonymous" }}
|
||||||
</td>
|
</td>
|
||||||
<td rowspan="{{ event.field_changes|length }}">
|
<td rowspan="{{ event.field_changes|length }}">
|
||||||
{{ event.event_type_display }}
|
{{ event.event_type_display }}
|
||||||
</td>
|
</td>
|
||||||
<td rowspan="{{ event.field_changes|length }}">
|
<td rowspan="{{ event.field_changes|length }}">
|
||||||
{{ event.model_name|title }}
|
{{ event.model_name|title }}
|
||||||
</td>
|
</td>
|
||||||
<td rowspan="{{ event.field_changes|length }}">
|
<td rowspan="{{ event.field_changes|length }}">
|
||||||
{{ event.object_id }}
|
{{ event.object_id }}
|
||||||
</td>
|
</td>
|
||||||
<td rowspan="{{ event.field_changes|length }}">
|
<td rowspan="{{ event.field_changes|length }}">
|
||||||
{{ event.object_repr }}
|
{{ event.object_repr }}
|
||||||
</td>
|
</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{# Display the specific field change details in their own columns #}
|
{# Display the specific field change details in their own columns #}
|
||||||
<td><strong>{{ change.field }}</strong></td>
|
<td><strong>{{ change.field }}</strong></td>
|
||||||
<td>
|
<td>
|
||||||
{% if change.old is not None %}
|
{% if change.old is not None %}
|
||||||
<pre style="white-space: pre-wrap; word-break: break-all; font-size: 0.85em; background-color: #f8f9fa; padding: 5px; border-radius: 3px;">{{ change.old }}</pre>
|
<pre style="white-space: pre-wrap; word-break: break-all; font-size: 0.85em; background-color: #f8f9fa; padding: 5px; border-radius: 3px;">{{ change.old }}</pre>
|
||||||
{% else %}
|
{% else %}
|
||||||
(None)
|
(None)
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{% if change.new is not None %}
|
{% if change.new is not None %}
|
||||||
<pre style="white-space: pre-wrap; word-break: break-all; font-size: 0.85em; background-color: #f8f9fa; padding: 5px; border-radius: 3px;">{{ change.new }}</pre>
|
<pre style="white-space: pre-wrap; word-break: break-all; font-size: 0.85em; background-color: #f8f9fa; padding: 5px; border-radius: 3px;">{{ change.new }}</pre>
|
||||||
{% else %}
|
{% else %}
|
||||||
(None)
|
(None)
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{# Fallback for events with no specific field changes (e.g., CREATE, DELETE) #}
|
{# Fallback for events with no specific field changes (e.g., CREATE, DELETE) #}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ event.datetime|date:"Y-m-d H:i:s" }}</td>
|
<td>{{ event.datetime|date:"Y-m-d H:i:s" }}</td>
|
||||||
<td>{{ event.user.username|default:"Anonymous" }}</td>
|
<td>{{ event.user.username|default:"Anonymous" }}</td>
|
||||||
<td>{{ event.event_type_display }}</td>
|
<td>{{ event.event_type_display }}</td>
|
||||||
<td>{{ event.model_name|title }}</td>
|
<td>{{ event.model_name|title }}</td>
|
||||||
<td>{{ event.object_id }}</td>
|
<td>{{ event.object_id }}</td>
|
||||||
<td>{{ event.object_repr }}</td>
|
<td>{{ event.object_repr }}</td>
|
||||||
{# Span the 'Field', 'Old Value', 'New Value' columns #}
|
{# Span the 'Field', 'Old Value', 'New Value' columns #}
|
||||||
<td>
|
<td>
|
||||||
{% if event.event_type_display == "Create" %}
|
{% if event.event_type_display == "Create" %}
|
||||||
{% trans "Object created." %}
|
{% trans "Object created." %}
|
||||||
{% elif event.event_type_display == "Delete" %}
|
{% elif event.event_type_display == "Delete" %}
|
||||||
{% trans "Object deleted." %}
|
{% trans "Object deleted." %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% trans "No specific field changes recorded." %}
|
{% trans "No specific field changes recorded." %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-flex justify-content-end mt-3">
|
<div class="d-flex justify-content-end mt-3">
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
{% include 'partials/pagination.html' with q='userActions' %}
|
{% include 'partials/pagination.html' with q='userActions' %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>{% trans "No model change audit events found." %}</p>
|
<p>{% trans "No model change audit events found." %}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|||||||
@ -1,19 +1,19 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
<ul class="nav nav-tabs" id="accountTypeTabs" role="tablist">
|
<ul class="nav nav-tabs" id="accountTypeTabs" role="tablist">
|
||||||
|
|
||||||
<li class="nav-item me-3" role="presentation">
|
<li class="nav-item me-3" role="presentation">
|
||||||
<a href="{% url 'audit_log_dashboard' %}?q=userActions">
|
<a href="{% url 'audit_log_dashboard' %}?q=userActions">
|
||||||
<i class="fas fa-history me-2"></i>{% trans "User Actions" %}
|
<i class="fas fa-history me-2"></i>{% trans "User Actions" %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item me-3" role="presentation">
|
<li class="nav-item me-3" role="presentation">
|
||||||
<a href="{% url 'audit_log_dashboard' %}?q=loginEvents">
|
<a href="{% url 'audit_log_dashboard' %}?q=loginEvents">
|
||||||
<i class="fas fa-right-to-bracket me-2"></i>{% trans "User Login Events" %}
|
<i class="fas fa-right-to-bracket me-2"></i>{% trans "User Login Events" %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<a href="{% url 'audit_log_dashboard' %}?q=userRequests">
|
<a href="{% url 'audit_log_dashboard' %}?q=userRequests">
|
||||||
<i class="fas fa-file-alt me-2"></i>{% trans "User Page Requests" %}
|
<i class="fas fa-file-alt me-2"></i>{% trans "User Page Requests" %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@ -1,69 +1,69 @@
|
|||||||
|
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load i18n custom_filters %}
|
{% load i18n custom_filters %}
|
||||||
{% block title %}{% trans "Accounts" %}{% endblock title %}
|
{% block title %}{% trans "Accounts" %}{% endblock title %}
|
||||||
{% block accounts %}
|
{% block accounts %}
|
||||||
<a class="nav-link active fw-bold">
|
<a class="nav-link active fw-bold">
|
||||||
{% trans "Accounts"|capfirst %}
|
{% trans "Accounts"|capfirst %}
|
||||||
<span class="visually-hidden">(current)</span>
|
<span class="visually-hidden">(current)</span>
|
||||||
</a>
|
</a>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row mt-4">
|
<div class="row mt-4">
|
||||||
|
|
||||||
<div class="d-flex justify-content-between mb-2">
|
<div class="d-flex justify-content-between mb-2">
|
||||||
<h3 class=""><i class="fas fa-file-alt me-2"></i> {% trans "Audit Log Dashboard" %}</h3>
|
<h3 class=""><i class="fas fa-file-alt me-2"></i> {% trans "Audit Log Dashboard" %}</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Log Type Tabs -->
|
<!-- Log Type Tabs -->
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
{% include 'admin_management/nav.html' %}
|
{% include 'admin_management/nav.html' %}
|
||||||
|
|
||||||
<div class="tab-content p-3 border border-top-0 rounded-bottom" id="accountTypeTabsContent">
|
<div class="tab-content p-3 border border-top-0 rounded-bottom" id="accountTypeTabsContent">
|
||||||
<!-- modellogs Tab -->
|
<!-- modellogs Tab -->
|
||||||
{% if page_obj %}
|
{% if page_obj %}
|
||||||
<div class="table-responsive px-1 scrollbar mt-3">
|
<div class="table-responsive px-1 scrollbar mt-3">
|
||||||
<table class= "table align-items-center table-flush table-hover">
|
<table class= "table align-items-center table-flush table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="bg-body-highlight">
|
<tr class="bg-body-highlight">
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{{ _("Timestamp") |capfirst }}</th>
|
<th class="sort white-space-nowrap align-middle" scope="col">{{ _("Timestamp") |capfirst }}</th>
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{{ _("User") |capfirst }}</th>
|
<th class="sort white-space-nowrap align-middle" scope="col">{{ _("User") |capfirst }}</th>
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{{ _("URL") }}</th>
|
<th class="sort white-space-nowrap align-middle" scope="col">{{ _("URL") }}</th>
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{{ _("Method") |capfirst }}</th>
|
<th class="sort white-space-nowrap align-middle" scope="col">{{ _("Method") |capfirst }}</th>
|
||||||
<th class="sort white-space-nowrap align-middle"scope="col">{{ _("IP Address") |capfirst }}</th>
|
<th class="sort white-space-nowrap align-middle"scope="col">{{ _("IP Address") |capfirst }}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="list">
|
<tbody class="list">
|
||||||
{% for event in page_obj.object_list %}
|
{% for event in page_obj.object_list %}
|
||||||
<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">{{event.datetime}}</td>
|
<td class="align-middle product white-space-nowrap">{{event.datetime}}</td>
|
||||||
<td class="align-middle product white-space-nowrap">{{ event.user.username|default:"Anonymous" }}</td>
|
<td class="align-middle product white-space-nowrap">{{ event.user.username|default:"Anonymous" }}</td>
|
||||||
<td class="align-middle product white-space-nowrap">{{ event.url }}</td>
|
<td class="align-middle product white-space-nowrap">{{ event.url }}</td>
|
||||||
<td class="align-middle product white-space-nowrap">{{ event.method}}</td>
|
<td class="align-middle product white-space-nowrap">{{ event.method}}</td>
|
||||||
<td class="align-middle product white-space-nowrap">{{ event.remote_ip}}</td>
|
<td class="align-middle product white-space-nowrap">{{ event.remote_ip}}</td>
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-flex justify-content-end mt-3">
|
<div class="d-flex justify-content-end mt-3">
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
{% include 'partials/pagination.html' with q='userRequests' %}
|
{% include 'partials/pagination.html' with q='userRequests' %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>No request audit events found.</p>
|
<p>No request audit events found.</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|||||||
@ -65,13 +65,13 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex justify-content-end mt-3">
|
<div class="d-flex justify-content-end mt-3">
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
{% if is_paginated %}
|
{% if is_paginated %}
|
||||||
{% include 'partials/pagination.html' %}
|
{% include 'partials/pagination.html' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="row mt-5">
|
<div class="row mt-5">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
@ -129,13 +129,13 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex justify-content-end mt-3">
|
<div class="d-flex justify-content-end mt-3">
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
{% if is_paginated %}
|
{% if is_paginated %}
|
||||||
{% include 'partials/pagination.html' %}
|
{% include 'partials/pagination.html' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="row mt-5">
|
<div class="row mt-5">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
@ -193,13 +193,13 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex justify-content-end mt-3">
|
<div class="d-flex justify-content-end mt-3">
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
{% if is_paginated %}
|
{% if is_paginated %}
|
||||||
{% include 'partials/pagination.html' %}
|
{% include 'partials/pagination.html' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="row mt-5">
|
<div class="row mt-5">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
@ -257,13 +257,13 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex justify-content-end mt-3">
|
<div class="d-flex justify-content-end mt-3">
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
{% if is_paginated %}
|
{% if is_paginated %}
|
||||||
{% include 'partials/pagination.html' %}
|
{% include 'partials/pagination.html' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@ -40,7 +40,7 @@
|
|||||||
<link href="{% static 'vendors/flatpickr/flatpickr.min.css' %}" rel="stylesheet">
|
<link href="{% static 'vendors/flatpickr/flatpickr.min.css' %}" rel="stylesheet">
|
||||||
<link href="{% static 'css/custom.css' %}" rel="stylesheet">
|
<link href="{% static 'css/custom.css' %}" rel="stylesheet">
|
||||||
<link rel="stylesheet" href="https://unicons.iconscout.com/release/v4.0.8/css/line.css">
|
<link rel="stylesheet" href="https://unicons.iconscout.com/release/v4.0.8/css/line.css">
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/intro.js/7.2.0/introjs.css" integrity="sha512-4OzqLjfh1aJa7M33b5+h0CSx0Q3i9Qaxlrr1T/Z+Vz+9zs5A7GM3T3MFKXoreghi3iDOSbkPMXiMBhFO7UBW/g==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/intro.js/7.2.0/introjs.css" integrity="sha512-4OzqLjfh1aJa7M33b5+h0CSx0Q3i9Qaxlrr1T/Z+Vz+9zs5A7GM3T3MFKXoreghi3iDOSbkPMXiMBhFO7UBW/g==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||||
{% if LANGUAGE_CODE == 'ar' %}
|
{% if LANGUAGE_CODE == 'ar' %}
|
||||||
<link href="{% static 'css/theme-rtl.min.css' %}" type="text/css" rel="stylesheet" id="style-rtl">
|
<link href="{% static 'css/theme-rtl.min.css' %}" type="text/css" rel="stylesheet" id="style-rtl">
|
||||||
<link href="{% static 'css/user-rtl.min.css' %}" type="text/css" rel="stylesheet" id="user-style-rtl">
|
<link href="{% static 'css/user-rtl.min.css' %}" type="text/css" rel="stylesheet" id="user-style-rtl">
|
||||||
@ -100,9 +100,9 @@
|
|||||||
<script src="{% static 'vendors/fontawesome/all.min.js' %}"></script>
|
<script src="{% static 'vendors/fontawesome/all.min.js' %}"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/intro.js/7.2.0/intro.js" integrity="sha512-f26fxKZJiF0AjutUaQHNJ5KnXSisqyUQ3oyfaoen2apB1wLa5ccW3lmtaRe2jdP5kh4LF2gAHP9xQbx7wYhU5w==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/intro.js/7.2.0/intro.js" integrity="sha512-f26fxKZJiF0AjutUaQHNJ5KnXSisqyUQ3oyfaoen2apB1wLa5ccW3lmtaRe2jdP5kh4LF2gAHP9xQbx7wYhU5w==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||||
<!-- Tour Manager -->
|
<!-- Tour Manager -->
|
||||||
<script src="{% static 'js/tours/tour-manager.js' %}"></script>
|
<script src="{% static 'js/tours/tour-manager.js' %}"></script>
|
||||||
|
|
||||||
<script src="{% static 'js/tours/help-button.js' %}"></script>
|
<script src="{% static 'js/tours/help-button.js' %}"></script>
|
||||||
<script src="{% static 'vendors/lodash/lodash.min.js' %}"></script>
|
<script src="{% static 'vendors/lodash/lodash.min.js' %}"></script>
|
||||||
<script src="{% static 'vendors/list.js/list.min.js' %}"></script>
|
<script src="{% static 'vendors/list.js/list.min.js' %}"></script>
|
||||||
<script src="{% static 'vendors/feather-icons/feather.min.js' %}"></script>
|
<script src="{% static 'vendors/feather-icons/feather.min.js' %}"></script>
|
||||||
|
|||||||
@ -7,236 +7,236 @@
|
|||||||
{% block title %}Bill Details - {{ block.super }}{% endblock %}
|
{% block title %}Bill Details - {{ block.super }}{% endblock %}
|
||||||
|
|
||||||
{% block customCSS %}
|
{% block customCSS %}
|
||||||
<style>
|
<style>
|
||||||
/* Optional custom overrides for Bootstrap 5 */
|
/* Optional custom overrides for Bootstrap 5 */
|
||||||
.table th,
|
.table th,
|
||||||
.table td {
|
.table td {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-header i {
|
.card-header i {
|
||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-xs {
|
.text-xs {
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-xxs {
|
.text-xxs {
|
||||||
font-size: 0.6rem;
|
font-size: 0.6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#djl-vendor-card-widget{
|
#djl-vendor-card-widget{
|
||||||
|
|
||||||
max-height:30rem;
|
max-height:30rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container-fluid py-4">
|
<div class="container-fluid py-4">
|
||||||
<div class="row g-4" >
|
<div class="row g-4" >
|
||||||
<!-- Left Sidebar -->
|
<!-- Left Sidebar -->
|
||||||
<div class="col-lg-4">
|
<div class="col-lg-4">
|
||||||
<div class="card shadow-sm" >
|
<div class="card shadow-sm" >
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
{% include 'bill/includes/card_bill.html' with bill=bill entity_slug=view.kwargs.entity_slug style='bill-detail' %}
|
{% include 'bill/includes/card_bill.html' with bill=bill entity_slug=view.kwargs.entity_slug style='bill-detail' %}
|
||||||
<hr class="my-4">
|
<hr class="my-4">
|
||||||
{% include 'bill/includes/card_vendor.html' with vendor=bill.vendor %}
|
{% include 'bill/includes/card_vendor.html' with vendor=bill.vendor %}
|
||||||
<div class="d-grid mt-4">
|
<div class="d-grid mt-4">
|
||||||
<a href="{% url 'bill_list' %}"
|
<a href="{% url 'bill_list' %}"
|
||||||
class="btn btn-phoenix-primary">
|
class="btn btn-phoenix-primary">
|
||||||
<i class="fas fa-arrow-left me-1"></i> {% trans 'Bill List' %}
|
<i class="fas fa-arrow-left me-1"></i> {% trans 'Bill List' %}
|
||||||
</a>
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Main Content -->
|
<!-- Main Content -->
|
||||||
<div class="col-lg-8 ">
|
<div class="col-lg-8 ">
|
||||||
{% if bill.is_configured %}
|
{% if bill.is_configured %}
|
||||||
<div class="card mb-4 shadow-sm">
|
<div class="card mb-4 shadow-sm">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="row text-center g-3">
|
<div class="row text-center g-3">
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<div class="border rounded p-3">
|
<div class="border rounded p-3">
|
||||||
<h6 class="text-uppercase text-xs text-muted mb-2">
|
<h6 class="text-uppercase text-xs text-muted mb-2">
|
||||||
{% trans 'Cash Account' %}:
|
{% trans 'Cash Account' %}:
|
||||||
<a href="{% url 'account_detail' bill.cash_account.uuid %}"
|
<a href="{% url 'account_detail' bill.cash_account.uuid %}"
|
||||||
class="text-decoration-none ms-1">
|
class="text-decoration-none ms-1">
|
||||||
{{ bill.cash_account.code }}
|
{{ bill.cash_account.code }}
|
||||||
</a>
|
</a>
|
||||||
</h6>
|
</h6>
|
||||||
<h4 class="mb-0" id="djl-bill-detail-amount-paid">
|
<h4 class="mb-0" id="djl-bill-detail-amount-paid">
|
||||||
{% currency_symbol %}{{ bill.get_amount_cash | absolute | currency_format }}
|
{% currency_symbol %}{{ bill.get_amount_cash | absolute | currency_format }}
|
||||||
</h4>
|
</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% if bill.accrue %}
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="border rounded p-3">
|
||||||
|
<h6 class="text-uppercase text-xs text-muted mb-2">
|
||||||
|
{% trans 'Prepaid Account' %}:
|
||||||
|
<a href="{% url 'account_detail' bill.prepaid_account.uuid %}"
|
||||||
|
class="text-decoration-none ms-1">
|
||||||
|
{{ bill.prepaid_account.code }}
|
||||||
|
</a>
|
||||||
|
</h6>
|
||||||
|
<h4 class="text-success mb-0" id="djl-bill-detail-amount-prepaid">
|
||||||
|
{% currency_symbol %}{{ bill.get_amount_prepaid | currency_format }}
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="border rounded p-3">
|
||||||
|
<h6 class="text-uppercase text-xs text-muted mb-2">
|
||||||
|
{% trans 'Accounts Payable' %}:
|
||||||
|
<a href="{% url 'account_detail' bill.unearned_account.uuid %}"
|
||||||
|
class="text-decoration-none ms-1">
|
||||||
|
{{ bill.unearned_account.code }}
|
||||||
|
</a>
|
||||||
|
</h6>
|
||||||
|
<h4 class="text-danger mb-0" id="djl-bill-detail-amount-unearned">
|
||||||
|
{% currency_symbol %}{{ bill.get_amount_unearned | currency_format }}
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="border rounded p-3">
|
||||||
|
<h6 class="text-uppercase text-xs text-muted mb-2">
|
||||||
|
{% trans 'Accrued' %} {{ bill.get_progress | percentage }}
|
||||||
|
</h6>
|
||||||
|
<h4 class="mb-0">
|
||||||
|
{% currency_symbol %}{{ bill.get_amount_earned | currency_format }}
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="col-md-3 offset-md-6">
|
||||||
|
<div class="border rounded p-3">
|
||||||
|
<h6 class="text-uppercase text-xs text-muted mb-2">
|
||||||
|
{% trans 'You Still Owe' %}
|
||||||
|
</h6>
|
||||||
|
<h4 class="text-danger mb-0" id="djl-bill-detail-amount-owed">
|
||||||
|
{% currency_symbol %}{{ bill.get_amount_open | currency_format }}
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% if bill.accrue %}
|
|
||||||
<div class="col-md-3">
|
|
||||||
<div class="border rounded p-3">
|
|
||||||
<h6 class="text-uppercase text-xs text-muted mb-2">
|
|
||||||
{% trans 'Prepaid Account' %}:
|
|
||||||
<a href="{% url 'account_detail' bill.prepaid_account.uuid %}"
|
|
||||||
class="text-decoration-none ms-1">
|
|
||||||
{{ bill.prepaid_account.code }}
|
|
||||||
</a>
|
|
||||||
</h6>
|
|
||||||
<h4 class="text-success mb-0" id="djl-bill-detail-amount-prepaid">
|
|
||||||
{% currency_symbol %}{{ bill.get_amount_prepaid | currency_format }}
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-3">
|
|
||||||
<div class="border rounded p-3">
|
|
||||||
<h6 class="text-uppercase text-xs text-muted mb-2">
|
|
||||||
{% trans 'Accounts Payable' %}:
|
|
||||||
<a href="{% url 'account_detail' bill.unearned_account.uuid %}"
|
|
||||||
class="text-decoration-none ms-1">
|
|
||||||
{{ bill.unearned_account.code }}
|
|
||||||
</a>
|
|
||||||
</h6>
|
|
||||||
<h4 class="text-danger mb-0" id="djl-bill-detail-amount-unearned">
|
|
||||||
{% currency_symbol %}{{ bill.get_amount_unearned | currency_format }}
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-3">
|
|
||||||
<div class="border rounded p-3">
|
|
||||||
<h6 class="text-uppercase text-xs text-muted mb-2">
|
|
||||||
{% trans 'Accrued' %} {{ bill.get_progress | percentage }}
|
|
||||||
</h6>
|
|
||||||
<h4 class="mb-0">
|
|
||||||
{% currency_symbol %}{{ bill.get_amount_earned | currency_format }}
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<div class="col-md-3 offset-md-6">
|
|
||||||
<div class="border rounded p-3">
|
|
||||||
<h6 class="text-uppercase text-xs text-muted mb-2">
|
|
||||||
{% trans 'You Still Owe' %}
|
|
||||||
</h6>
|
|
||||||
<h4 class="text-danger mb-0" id="djl-bill-detail-amount-owed">
|
|
||||||
{% currency_symbol %}{{ bill.get_amount_open | currency_format }}
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{% endif %}
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<!-- Bill Items Card -->
|
<!-- Bill Items Card -->
|
||||||
<div class="card mb-4 shadow-sm">
|
<div class="card mb-4 shadow-sm">
|
||||||
<div class="card-header pb-0">
|
<div class="card-header pb-0">
|
||||||
<div class="d-flex align-items-center mb-1">
|
<div class="d-flex align-items-center mb-1">
|
||||||
<i class="fas fa-receipt me-3 text-primary"></i>
|
<i class="fas fa-receipt me-3 text-primary"></i>
|
||||||
<h5 class="mb-0">{% trans 'Bill Items' %}</h5>
|
<h5 class="mb-0">{% trans 'Bill Items' %}</h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body px-0 pt-0 pb-2">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover">
|
||||||
|
<thead >
|
||||||
|
<tr class="bg-body-highlight">
|
||||||
|
<th class="sort white-space-nowrap align-middle" scope="col">{% trans 'Item' %}</th>
|
||||||
|
<th class="sort white-space-nowrap align-middle" scope="col">{% trans 'Entity Unit' %}</th>
|
||||||
|
<th class="sort white-space-nowrap align-middle" scope="col">{% trans 'Unit Cost' %}</th>
|
||||||
|
<th class="sort white-space-nowrap align-middle" scope="col">{% trans 'Quantity' %}</th>
|
||||||
|
<th class="sort white-space-nowrap align-middle" scope="col">{% trans 'Total' %}</th>
|
||||||
|
<th class="sort white-space-nowrap align-middle " scope="col">{% trans 'PO' %}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="list" id="project-list-table-body">
|
||||||
|
{% for bill_item in itemtxs_qs %}
|
||||||
|
<tr>
|
||||||
|
<td class="align-middle white-space-nowrap">
|
||||||
|
<div class="d-flex px-2 py-1">
|
||||||
|
<div class="d-flex flex-column justify-content-center">
|
||||||
|
<h6 class="mb-0 text-sm">{{ bill_item.item_model }}</h6>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="align-middle white-space-nowrap">
|
||||||
|
<span class="text-xs font-weight-bold">
|
||||||
|
{% if bill_item.entity_unit %}
|
||||||
|
{{ bill_item.entity_unit }}
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="align-middle white-space-nowrap">
|
||||||
|
<span class="text-xs font-weight-bold">
|
||||||
|
{% currency_symbol %}{{ bill_item.unit_cost | currency_format }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="align-middle white-space-nowrap">
|
||||||
|
<span class="text-xs font-weight-bold">{{ bill_item.quantity }}</span>
|
||||||
|
</td>
|
||||||
|
<td class="align-middle white-space-nowrap">
|
||||||
|
<span class="text-xs font-weight-bold">
|
||||||
|
{% currency_symbol %}{{ bill_item.total_amount | currency_format }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="align-middle white-space-nowrap pe-2">
|
||||||
|
{% if bill_item.po_model_id %}
|
||||||
|
<a class="btn btn-sm btn-phoenix-primary"
|
||||||
|
href="{% url 'purchase_order_detail' bill_item.po_model_id %}">
|
||||||
|
{% trans 'View PO' %}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
<tfoot>
|
||||||
|
<tr>
|
||||||
|
<td colspan="3"></td>
|
||||||
|
<td class="text-end"><strong>{% trans 'Total' %}</strong></td>
|
||||||
|
<td class="text-end">
|
||||||
|
<strong>
|
||||||
|
{% currency_symbol %}{{ total_amount__sum | currency_format }}
|
||||||
|
</strong>
|
||||||
|
</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body px-0 pt-0 pb-2">
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table class="table table-hover">
|
|
||||||
<thead >
|
|
||||||
<tr class="bg-body-highlight">
|
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans 'Item' %}</th>
|
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans 'Entity Unit' %}</th>
|
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans 'Unit Cost' %}</th>
|
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans 'Quantity' %}</th>
|
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans 'Total' %}</th>
|
|
||||||
<th class="sort white-space-nowrap align-middle " scope="col">{% trans 'PO' %}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody class="list" id="project-list-table-body">
|
|
||||||
{% for bill_item in itemtxs_qs %}
|
|
||||||
<tr>
|
|
||||||
<td class="align-middle white-space-nowrap">
|
|
||||||
<div class="d-flex px-2 py-1">
|
|
||||||
<div class="d-flex flex-column justify-content-center">
|
|
||||||
<h6 class="mb-0 text-sm">{{ bill_item.item_model }}</h6>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td class="align-middle white-space-nowrap">
|
|
||||||
<span class="text-xs font-weight-bold">
|
|
||||||
{% if bill_item.entity_unit %}
|
|
||||||
{{ bill_item.entity_unit }}
|
|
||||||
{% endif %}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td class="align-middle white-space-nowrap">
|
|
||||||
<span class="text-xs font-weight-bold">
|
|
||||||
{% currency_symbol %}{{ bill_item.unit_cost | currency_format }}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td class="align-middle white-space-nowrap">
|
|
||||||
<span class="text-xs font-weight-bold">{{ bill_item.quantity }}</span>
|
|
||||||
</td>
|
|
||||||
<td class="align-middle white-space-nowrap">
|
|
||||||
<span class="text-xs font-weight-bold">
|
|
||||||
{% currency_symbol %}{{ bill_item.total_amount | currency_format }}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td class="align-middle white-space-nowrap pe-2">
|
|
||||||
{% if bill_item.po_model_id %}
|
|
||||||
<a class="btn btn-sm btn-phoenix-primary"
|
|
||||||
href="{% url 'purchase_order_detail' bill_item.po_model_id %}">
|
|
||||||
{% trans 'View PO' %}
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
<tfoot>
|
|
||||||
<tr>
|
|
||||||
<td colspan="3"></td>
|
|
||||||
<td class="text-end"><strong>{% trans 'Total' %}</strong></td>
|
|
||||||
<td class="text-end">
|
|
||||||
<strong>
|
|
||||||
{% currency_symbol %}{{ total_amount__sum | currency_format }}
|
|
||||||
</strong>
|
|
||||||
</td>
|
|
||||||
<td></td>
|
|
||||||
</tr>
|
|
||||||
</tfoot>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Bill Transactions Card -->
|
<!-- Bill Transactions Card -->
|
||||||
<div class="card mb-4 shadow-sm">
|
<div class="card mb-4 shadow-sm">
|
||||||
<div class="card-header pb-0">
|
<div class="card-header pb-0">
|
||||||
<div class="d-flex align-items-center mb-1">
|
<div class="d-flex align-items-center mb-1">
|
||||||
<i class="fas fa-exchange-alt me-3 text-primary"></i>
|
<i class="fas fa-exchange-alt me-3 text-primary"></i>
|
||||||
<h5 class="mb-0">{% trans 'Bill Transactions' %}</h5>
|
<h5 class="mb-0">{% trans 'Bill Transactions' %}</h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body px-0 pt-0 pb-2 table-responsive">
|
||||||
|
{% transactions_table bill %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body px-0 pt-0 pb-2 table-responsive">
|
|
||||||
{% transactions_table bill %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Bill Notes Card -->
|
<!-- Bill Notes Card -->
|
||||||
<div class="card shadow-sm ">
|
<div class="card shadow-sm ">
|
||||||
<div class="card-header pb-0">
|
<div class="card-header pb-0">
|
||||||
<div class="d-flex align-items-center mb-1">
|
<div class="d-flex align-items-center mb-1">
|
||||||
<i class="fas fa-sticky-note me-3 text-primary"></i>
|
<i class="fas fa-sticky-note me-3 text-primary"></i>
|
||||||
<h5 class="mb-0">{% trans 'Bill Notes' %}</h5>
|
<h5 class="mb-0">{% trans 'Bill Notes' %}</h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
{% include 'bill/includes/card_markdown.html' with style='card_1' title='' notes_html=bill.notes_html %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
{% include 'bill/includes/card_markdown.html' with style='card_1' title='' notes_html=bill.notes_html %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{% include "bill/includes/mark_as.html" %}
|
||||||
{% include "bill/includes/mark_as.html" %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -6,52 +6,52 @@
|
|||||||
{% load widget_tweaks crispy_forms_filters %}
|
{% load widget_tweaks crispy_forms_filters %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container py-4">
|
<div class="container py-4">
|
||||||
<div class="row g-4">
|
<div class="row g-4">
|
||||||
<!-- Vendor Card -->
|
<!-- Vendor Card -->
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
{% include 'bill/includes/card_vendor.html' with vendor=bill_model.vendor %}
|
{% include 'bill/includes/card_vendor.html' with vendor=bill_model.vendor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Bill Form -->
|
<!-- Bill Form -->
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card mb-4">
|
<div class="card mb-4">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
{% include 'bill/includes/card_bill.html' with bill=bill_model style='bill-detail' entity_slug=view.kwargs.entity_slug %}
|
{% include 'bill/includes/card_bill.html' with bill=bill_model style='bill-detail' entity_slug=view.kwargs.entity_slug %}
|
||||||
|
|
||||||
<form action="{% url 'bill-update' entity_slug=view.kwargs.entity_slug bill_pk=bill_model.uuid %}" method="post">
|
<form action="{% url 'bill-update' entity_slug=view.kwargs.entity_slug bill_pk=bill_model.uuid %}" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
{{ form|crispy }}
|
{{ form|crispy }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-phoenix-primary w-100 mb-2">
|
<button type="submit" class="btn btn-phoenix-primary w-100 mb-2">
|
||||||
<i class="fas fa-save me-2"></i>{% trans 'Save Bill' %}
|
<i class="fas fa-save me-2"></i>{% trans 'Save Bill' %}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<a href="{% url 'bill-detail' entity_slug=view.kwargs.entity_slug bill_pk=bill_model.uuid %}"
|
<a href="{% url 'bill-detail' entity_slug=view.kwargs.entity_slug bill_pk=bill_model.uuid %}"
|
||||||
class="btn btn-phoenix-secondary w-100 mb-2">
|
class="btn btn-phoenix-secondary w-100 mb-2">
|
||||||
<i class="fas fa-arrow-left me-2"></i>{% trans 'Back to Bill Detail' %}
|
<i class="fas fa-arrow-left me-2"></i>{% trans 'Back to Bill Detail' %}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="{% url 'bill_list' %}"
|
<a href="{% url 'bill_list' %}"
|
||||||
class="btn btn-phoenix-info w-100 mb-2">
|
class="btn btn-phoenix-info w-100 mb-2">
|
||||||
<i class="fas fa-list me-2"></i>{% trans 'Bill List' %}
|
<i class="fas fa-list me-2"></i>{% trans 'Bill List' %}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Bill Item Formset -->
|
<!-- Bill Item Formset -->
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
{% bill_item_formset_table itemtxs_formset %}
|
{% bill_item_formset_table itemtxs_formset %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{% include "bill/includes/mark_as.html" %}
|
{% include "bill/includes/mark_as.html" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -201,9 +201,9 @@
|
|||||||
<div class="card-footer p-0">
|
<div class="card-footer p-0">
|
||||||
<div class="d-flex flex-wrap gap-2 mt-2">
|
<div class="d-flex flex-wrap gap-2 mt-2">
|
||||||
<!-- Update Button -->
|
<!-- Update Button -->
|
||||||
<a href="{% url 'bill-update' entity_slug=entity_slug bill_pk=bill.uuid %}" class="btn btn-phoenix-primary">
|
<a href="{% url 'bill-update' entity_slug=entity_slug bill_pk=bill.uuid %}" class="btn btn-phoenix-primary">
|
||||||
<i class="fas fa-edit me-2"></i>{% trans 'Update' %}
|
<i class="fas fa-edit me-2"></i>{% trans 'Update' %}
|
||||||
</a>
|
</a>
|
||||||
<!-- Mark as Draft -->
|
<!-- Mark as Draft -->
|
||||||
{% if bill.can_draft %}
|
{% if bill.can_draft %}
|
||||||
<button class="btn btn-phoenix-success"
|
<button class="btn btn-phoenix-success"
|
||||||
@ -267,29 +267,29 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.card-footer .btn-link {
|
.card-footer .btn-link {
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
.card-footer .btn-link:hover {
|
.card-footer .btn-link:hover {
|
||||||
background-color: rgba(0,0,0,0.03);
|
background-color: rgba(0,0,0,0.03);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
window.showPOModal = function(title, actionUrl, buttonText) {
|
window.showPOModal = function(title, actionUrl, buttonText) {
|
||||||
const modalEl = document.getElementById('POModal');
|
const modalEl = document.getElementById('POModal');
|
||||||
if (!modalEl) {
|
if (!modalEl) {
|
||||||
console.error('Modal element not found');
|
console.error('Modal element not found');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const modal = bootstrap.Modal.getOrCreateInstance(modalEl);
|
const modal = bootstrap.Modal.getOrCreateInstance(modalEl);
|
||||||
document.getElementById('POModalTitle').textContent = title;
|
document.getElementById('POModalTitle').textContent = title;
|
||||||
|
|
||||||
document.getElementById('POModalBody').innerHTML = `
|
document.getElementById('POModalBody').innerHTML = `
|
||||||
<div class="d-flex justify-content-center gap-3 py-3">
|
<div class="d-flex justify-content-center gap-3 py-3">
|
||||||
<a class="btn btn-phoenix-primary px-4" href="${actionUrl}">
|
<a class="btn btn-phoenix-primary px-4" href="${actionUrl}">
|
||||||
<i class="fas fa-check-circle me-2"></i>${buttonText}
|
<i class="fas fa-check-circle me-2"></i>${buttonText}
|
||||||
@ -300,7 +300,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
modal.show();
|
modal.show();
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@ -2,22 +2,22 @@
|
|||||||
{% load django_ledger %}
|
{% load django_ledger %}
|
||||||
|
|
||||||
{% if style == 'card_1' %}
|
{% if style == 'card_1' %}
|
||||||
<div class="card h-100" style="height: 25rem;">
|
<div class="card h-100" style="height: 25rem;">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h5 class="card-title fs-3 fw-light mb-0">
|
<h5 class="card-title fs-3 fw-light mb-0">
|
||||||
{% if title %}
|
{% if title %}
|
||||||
{{ title }}
|
{{ title }}
|
||||||
|
{% else %}
|
||||||
|
{% trans 'Notes' %}
|
||||||
|
{% endif %}
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body overflow-auto">
|
||||||
|
{% if notes_html %}
|
||||||
|
{{ notes_html|safe }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% trans 'Notes' %}
|
<p class="card-text">{% trans 'No available notes to display...' %}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</h5>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body overflow-auto">
|
|
||||||
{% if notes_html %}
|
|
||||||
{{ notes_html|safe }}
|
|
||||||
{% else %}
|
|
||||||
<p class="card-text">{% trans 'No available notes to display...' %}</p>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@ -43,77 +43,77 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for f in item_formset %}
|
{% for f in item_formset %}
|
||||||
<tr class="align-middle">
|
<tr class="align-middle">
|
||||||
<!-- Item Column -->
|
<!-- Item Column -->
|
||||||
<td>
|
<td>
|
||||||
<div class="d-flex flex-column">
|
<div class="d-flex flex-column">
|
||||||
{% for hidden_field in f.hidden_fields %}
|
{% for hidden_field in f.hidden_fields %}
|
||||||
{{ hidden_field }}
|
{{ hidden_field }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{{ f.item_model|add_class:"form-control" }}
|
{{ f.item_model|add_class:"form-control" }}
|
||||||
{% if f.errors %}
|
{% if f.errors %}
|
||||||
<span class="text-danger text-xs">{{ f.errors }}</span>
|
<span class="text-danger text-xs">{{ f.errors }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<!-- PO Quantity -->
|
<!-- PO Quantity -->
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<span class="text-muted text-xs">
|
<span class="text-muted text-xs">
|
||||||
{% if f.instance.po_quantity %}{{ f.instance.po_quantity }}{% endif %}
|
{% if f.instance.po_quantity %}{{ f.instance.po_quantity }}{% endif %}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<!-- PO Amount -->
|
<!-- PO Amount -->
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
{% if f.instance.po_total_amount %}
|
{% if f.instance.po_total_amount %}
|
||||||
<div class="d-flex flex-column">
|
<div class="d-flex flex-column">
|
||||||
<span class="text-xs font-weight-bold">
|
<span class="text-xs font-weight-bold">
|
||||||
{% currency_symbol %}{{ f.instance.po_total_amount | currency_format }}
|
{% currency_symbol %}{{ f.instance.po_total_amount | currency_format }}
|
||||||
</span>
|
</span>
|
||||||
<a class="btn btn-sm btn-phoenix-info mt-1"
|
<a class="btn btn-sm btn-phoenix-info mt-1"
|
||||||
href="{% url 'purchase_order_detail' f.instance.po_model_id %}">
|
href="{% url 'purchase_order_detail' f.instance.po_model_id %}">
|
||||||
{% trans 'View PO' %}
|
{% trans 'View PO' %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<!-- Quantity -->
|
<!-- Quantity -->
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<div class="input-group input-group-sm w-100">
|
<div class="input-group input-group-sm w-100">
|
||||||
{{ f.quantity|add_class:"form-control" }}
|
{{ f.quantity|add_class:"form-control" }}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<!-- Unit Cost -->
|
<!-- Unit Cost -->
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<div class="input-group input-group-sm w-100">
|
<div class="input-group input-group-sm w-100">
|
||||||
{{ f.unit_cost|add_class:"form-control" }}
|
{{ f.unit_cost|add_class:"form-control" }}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<!-- Entity Unit -->
|
<!-- Entity Unit -->
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
{{ f.entity_unit|add_class:"form-control" }}
|
{{ f.entity_unit|add_class:"form-control" }}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<!-- Total Amount -->
|
<!-- Total Amount -->
|
||||||
<td class="text-end">
|
<td class="text-end">
|
||||||
<span class="text-xs font-weight-bold">
|
<span class="text-xs font-weight-bold">
|
||||||
{% currency_symbol %}{{ f.instance.total_amount | currency_format }}
|
{% currency_symbol %}{{ f.instance.total_amount | currency_format }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<!-- Delete Checkbox -->
|
<!-- Delete Checkbox -->
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
{% if item_formset.can_delete %}
|
{% if item_formset.can_delete %}
|
||||||
<div class="form-check d-flex justify-content-center">
|
<div class="form-check d-flex justify-content-center">
|
||||||
{{ f.DELETE }}
|
{{ f.DELETE }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
||||||
@ -142,11 +142,11 @@
|
|||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="d-flex justify-content-end gap-2">
|
<div class="d-flex justify-content-end gap-2">
|
||||||
{% if not item_formset.has_po %}
|
{% if not item_formset.has_po %}
|
||||||
<a href="{% url 'django_ledger:product-create' entity_slug=entity_slug %}"
|
<a href="{% url 'django_ledger:product-create' entity_slug=entity_slug %}"
|
||||||
class="btn btn-phoenix-primary">
|
class="btn btn-phoenix-primary">
|
||||||
<i class="fas fa-plus me-1"></i>
|
<i class="fas fa-plus me-1"></i>
|
||||||
{% trans 'New Item' %}
|
{% trans 'New Item' %}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<button type="submit" class="btn btn-phoenix-primary">
|
<button type="submit" class="btn btn-phoenix-primary">
|
||||||
<i class="fas fa-save me-1"></i>
|
<i class="fas fa-save me-1"></i>
|
||||||
|
|||||||
@ -108,11 +108,11 @@
|
|||||||
<div class="col-6 col-sm-auto d-flex flex-column align-items-center text-center">
|
<div class="col-6 col-sm-auto d-flex flex-column align-items-center text-center">
|
||||||
<h5 class="fw-bolder mb-2 text-body-highlight">{{ _("Related Records") }}</h5>
|
<h5 class="fw-bolder mb-2 text-body-highlight">{{ _("Related Records") }}</h5>
|
||||||
<h6 class="fw-bolder mb-2 text-body-highlight">{{ _("Opportunity") }}</h6>
|
<h6 class="fw-bolder mb-2 text-body-highlight">{{ _("Opportunity") }}</h6>
|
||||||
{% if lead.opportunity %}
|
{% if lead.opportunity %}
|
||||||
<a href="{% url 'opportunity_detail' lead.opportunity.slug %}" class="">{{ lead.opportunity }}</a>
|
<a href="{% url 'opportunity_detail' lead.opportunity.slug %}" class="">{{ lead.opportunity }}</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>{{ _("No Opportunity") }}</p>
|
<p>{{ _("No Opportunity") }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -255,28 +255,28 @@
|
|||||||
<a href="{% url 'opportunity_create' %}" class="btn btn-phoenix-primary btn-sm" type="button"> <i class="fa-solid fa-plus me-2"></i>{{ _("Add Opportunity") }}</a>
|
<a href="{% url 'opportunity_create' %}" class="btn btn-phoenix-primary btn-sm" type="button"> <i class="fa-solid fa-plus me-2"></i>{{ _("Add Opportunity") }}</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="border-top border-bottom border-translucent" id="leadDetailsTable">
|
<div class="border-top border-bottom border-translucent" id="leadDetailsTable">
|
||||||
<div class="table-responsive scrollbar mx-n1 px-1">
|
<div class="table-responsive scrollbar mx-n1 px-1">
|
||||||
<table class="table fs-9 mb-0">
|
<table class="table fs-9 mb-0">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="align-middle pe-6 text-uppercase text-start" scope="col" style="width:40%;">{{ _("Car") }}</th>
|
<th class="align-middle pe-6 text-uppercase text-start" scope="col" style="width:40%;">{{ _("Car") }}</th>
|
||||||
<th class="align-middle text-start text-uppercase" scope="col" style="width:20%;">{{ _("Probability")}}</th>
|
<th class="align-middle text-start text-uppercase" scope="col" style="width:20%;">{{ _("Probability")}}</th>
|
||||||
<th class="align-middle text-start text-uppercase white-space-nowrap" scope="col" style="width:20%;">{{ _("Priority")}}</th>
|
<th class="align-middle text-start text-uppercase white-space-nowrap" scope="col" style="width:20%;">{{ _("Priority")}}</th>
|
||||||
<th class="align-middle pe-0 text-end" scope="col" style="width:10%;"></th>
|
<th class="align-middle pe-0 text-end" scope="col" style="width:10%;"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody >
|
<tbody >
|
||||||
{% for opportunity in lead.get_opportunities %}
|
{% for opportunity in lead.get_opportunities %}
|
||||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
||||||
<td class="align-middle text-start fw-bold text-body-tertiary ps-1">{{opportunity.car}}</td>
|
<td class="align-middle text-start fw-bold text-body-tertiary ps-1">{{opportunity.car}}</td>
|
||||||
<td class="align-middle text-start fw-bold text-body-tertiary ps-1">{{opportunity.probability}}</td>
|
<td class="align-middle text-start fw-bold text-body-tertiary ps-1">{{opportunity.probability}}</td>
|
||||||
<td class="align-middle text-start fw-bold text-body-tertiary ps-1">{{opportunity.priority|capfirst}}</td>
|
<td class="align-middle text-start fw-bold text-body-tertiary ps-1">{{opportunity.priority|capfirst}}</td>
|
||||||
<td class="align-middle text-start fw-bold text-body-tertiary ps-1"><a class="btn btn-sm btn-phoenix-primary" href="{% url 'opportunity_detail' opportunity.slug %}">View</a></td>
|
<td class="align-middle text-start fw-bold text-body-tertiary ps-1"><a class="btn btn-sm btn-phoenix-primary" href="{% url 'opportunity_detail' opportunity.slug %}">View</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -580,109 +580,91 @@
|
|||||||
let form = document.querySelector('.add_note_form')
|
let form = document.querySelector('.add_note_form')
|
||||||
form.action = "{% url 'add_note' 'lead' lead.slug %}"
|
form.action = "{% url 'add_note' 'lead' lead.slug %}"
|
||||||
}
|
}
|
||||||
let Toast = Swal.mixin({
|
let Toast = Swal.mixin({
|
||||||
|
toast: true,
|
||||||
|
position: "top-end",
|
||||||
|
showConfirmButton: false,
|
||||||
|
timer: 3000,
|
||||||
|
timerProgressBar: true,
|
||||||
|
didOpen: (toast) => {
|
||||||
|
toast.onmouseenter = Swal.stopTimer;
|
||||||
|
toast.onmouseleave = Swal.resumeTimer;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Display Django messages
|
||||||
|
{% if messages %}
|
||||||
|
{% for message in messages %}
|
||||||
|
Toast.fire({
|
||||||
|
icon: "{{ message.tags }}",
|
||||||
|
titleText: "{{ message|safe }}"
|
||||||
|
});
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
function openActionModal(leadId, currentAction, nextAction, nextActionDate) {
|
||||||
|
const modal = new bootstrap.Modal(document.getElementById('actionTrackingModal'));
|
||||||
|
document.getElementById('leadId').value = leadId;
|
||||||
|
document.getElementById('currentAction').value = currentAction;
|
||||||
|
document.getElementById('nextAction').value = nextAction;
|
||||||
|
document.getElementById('nextActionDate').value = nextActionDate;
|
||||||
|
modal.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('actionTrackingForm').addEventListener('submit', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const formData = new FormData(this);
|
||||||
|
|
||||||
|
// Show loading indicator
|
||||||
|
Swal.fire({
|
||||||
toast: true,
|
toast: true,
|
||||||
|
icon: 'info',
|
||||||
|
text: 'Please wait...',
|
||||||
|
allowOutsideClick: false,
|
||||||
position: "top-end",
|
position: "top-end",
|
||||||
showConfirmButton: false,
|
showConfirmButton: false,
|
||||||
timer: 3000,
|
timer: 2000,
|
||||||
timerProgressBar: true,
|
timerProgressBar: false,
|
||||||
didOpen: (toast) => {
|
didOpen: (toast) => {
|
||||||
toast.onmouseenter = Swal.stopTimer;
|
toast.onmouseenter = Swal.stopTimer;
|
||||||
toast.onmouseleave = Swal.resumeTimer;
|
toast.onmouseleave = Swal.resumeTimer;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Display Django messages
|
fetch("{% url 'update_lead_actions' %}", {
|
||||||
{% if messages %}
|
method: 'POST',
|
||||||
{% for message in messages %}
|
body: formData,
|
||||||
Toast.fire({
|
headers: {
|
||||||
icon: "{{ message.tags }}",
|
'X-CSRFToken': '{{ csrf_token }}'
|
||||||
titleText: "{{ message|safe }}"
|
}
|
||||||
});
|
})
|
||||||
{% endfor %}
|
.then(response => response.json())
|
||||||
{% endif %}
|
.then(data => {
|
||||||
|
Swal.close();
|
||||||
function openActionModal(leadId, currentAction, nextAction, nextActionDate) {
|
if (data.success) {
|
||||||
const modal = new bootstrap.Modal(document.getElementById('actionTrackingModal'));
|
|
||||||
document.getElementById('leadId').value = leadId;
|
|
||||||
document.getElementById('currentAction').value = currentAction;
|
|
||||||
document.getElementById('nextAction').value = nextAction;
|
|
||||||
document.getElementById('nextActionDate').value = nextActionDate;
|
|
||||||
modal.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById('actionTrackingForm').addEventListener('submit', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
const formData = new FormData(this);
|
|
||||||
|
|
||||||
// Show loading indicator
|
|
||||||
Swal.fire({
|
|
||||||
toast: true,
|
|
||||||
icon: 'info',
|
|
||||||
text: 'Please wait...',
|
|
||||||
allowOutsideClick: false,
|
|
||||||
position: "top-end",
|
|
||||||
showConfirmButton: false,
|
|
||||||
timer: 2000,
|
|
||||||
timerProgressBar: false,
|
|
||||||
didOpen: (toast) => {
|
|
||||||
toast.onmouseenter = Swal.stopTimer;
|
|
||||||
toast.onmouseleave = Swal.resumeTimer;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fetch("{% url 'update_lead_actions' %}", {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData,
|
|
||||||
headers: {
|
|
||||||
'X-CSRFToken': '{{ csrf_token }}'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
Swal.close();
|
|
||||||
if (data.success) {
|
|
||||||
// Success notification
|
// Success notification
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
toast: true,
|
toast: true,
|
||||||
icon: 'success',
|
icon: 'success',
|
||||||
position: "top-end",
|
position: "top-end",
|
||||||
text: data.message || 'Actions updated successfully',
|
text: data.message || 'Actions updated successfully',
|
||||||
showConfirmButton: false,
|
showConfirmButton: false,
|
||||||
timer: 2000,
|
timer: 2000,
|
||||||
timerProgressBar: false,
|
timerProgressBar: false,
|
||||||
didOpen: (toast) => {
|
didOpen: (toast) => {
|
||||||
toast.onmouseenter = Swal.stopTimer;
|
toast.onmouseenter = Swal.stopTimer;
|
||||||
toast.onmouseleave = Swal.resumeTimer;
|
toast.onmouseleave = Swal.resumeTimer;
|
||||||
}
|
}
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
location.reload(); // Refresh after user clicks OK
|
location.reload(); // Refresh after user clicks OK
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Error notification
|
// Error notification
|
||||||
Swal.fire({
|
|
||||||
toast: true,
|
|
||||||
icon: 'error',
|
|
||||||
position: "top-end",
|
|
||||||
text: data.message || 'Failed to update actions',
|
|
||||||
showConfirmButton: false,
|
|
||||||
timer: 2000,
|
|
||||||
timerProgressBar: false,
|
|
||||||
didOpen: (toast) => {
|
|
||||||
toast.onmouseenter = Swal.stopTimer;
|
|
||||||
toast.onmouseleave = Swal.resumeTimer;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
Swal.close();
|
|
||||||
console.error('Error:', error);
|
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
toast: true,
|
toast: true,
|
||||||
icon: 'error',
|
icon: 'error',
|
||||||
position: "top-end",
|
position: "top-end",
|
||||||
text: 'An unexpected error occurred',
|
text: data.message || 'Failed to update actions',
|
||||||
showConfirmButton: false,
|
showConfirmButton: false,
|
||||||
timer: 2000,
|
timer: 2000,
|
||||||
timerProgressBar: false,
|
timerProgressBar: false,
|
||||||
@ -691,16 +673,34 @@
|
|||||||
toast.onmouseleave = Swal.resumeTimer;
|
toast.onmouseleave = Swal.resumeTimer;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
Swal.close();
|
||||||
|
console.error('Error:', error);
|
||||||
|
Swal.fire({
|
||||||
|
toast: true,
|
||||||
|
icon: 'error',
|
||||||
|
position: "top-end",
|
||||||
|
text: 'An unexpected error occurred',
|
||||||
|
showConfirmButton: false,
|
||||||
|
timer: 2000,
|
||||||
|
timerProgressBar: false,
|
||||||
|
didOpen: (toast) => {
|
||||||
|
toast.onmouseenter = Swal.stopTimer;
|
||||||
|
toast.onmouseleave = Swal.resumeTimer;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Helper function for notifications
|
// Helper function for notifications
|
||||||
function notify(tag, msg) {
|
function notify(tag, msg) {
|
||||||
Toast.fire({
|
Toast.fire({
|
||||||
icon: tag,
|
icon: tag,
|
||||||
titleText: msg
|
titleText: msg
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock customJS %}
|
{% endblock customJS %}
|
||||||
|
|||||||
@ -3,9 +3,9 @@
|
|||||||
{% block title %}
|
{% block title %}
|
||||||
{# Check if an 'object' exists in the context #}
|
{# Check if an 'object' exists in the context #}
|
||||||
{% if object %}
|
{% if object %}
|
||||||
{% trans 'Update Lead'%}
|
{% trans 'Update Lead'%}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% trans 'Add New Lead'%}
|
{% trans 'Add New Lead'%}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block customcss %}
|
{% block customcss %}
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
<h2 class="mb-4">{{ _("Leads")|capfirst }}</h2>
|
<h2 class="mb-4">{{ _("Leads")|capfirst }}</h2>
|
||||||
<!-- Action Tracking Modal -->
|
<!-- Action Tracking Modal -->
|
||||||
{% include "crm/leads/partials/update_action.html" %}
|
{% include "crm/leads/partials/update_action.html" %}
|
||||||
|
|
||||||
<div class="row g-3 justify-content-between mb-4">
|
<div class="row g-3 justify-content-between mb-4">
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
@ -102,79 +102,79 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
||||||
<td class="name align-middle white-space-nowrap ps-0">
|
<td class="name align-middle white-space-nowrap ps-0">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<div><a class="fs-8 fw-bold" href="{% url 'lead_detail' lead.slug %}">{{lead.full_name|capfirst}}</a>
|
<div><a class="fs-8 fw-bold" href="{% url 'lead_detail' lead.slug %}">{{lead.full_name|capfirst}}</a>
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<p class="mb-0 text-body-highlight fw-semibold fs-9 me-2"></p>
|
<p class="mb-0 text-body-highlight fw-semibold fs-9 me-2"></p>
|
||||||
{% if lead.status == "new" %}
|
{% if lead.status == "new" %}
|
||||||
<span class="badge badge-phoenix badge-phoenix-primary"><span class="badge-label">{{_("New")}}</span><span class="fa fa-bell ms-1"></span></span>
|
<span class="badge badge-phoenix badge-phoenix-primary"><span class="badge-label">{{_("New")}}</span><span class="fa fa-bell ms-1"></span></span>
|
||||||
{% elif lead.status == "pending" %}
|
{% elif lead.status == "pending" %}
|
||||||
<span class="badge badge-phoenix badge-phoenix-warning"><span class="badge-label">{{_("Pending")}}</span><span class="fa fa-clock-o ms-1"></span></span>
|
<span class="badge badge-phoenix badge-phoenix-warning"><span class="badge-label">{{_("Pending")}}</span><span class="fa fa-clock-o ms-1"></span></span>
|
||||||
{% elif lead.status == "in_progress" %}
|
{% elif lead.status == "in_progress" %}
|
||||||
<span class="badge badge-phoenix badge-phoenix-info"><span class="badge-label">{{_("In Progress")}}</span><span class="fa fa-wrench ms-1"></span></span>
|
<span class="badge badge-phoenix badge-phoenix-info"><span class="badge-label">{{_("In Progress")}}</span><span class="fa fa-wrench ms-1"></span></span>
|
||||||
{% elif lead.status == "qualified" %}
|
{% elif lead.status == "qualified" %}
|
||||||
<span class="badge badge-phoenix badge-phoenix-success"><span class="badge-label">{{_("Qualified")}}</span><span class="fa fa-check ms-1"></span></span>
|
<span class="badge badge-phoenix badge-phoenix-success"><span class="badge-label">{{_("Qualified")}}</span><span class="fa fa-check ms-1"></span></span>
|
||||||
{% elif lead.status == "contacted" %}
|
{% elif lead.status == "contacted" %}
|
||||||
<span class="badge badge-phoenix badge-phoenix-info"><span class="badge-label">{{_("Contacted")}}</span><span class="fa fa-times ms-1"></span></span>
|
<span class="badge badge-phoenix badge-phoenix-info"><span class="badge-label">{{_("Contacted")}}</span><span class="fa fa-times ms-1"></span></span>
|
||||||
{% elif lead.status == "canceled" %}
|
{% elif lead.status == "canceled" %}
|
||||||
<span class="badge badge-phoenix badge-phoenix-danger"><span class="badge-label">{{_("Canceled")}}</span><span class="fa fa-times ms-1"></span></span>
|
<span class="badge badge-phoenix badge-phoenix-danger"><span class="badge-label">{{_("Canceled")}}</span><span class="fa fa-times ms-1"></span></span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td class="align-middle white-space-nowrap fw-semibold"><a class="text-body-highlight" href="">{{ lead.id_car_make.get_local_name }} - {{ lead.id_car_model.get_local_name }} {{ lead.year }}</a></td>
|
|
||||||
<td class="align-middle white-space-nowrap fw-semibold"><a class="text-body-highlight" href="">{{ lead.email }}</a></td>
|
|
||||||
<td class="align-middle white-space-nowrap fw-semibold"><a class="text-body-highlight" href="tel:{{ lead.phone_number }}">{{ lead.phone_number }}</a></td>
|
|
||||||
<td class="align-middle white-space-nowrap fw-semibold">
|
|
||||||
{% if request.user.staffmember.staff %}
|
|
||||||
<div class="accordion" id="accordionExample">
|
|
||||||
<div class="accordion-item">
|
|
||||||
<h2 class="accordion-header" id="headingTwo">
|
|
||||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse{{lead.slug}}" aria-expanded="false" aria-controls="collapseTwo">
|
|
||||||
{{ _("View Schedules")}} ({{lead.get_latest_schedules.count}})
|
|
||||||
</button>
|
|
||||||
</h2>
|
|
||||||
<div class="accordion-collapse collapse" id="collapse{{lead.slug}}" aria-labelledby="headingTwo" data-bs-parent="#accordionExample">
|
|
||||||
<div class="accordion-body pt-0">
|
|
||||||
<div class="d-flex flex-column gap-2">
|
|
||||||
<table><tbody>
|
|
||||||
{% for schedule in lead.get_latest_schedules %}
|
|
||||||
<tr class="schedule-{{ schedule.pk }}">
|
|
||||||
<td class="align-middle white-space-nowrap">
|
|
||||||
{% if schedule.scheduled_type == "call" %}
|
|
||||||
<a href="{% url 'appointment:get_user_appointments' %}">
|
|
||||||
<span class="badge badge-phoenix badge-phoenix-primary text-primary {% if schedule.schedule_past_date %}badge-phoenix-danger text-danger{% endif %} fw-semibold"><span class="text-primary {% if schedule.schedule_past_date %}text-danger{% endif %}" data-feather="phone"></span>
|
|
||||||
{{ schedule.scheduled_at|naturaltime|capfirst }}</span></a>
|
|
||||||
{% elif schedule.scheduled_type == "meeting" %}
|
|
||||||
<a href="{% url 'appointment:get_user_appointments' %}">
|
|
||||||
<span class="badge badge-phoenix badge-phoenix-success text-success fw-semibold"><span class="text-success" data-feather="calendar"></span>
|
|
||||||
{{ schedule.scheduled_at|naturaltime|capfirst }}</span></a>
|
|
||||||
{% elif schedule.scheduled_type == "email" %}
|
|
||||||
<a href="{% url 'appointment:get_user_appointments' %}">
|
|
||||||
<span class="badge badge-phoenix badge-phoenix-warning text-warning fw-semibold"><span class="text-warning" data-feather="email"></span>
|
|
||||||
{{ schedule.scheduled_at|naturaltime|capfirst }}</span></a>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a style="cursor: pointer;" hx-delete="{% url 'schedule_cancel' schedule.pk %}" hx-target=".schedule-{{ schedule.pk }}" hx-confirm="Are you sure you want to cancel this schedule?"><i class="fa-solid fa-ban text-danger"></i></a>
|
|
||||||
</td>
|
|
||||||
{% endfor %}
|
|
||||||
</tr>
|
|
||||||
<tr><td><small><a href="{% url 'appointment:get_user_appointments' %}">View All ...</a></small></td></tr>
|
|
||||||
</tbody></table>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</td>
|
||||||
</div>
|
|
||||||
{% endif %}
|
<td class="align-middle white-space-nowrap fw-semibold"><a class="text-body-highlight" href="">{{ lead.id_car_make.get_local_name }} - {{ lead.id_car_model.get_local_name }} {{ lead.year }}</a></td>
|
||||||
</td>
|
<td class="align-middle white-space-nowrap fw-semibold"><a class="text-body-highlight" href="">{{ lead.email }}</a></td>
|
||||||
<td class="align-middle white-space-nowrap text-body-tertiary text-opacity-85 fw-semibold text-body-highlight">{{ lead.get_status|upper }}</td>
|
<td class="align-middle white-space-nowrap fw-semibold"><a class="text-body-highlight" href="tel:{{ lead.phone_number }}">{{ lead.phone_number }}</a></td>
|
||||||
<td class="align-middle white-space-nowrap text-body-tertiary text-opacity-85 fw-semibold text-body-highlight">{{ lead.staff|upper }}</td>
|
<td class="align-middle white-space-nowrap fw-semibold">
|
||||||
|
{% if request.user.staffmember.staff %}
|
||||||
|
<div class="accordion" id="accordionExample">
|
||||||
|
<div class="accordion-item">
|
||||||
|
<h2 class="accordion-header" id="headingTwo">
|
||||||
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse{{lead.slug}}" aria-expanded="false" aria-controls="collapseTwo">
|
||||||
|
{{ _("View Schedules")}} ({{lead.get_latest_schedules.count}})
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div class="accordion-collapse collapse" id="collapse{{lead.slug}}" aria-labelledby="headingTwo" data-bs-parent="#accordionExample">
|
||||||
|
<div class="accordion-body pt-0">
|
||||||
|
<div class="d-flex flex-column gap-2">
|
||||||
|
<table><tbody>
|
||||||
|
{% for schedule in lead.get_latest_schedules %}
|
||||||
|
<tr class="schedule-{{ schedule.pk }}">
|
||||||
|
<td class="align-middle white-space-nowrap">
|
||||||
|
{% if schedule.scheduled_type == "call" %}
|
||||||
|
<a href="{% url 'appointment:get_user_appointments' %}">
|
||||||
|
<span class="badge badge-phoenix badge-phoenix-primary text-primary {% if schedule.schedule_past_date %}badge-phoenix-danger text-danger{% endif %} fw-semibold"><span class="text-primary {% if schedule.schedule_past_date %}text-danger{% endif %}" data-feather="phone"></span>
|
||||||
|
{{ schedule.scheduled_at|naturaltime|capfirst }}</span></a>
|
||||||
|
{% elif schedule.scheduled_type == "meeting" %}
|
||||||
|
<a href="{% url 'appointment:get_user_appointments' %}">
|
||||||
|
<span class="badge badge-phoenix badge-phoenix-success text-success fw-semibold"><span class="text-success" data-feather="calendar"></span>
|
||||||
|
{{ schedule.scheduled_at|naturaltime|capfirst }}</span></a>
|
||||||
|
{% elif schedule.scheduled_type == "email" %}
|
||||||
|
<a href="{% url 'appointment:get_user_appointments' %}">
|
||||||
|
<span class="badge badge-phoenix badge-phoenix-warning text-warning fw-semibold"><span class="text-warning" data-feather="email"></span>
|
||||||
|
{{ schedule.scheduled_at|naturaltime|capfirst }}</span></a>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a style="cursor: pointer;" hx-delete="{% url 'schedule_cancel' schedule.pk %}" hx-target=".schedule-{{ schedule.pk }}" hx-confirm="Are you sure you want to cancel this schedule?"><i class="fa-solid fa-ban text-danger"></i></a>
|
||||||
|
</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
<tr><td><small><a href="{% url 'appointment:get_user_appointments' %}">View All ...</a></small></td></tr>
|
||||||
|
</tbody></table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="align-middle white-space-nowrap text-body-tertiary text-opacity-85 fw-semibold text-body-highlight">{{ lead.get_status|upper }}</td>
|
||||||
|
<td class="align-middle white-space-nowrap text-body-tertiary text-opacity-85 fw-semibold text-body-highlight">{{ lead.staff|upper }}</td>
|
||||||
{% comment %} <td class="align-middle white-space-nowrap text-body-tertiary text-opacity-85 fw-semibold text-body-highlight">
|
{% comment %} <td class="align-middle white-space-nowrap text-body-tertiary text-opacity-85 fw-semibold text-body-highlight">
|
||||||
{% if lead.opportunity.stage == "prospect" %}
|
{% if lead.opportunity.stage == "prospect" %}
|
||||||
<span class="badge text-bg-primary">{{ lead.opportunity.stage|upper }}</span>
|
<span class="badge text-bg-primary">{{ lead.opportunity.stage|upper }}</span>
|
||||||
@ -188,47 +188,47 @@
|
|||||||
<span class="badge text-bg-danger">{{ lead.opportunity.stage|upper }}</span>
|
<span class="badge text-bg-danger">{{ lead.opportunity.stage|upper }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td> {% endcomment %}
|
</td> {% endcomment %}
|
||||||
<td class="align-middle white-space-nowrap text-body-tertiary text-opacity-85 fw-semibold text-body-highlight">
|
<td class="align-middle white-space-nowrap text-body-tertiary text-opacity-85 fw-semibold text-body-highlight">
|
||||||
{% if lead.opportunity %}
|
{% if lead.opportunity %}
|
||||||
<a href="{% url 'opportunity_detail' lead.opportunity.slug %}">
|
<a href="{% url 'opportunity_detail' lead.opportunity.slug %}">
|
||||||
<span class="badge badge-phoenix badge-phoenix-success">Opportunity {{ lead.opportunity.lead}} <i class="fa-solid fa-arrow-up-right-from-square"></i></span>
|
<span class="badge badge-phoenix badge-phoenix-success">Opportunity {{ lead.opportunity.lead}} <i class="fa-solid fa-arrow-up-right-from-square"></i></span>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle white-space-nowrap text-end">
|
<td class="align-middle white-space-nowrap text-end">
|
||||||
{% if user == lead.staff.user or request.is_dealer %}
|
{% if user == lead.staff.user or request.is_dealer %}
|
||||||
<div class="btn-reveal-trigger position-static">
|
<div class="btn-reveal-trigger position-static">
|
||||||
<button
|
<button
|
||||||
class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10"
|
class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10"
|
||||||
type="button"
|
type="button"
|
||||||
data-bs-toggle="dropdown"
|
data-bs-toggle="dropdown"
|
||||||
data-boundary="window"
|
data-boundary="window"
|
||||||
aria-haspopup="true"
|
aria-haspopup="true"
|
||||||
aria-expanded="false"
|
aria-expanded="false"
|
||||||
data-bs-reference="parent">
|
data-bs-reference="parent">
|
||||||
<span class="fas fa-ellipsis-h fs-10"></span>
|
<span class="fas fa-ellipsis-h fs-10"></span>
|
||||||
</button>
|
</button>
|
||||||
<div class="dropdown-menu dropdown-menu-end py-2">
|
<div class="dropdown-menu dropdown-menu-end py-2">
|
||||||
{% if perms.inventory.change_lead %}
|
{% if perms.inventory.change_lead %}
|
||||||
<a href="{% url 'lead_update' lead.slug %}" class="dropdown-item text-success-dark">{% trans "Edit" %}</a>
|
<a href="{% url 'lead_update' lead.slug %}" class="dropdown-item text-success-dark">{% trans "Edit" %}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<button class="dropdown-item text-primary" onclick="openActionModal('{{ lead.pk }}', '{{ lead.action }}', '{{ lead.next_action }}', '{{ lead.next_action_date|date:"Y-m-d\TH:i" }}')">
|
<button class="dropdown-item text-primary" onclick="openActionModal('{{ lead.pk }}', '{{ lead.action }}', '{{ lead.next_action }}', '{{ lead.next_action_date|date:"Y-m-d\TH:i" }}')">
|
||||||
{% trans "Update Actions" %}
|
{% trans "Update Actions" %}
|
||||||
</button>
|
</button>
|
||||||
<a href="{% url 'send_lead_email' lead.slug %}" class="dropdown-item text-success-dark">{% trans "Send Email" %}</a>
|
<a href="{% url 'send_lead_email' lead.slug %}" class="dropdown-item text-success-dark">{% trans "Send Email" %}</a>
|
||||||
<a href="{% url 'schedule_lead' lead.slug %}" class="dropdown-item text-success-dark">{% trans "Schedule Event" %}</a>
|
<a href="{% url 'schedule_lead' lead.slug %}" class="dropdown-item text-success-dark">{% trans "Schedule Event" %}</a>
|
||||||
{% if not lead.opportunity %}
|
{% if not lead.opportunity %}
|
||||||
<a href="{% url 'lead_opportunity_create' lead.slug %}" class="dropdown-item text-success-dark">{% trans "Convert to Opportunity" %}</a>
|
<a href="{% url 'lead_opportunity_create' lead.slug %}" class="dropdown-item text-success-dark">{% trans "Convert to Opportunity" %}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="dropdown-divider"></div>
|
<div class="dropdown-divider"></div>
|
||||||
{% if perms.inventory.delete_lead %}
|
{% if perms.inventory.delete_lead %}
|
||||||
<button class="dropdown-item text-danger" data-bs-toggle="modal" data-bs-target="#deleteModal">{% trans "Delete" %}</button>
|
<button class="dropdown-item text-danger" data-bs-toggle="modal" data-bs-target="#deleteModal">{% trans "Delete" %}</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -237,13 +237,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex justify-content-end mt-3">
|
<div class="d-flex justify-content-end mt-3">
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
{% if is_paginated %}
|
{% if is_paginated %}
|
||||||
{% include 'partials/pagination.html' %}
|
{% include 'partials/pagination.html' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -10,19 +10,19 @@
|
|||||||
min-height: 500px;
|
min-height: 500px;
|
||||||
}
|
}
|
||||||
.kanban-header {
|
.kanban-header {
|
||||||
position: relative;
|
position: relative;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
color: #333;
|
color: #333;
|
||||||
--pointed-edge: {% if LANGUAGE_CODE == 'en' %} right {% else %} left {% endif %};
|
--pointed-edge: {% if LANGUAGE_CODE == 'en' %} right {% else %} left {% endif %};
|
||||||
clip-path: {% if LANGUAGE_CODE == 'en' %}
|
clip-path: {% if LANGUAGE_CODE == 'en' %}
|
||||||
polygon(0 0, calc(100% - 15px) 0, 100% 50%, calc(100% - 15px) 100%, 0 100%)
|
polygon(0 0, calc(100% - 15px) 0, 100% 50%, calc(100% - 15px) 100%, 0 100%)
|
||||||
{% else %}
|
{% else %}
|
||||||
polygon(15px 0, 100% 0, 100% 100%, 15px 100%, 0 50%)
|
polygon(15px 0, 100% 0, 100% 100%, 15px 100%, 0 50%)
|
||||||
{% endif %};
|
{% endif %};
|
||||||
box-shadow: 0 1px 2px rgba(0,0,0,0.1);
|
box-shadow: 0 1px 2px rgba(0,0,0,0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.kanban-header::after {
|
.kanban-header::after {
|
||||||
@ -65,95 +65,95 @@
|
|||||||
{% endblock customCSS %}
|
{% endblock customCSS %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container-fluid my-4">
|
<div class="container-fluid my-4">
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="d-flex justify-content-between mb-3">
|
<div class="d-flex justify-content-between mb-3">
|
||||||
<h3>{{ _("Lead Tracking")}}</h3>
|
<h3>{{ _("Lead Tracking")}}</h3>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row g-3">
|
|
||||||
<!-- New Lead -->
|
|
||||||
<div class="col-md">
|
|
||||||
<div class="kanban-column bg-body">
|
|
||||||
<div class="kanban-header opacity-75"><span class="text-body">{{ _("New Leads")}} ({{new|length}})</span></div>
|
|
||||||
{% for lead in new %}
|
|
||||||
<a href="{% url 'lead_detail' lead.slug %}">
|
|
||||||
<div class="lead-card">
|
|
||||||
<strong>{{lead.full_name|capfirst}}</strong><br>
|
|
||||||
<small>{{lead.email}}</small><br>
|
|
||||||
<small>{{lead.phone_number}}</small>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
<div class="row g-3">
|
||||||
|
<!-- New Lead -->
|
||||||
|
<div class="col-md">
|
||||||
|
<div class="kanban-column bg-body">
|
||||||
|
<div class="kanban-header opacity-75"><span class="text-body">{{ _("New Leads")}} ({{new|length}})</span></div>
|
||||||
|
{% for lead in new %}
|
||||||
|
<a href="{% url 'lead_detail' lead.slug %}">
|
||||||
|
<div class="lead-card">
|
||||||
|
<strong>{{lead.full_name|capfirst}}</strong><br>
|
||||||
|
<small>{{lead.email}}</small><br>
|
||||||
|
<small>{{lead.phone_number}}</small>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Follow Ups -->
|
<!-- Follow Ups -->
|
||||||
<div class="col-md">
|
<div class="col-md">
|
||||||
<div class="kanban-column bg-body">
|
<div class="kanban-column bg-body">
|
||||||
<div class="kanban-header opacity-75"><span class="text-body">{{ _("Follow Ups")}} ({{follow_up|length}})</span></div>
|
<div class="kanban-header opacity-75"><span class="text-body">{{ _("Follow Ups")}} ({{follow_up|length}})</span></div>
|
||||||
{% for lead in follow_up %}
|
{% for lead in follow_up %}
|
||||||
<a href="{% url 'lead_detail' lead.slug %}">
|
<a href="{% url 'lead_detail' lead.slug %}">
|
||||||
<div class="lead-card">
|
<div class="lead-card">
|
||||||
<strong>{{lead.full_name|capfirst}}</strong><br>
|
<strong>{{lead.full_name|capfirst}}</strong><br>
|
||||||
<small>{{lead.email}}</small><br>
|
<small>{{lead.email}}</small><br>
|
||||||
<small>{{lead.phone_number}}</small>
|
<small>{{lead.phone_number}}</small>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Negotiation -->
|
<!-- Negotiation -->
|
||||||
<div class="col-md">
|
<div class="col-md">
|
||||||
<div class="kanban-column bg-body">
|
<div class="kanban-column bg-body">
|
||||||
<div class="kanban-header opacity-75"><span class="text-body">{{ _("Negotiation Ups")}} ({{follow_up|length}})</span></div>
|
<div class="kanban-header opacity-75"><span class="text-body">{{ _("Negotiation Ups")}} ({{follow_up|length}})</span></div>
|
||||||
{% for lead in negotiation %}
|
{% for lead in negotiation %}
|
||||||
<a href="{% url 'lead_detail' lead.slug %}">
|
<a href="{% url 'lead_detail' lead.slug %}">
|
||||||
<div class="lead-card">
|
<div class="lead-card">
|
||||||
<strong>{{lead.full_name|capfirst}}</strong><br>
|
<strong>{{lead.full_name|capfirst}}</strong><br>
|
||||||
<small>{{lead.email}}</small><br>
|
<small>{{lead.email}}</small><br>
|
||||||
<small>{{lead.phone_number}}</small>
|
<small>{{lead.phone_number}}</small>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Won -->
|
<!-- Won -->
|
||||||
<div class="col-md">
|
<div class="col-md">
|
||||||
<div class="kanban-column bg-body">
|
<div class="kanban-column bg-body">
|
||||||
<div class="kanban-header bg-success-light opacity-75"><span class="text-body">{{ _("Won") }} ({{won|length}}) ({{follow_up|length}})</span></div>
|
<div class="kanban-header bg-success-light opacity-75"><span class="text-body">{{ _("Won") }} ({{won|length}}) ({{follow_up|length}})</span></div>
|
||||||
{% for lead in won %}
|
{% for lead in won %}
|
||||||
<a href="{% url 'lead_detail' lead.slug %}">
|
<a href="{% url 'lead_detail' lead.slug %}">
|
||||||
<div class="lead-card">
|
<div class="lead-card">
|
||||||
<strong>{{lead.full_name|capfirst}}</strong><br>
|
<strong>{{lead.full_name|capfirst}}</strong><br>
|
||||||
<small>{{lead.email}}</small><br>
|
<small>{{lead.email}}</small><br>
|
||||||
<small>{{lead.phone_number}}</small>
|
<small>{{lead.phone_number}}</small>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Lose -->
|
<!-- Lose -->
|
||||||
<div class="col-md">
|
<div class="col-md">
|
||||||
<div class="kanban-column bg-body">
|
<div class="kanban-column bg-body">
|
||||||
<div class="kanban-header bg-danger-light opacity-75">{{ _("Lost") }} ({{lose|length}})</div>
|
<div class="kanban-header bg-danger-light opacity-75">{{ _("Lost") }} ({{lose|length}})</div>
|
||||||
{% for lead in lose %}
|
{% for lead in lose %}
|
||||||
<a href="{% url 'lead_detail' lead.slug %}">
|
<a href="{% url 'lead_detail' lead.slug %}">
|
||||||
<div class="lead-card">
|
<div class="lead-card">
|
||||||
<strong>{{lead.full_name|capfirst}}</strong><br>
|
<strong>{{lead.full_name|capfirst}}</strong><br>
|
||||||
<small>{{lead.email}}</small><br>
|
<small>{{lead.email}}</small><br>
|
||||||
<small>{{lead.phone_number}}</small>
|
<small>{{lead.phone_number}}</small>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -1,53 +1,53 @@
|
|||||||
<div class="modal fade" id="actionTrackingModal" tabindex="-1" aria-labelledby="actionTrackingModalLabel" aria-hidden="true">
|
<div class="modal fade" id="actionTrackingModal" tabindex="-1" aria-labelledby="actionTrackingModalLabel" aria-hidden="true">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title" id="actionTrackingModalLabel">{{ _("Update Lead Actions") }}</h5>
|
<h5 class="modal-title" id="actionTrackingModalLabel">{{ _("Update Lead Actions") }}</h5>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<form id="actionTrackingForm" method="post">
|
<form id="actionTrackingForm" method="post">
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="hidden" id="leadId" name="lead_id">
|
<input type="hidden" id="leadId" name="lead_id">
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="currentAction" class="form-label">{{ _("Current Stage") }}</label>
|
<label for="currentAction" class="form-label">{{ _("Current Stage") }}</label>
|
||||||
<select class="form-select" id="currentAction" name="current_action" required>
|
<select class="form-select" id="currentAction" name="current_action" required>
|
||||||
<option value="">{{ _("Select Stage") }}</option>
|
<option value="">{{ _("Select Stage") }}</option>
|
||||||
<option value="new">{{ _("New") }}</option>
|
<option value="new">{{ _("New") }}</option>
|
||||||
<option value="contacted">{{ _("Contacted") }}</option>
|
<option value="contacted">{{ _("Contacted") }}</option>
|
||||||
<option value="qualified">{{ _("Qualified") }}</option>
|
<option value="qualified">{{ _("Qualified") }}</option>
|
||||||
<option value="unqualified">{{ _("Unqualified") }}</option>
|
<option value="unqualified">{{ _("Unqualified") }}</option>
|
||||||
<option value="converted">{{ _("Converted") }}</option>
|
<option value="converted">{{ _("Converted") }}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="nextAction" class="form-label">{{ _("Next Action") }}</label>
|
<label for="nextAction" class="form-label">{{ _("Next Action") }}</label>
|
||||||
<select class="form-select" id="nextAction" name="next_action" required>
|
<select class="form-select" id="nextAction" name="next_action" required>
|
||||||
<option value="">{{ _("Select Next Action") }}</option>
|
<option value="">{{ _("Select Next Action") }}</option>
|
||||||
<option value="no_action">{{ _("No Action") }}</option>
|
<option value="no_action">{{ _("No Action") }}</option>
|
||||||
<option value="call">{{ _("Call") }}</option>
|
<option value="call">{{ _("Call") }}</option>
|
||||||
<option value="meeting">{{ _("Meeting") }}</option>
|
<option value="meeting">{{ _("Meeting") }}</option>
|
||||||
<option value="email">{{ _("Email") }}</option>
|
<option value="email">{{ _("Email") }}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="nextActionDate" class="form-label">{{ _("Next Action Date") }}</label>
|
<label for="nextActionDate" class="form-label">{{ _("Next Action Date") }}</label>
|
||||||
<input type="datetime-local" class="form-control" id="nextActionDate" name="next_action_date">
|
<input type="datetime-local" class="form-control" id="nextActionDate" name="next_action_date">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="actionNotes" class="form-label">{{ _("Notes") }}</label>
|
<label for="actionNotes" class="form-label">{{ _("Notes") }}</label>
|
||||||
<textarea class="form-control" id="actionNotes" name="action_notes" rows="3"></textarea>
|
<textarea class="form-control" id="actionNotes" name="action_notes" rows="3"></textarea>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-phoenix-secondary" data-bs-dismiss="modal">{{ _("Close") }}</button>
|
|
||||||
<button type="submit" class="btn btn-phoenix-primary">{{ _("Save Changes") }}</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-phoenix-secondary" data-bs-dismiss="modal">{{ _("Close") }}</button>
|
||||||
|
<button type="submit" class="btn btn-phoenix-primary">{{ _("Save Changes") }}</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,12 +1,12 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% load i18n static widget_tweaks custom_filters %}
|
{% load i18n static widget_tweaks custom_filters %}
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{# Check if an 'object' exists in the context #}
|
{# Check if an 'object' exists in the context #}
|
||||||
{% if object %}
|
{% if object %}
|
||||||
{% trans 'Update Opportunity'%}
|
{% trans 'Update Opportunity'%}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% trans 'Add New Opportunity'%}
|
{% trans 'Add New Opportunity'%}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
@ -88,8 +88,8 @@
|
|||||||
<span class="text-danger">*</span>
|
<span class="text-danger">*</span>
|
||||||
</label>
|
</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<span class="input-group-text"><span class="icon-saudi_riyal"></span></span>
|
<span class="input-group-text"><span class="icon-saudi_riyal"></span></span>
|
||||||
{{ form.amount|add_class:"form-control" }}
|
{{ form.amount|add_class:"form-control" }}
|
||||||
</div>
|
</div>
|
||||||
{% if form.amount.errors %}
|
{% if form.amount.errors %}
|
||||||
<div class="invalid-feedback d-block">
|
<div class="invalid-feedback d-block">
|
||||||
|
|||||||
@ -90,11 +90,11 @@
|
|||||||
<div id="opportunities-grid" class="row g-4 px-2 px-lg-4 mt-1">
|
<div id="opportunities-grid" class="row g-4 px-2 px-lg-4 mt-1">
|
||||||
{% include 'crm/opportunities/partials/opportunity_grid.html' %}
|
{% include 'crm/opportunities/partials/opportunity_grid.html' %}
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex justify-content-end mt-3">
|
<div class="d-flex justify-content-end mt-3">
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
{% if is_paginated %}
|
{% if is_paginated %}
|
||||||
{% include 'partials/pagination.html' %}
|
{% include 'partials/pagination.html' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -20,15 +20,15 @@
|
|||||||
{% elif opportunity.get_stage_display == 'Closed Lost' %}bg-danger-soft{% endif %}">
|
{% elif opportunity.get_stage_display == 'Closed Lost' %}bg-danger-soft{% endif %}">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="avatar avatar-xl me-3 mb-3">
|
<div class="avatar avatar-xl me-3 mb-3">
|
||||||
{% if opportunity.car.id_car_make.logo %}
|
{% if opportunity.car.id_car_make.logo %}
|
||||||
<img class="rounded" src="{{ opportunity.car.id_car_make.logo.url }}" alt="" />
|
<img class="rounded" src="{{ opportunity.car.id_car_make.logo.url }}" alt="" />
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% if opportunity.customer %}
|
{% if opportunity.customer %}
|
||||||
<h5 class="mb-4">Opportunity for {{ opportunity.customer }}</h5>
|
<h5 class="mb-4">Opportunity for {{ opportunity.customer }}</h5>
|
||||||
{% elif opportunity.organization %}
|
{% elif opportunity.organization %}
|
||||||
<h5 class="mb-4">Opportunity for {{ opportunity.organization }}</h5>
|
<h5 class="mb-4">Opportunity for {{ opportunity.organization }}</h5>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="d-flex align-items-center justify-content-between mb-3">
|
<div class="d-flex align-items-center justify-content-between mb-3">
|
||||||
<div class="d-flex gap-2">
|
<div class="d-flex gap-2">
|
||||||
@ -50,27 +50,27 @@
|
|||||||
<span class="badge badge-phoenix fs-10 badge-phoenix-secondary">
|
<span class="badge badge-phoenix fs-10 badge-phoenix-secondary">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{{ opportunity.stage }}</span>
|
{{ opportunity.stage }}</span>
|
||||||
<span class="badge badge-phoenix fs-10
|
<span class="badge badge-phoenix fs-10
|
||||||
{% if opportunity.get_stage_display == 'Won' %}badge-phoenix-success
|
{% if opportunity.get_stage_display == 'Won' %}badge-phoenix-success
|
||||||
{% elif opportunity.get_stage_display == 'Lost' %}badge-phoenix-danger{% endif %}">
|
{% elif opportunity.get_stage_display == 'Lost' %}badge-phoenix-danger{% endif %}">
|
||||||
{{ opportunity.get_status_display }}
|
{{ opportunity.get_status_display }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
|
||||||
<div class="d-flex align-items-center">
|
|
||||||
<i class="fa-regular fa-clock"></i>
|
|
||||||
<p class="mb-0 fs-9 fw-semibold text-body-tertiary">{{ opportunity.created|naturalday|capfirst }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<i class="fa-regular fa-clock"></i>
|
||||||
|
<p class="mb-0 fs-9 fw-semibold text-body-tertiary">{{ opportunity.created|naturalday|capfirst }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="deals-company-agent d-flex justify-content-between mb-3">
|
<div class="deals-company-agent d-flex justify-content-between mb-3">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<span class="uil uil-user me-2"></span>
|
<span class="uil uil-user me-2"></span>
|
||||||
<p class="text-body-secondary fw-bold fs-10 mb-0">
|
<p class="text-body-secondary fw-bold fs-10 mb-0">
|
||||||
{{ _("Assigned To") }}{% if request.user.email == opportunity.staff.email %}
|
{{ _("Assigned To") }}{% if request.user.email == opportunity.staff.email %}
|
||||||
{{ _("You") }}
|
{{ _("You") }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ opportunity.staff.name }}</p>
|
{{ opportunity.staff.name }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<table class="mb-3 w-100">
|
<table class="mb-3 w-100">
|
||||||
|
|||||||
@ -2,176 +2,176 @@
|
|||||||
{% load static i18n %}
|
{% load static i18n %}
|
||||||
|
|
||||||
{% block customCSS %}
|
{% block customCSS %}
|
||||||
<style>
|
<style>
|
||||||
.color-card {
|
.color-card {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
border: 2px solid transparent;
|
border: 2px solid transparent;
|
||||||
width: 80px; /* Increased from 3rem for better visibility */
|
width: 80px; /* Increased from 3rem for better visibility */
|
||||||
height: 80px; /* Increased from 3rem for better visibility */
|
height: 80px; /* Increased from 3rem for better visibility */
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-card:hover {
|
.color-card:hover {
|
||||||
transform: translateY(-3px);
|
transform: translateY(-3px);
|
||||||
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-option {
|
.color-option {
|
||||||
display: block;
|
display: block;
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-radio {
|
.color-radio {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-radio:checked + .color-display {
|
.color-radio:checked + .color-display {
|
||||||
border: 2px solid #0d6efd;
|
border: 2px solid #0d6efd;
|
||||||
box-shadow: 0 0 0 3px rgba(13, 110, 253, 0.25);
|
box-shadow: 0 0 0 3px rgba(13, 110, 253, 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-radio:focus + .color-display {
|
.color-radio:focus + .color-display {
|
||||||
border-color: #86b7fe;
|
border-color: #86b7fe;
|
||||||
box-shadow: 0 0 0 3px rgba(13, 110, 253, 0.25);
|
box-shadow: 0 0 0 3px rgba(13, 110, 253, 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-display {
|
.color-display {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-name {
|
.color-name {
|
||||||
background-color: rgba(255, 255, 255, 0.8);
|
background-color: rgba(255, 255, 255, 0.8);
|
||||||
padding: 2px 5px;
|
padding: 2px 5px;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Added for better layout of color options */
|
/* Added for better layout of color options */
|
||||||
.color-options-container {
|
.color-options-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock customCSS %}
|
{% endblock customCSS %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container mt-4">
|
<div class="container mt-4">
|
||||||
<h2>Upload Cars CSV</h2>
|
<h2>Upload Cars CSV</h2>
|
||||||
<div class="d-flex justify-content-end">
|
<div class="d-flex justify-content-end">
|
||||||
<a href="{% static 'sample/cars_sample.csv' %}" class="btn btn-outline-success mt-4">
|
<a href="{% static 'sample/cars_sample.csv' %}" class="btn btn-outline-success mt-4">
|
||||||
<i class="fa-solid fa-file-csv me-2"></i>Download Sample CSV
|
<i class="fa-solid fa-file-csv me-2"></i>Download Sample CSV
|
||||||
</a>
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if messages %}
|
||||||
|
{% for message in messages %}
|
||||||
|
<div class="alert alert-outline-warning }} mt-4" role="alert">
|
||||||
|
{{ message }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<form method="post" enctype="multipart/form-data" class="mt-4">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% if not item %}
|
||||||
|
<div class="row g-4">
|
||||||
|
<div class="col">
|
||||||
|
{% include "purchase_orders/partials/po-select.html" with name="make" target="model" data=make_data pk=po_model.pk %}
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
{% include "purchase_orders/partials/po-select.html" with name="model" target="serie" data=model_data pk=po_model.pk %}
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
{% include "purchase_orders/partials/po-select.html" with name="serie" target="trim" data=serie_data pk=po_model.pk %}
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
{% include "purchase_orders/partials/po-select.html" with name="trim" target="none" data=trim_data pk=po_model.pk %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row g-4">
|
||||||
|
<div class="col">
|
||||||
|
{{form.vendor.label}}
|
||||||
|
{{form.vendor}}
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
{{form.year.label}}
|
||||||
|
{{form.year}}
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
{{form.receiving_date.label}}
|
||||||
|
{{form.receiving_date}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<div class="row g-4">
|
||||||
|
<div class="col">
|
||||||
|
<p class="fs-5 mb-2">{% trans 'Exterior Colors' %}</p>
|
||||||
|
<div class="color-options-container">
|
||||||
|
{% for color in form.fields.exterior.queryset %}
|
||||||
|
<div class="color-card">
|
||||||
|
<label class="color-option">
|
||||||
|
<input class="color-radio" type="radio" name="exterior" value="{{ color.id }}" {% if color.id == form.instance.exterior.id %}checked{% endif %}>
|
||||||
|
<div class="color-display" style="background-color: rgb({{ color.rgb }})">
|
||||||
|
<span class="color-name">{{ color.get_local_name }}</span>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<p class="fs-5 mb-2">{% trans 'Interior Colors' %}</p>
|
||||||
|
<div class="color-options-container">
|
||||||
|
{% for color in form.fields.interior.queryset %}
|
||||||
|
<div class="color-card">
|
||||||
|
<label class="color-option">
|
||||||
|
<input class="color-radio" type="radio" name="interior" value="{{ color.id }}" {% if color.id == form.instance.interior.id %}checked{% endif %}>
|
||||||
|
<div class="color-display" style="background-color: rgb({{ color.rgb }})">
|
||||||
|
<span class="color-name">{{ color.get_local_name }}</span>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if item %}
|
||||||
|
<h3 class="mt-4">List of Items</h3>
|
||||||
|
<ul class="list-group">
|
||||||
|
<li class="list-group-item">
|
||||||
|
<span class="badge bg-primary">{{ item.item_model }}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="csv_file" class="form-label">CSV File</label>
|
||||||
|
<input type="file" class="form-control" id="csv_file" name="csv_file" accept=".csv" required>
|
||||||
|
<div class="form-text">
|
||||||
|
CSV should include columns: vin
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Upload</button>
|
||||||
|
<a href="{{ request.META.HTTP_REFERER }}" class="btn btn-secondary">Cancel</a>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if messages %}
|
|
||||||
{% for message in messages %}
|
|
||||||
<div class="alert alert-outline-warning }} mt-4" role="alert">
|
|
||||||
{{ message }}
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<form method="post" enctype="multipart/form-data" class="mt-4">
|
|
||||||
{% csrf_token %}
|
|
||||||
{% if not item %}
|
|
||||||
<div class="row g-4">
|
|
||||||
<div class="col">
|
|
||||||
{% include "purchase_orders/partials/po-select.html" with name="make" target="model" data=make_data pk=po_model.pk %}
|
|
||||||
</div>
|
|
||||||
<div class="col">
|
|
||||||
{% include "purchase_orders/partials/po-select.html" with name="model" target="serie" data=model_data pk=po_model.pk %}
|
|
||||||
</div>
|
|
||||||
<div class="col">
|
|
||||||
{% include "purchase_orders/partials/po-select.html" with name="serie" target="trim" data=serie_data pk=po_model.pk %}
|
|
||||||
</div>
|
|
||||||
<div class="col">
|
|
||||||
{% include "purchase_orders/partials/po-select.html" with name="trim" target="none" data=trim_data pk=po_model.pk %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row g-4">
|
|
||||||
<div class="col">
|
|
||||||
{{form.vendor.label}}
|
|
||||||
{{form.vendor}}
|
|
||||||
</div>
|
|
||||||
<div class="col">
|
|
||||||
{{form.year.label}}
|
|
||||||
{{form.year}}
|
|
||||||
</div>
|
|
||||||
<div class="col">
|
|
||||||
{{form.receiving_date.label}}
|
|
||||||
{{form.receiving_date}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<div class="row g-4">
|
|
||||||
<div class="col">
|
|
||||||
<p class="fs-5 mb-2">{% trans 'Exterior Colors' %}</p>
|
|
||||||
<div class="color-options-container">
|
|
||||||
{% for color in form.fields.exterior.queryset %}
|
|
||||||
<div class="color-card">
|
|
||||||
<label class="color-option">
|
|
||||||
<input class="color-radio" type="radio" name="exterior" value="{{ color.id }}" {% if color.id == form.instance.exterior.id %}checked{% endif %}>
|
|
||||||
<div class="color-display" style="background-color: rgb({{ color.rgb }})">
|
|
||||||
<span class="color-name">{{ color.get_local_name }}</span>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col">
|
|
||||||
<p class="fs-5 mb-2">{% trans 'Interior Colors' %}</p>
|
|
||||||
<div class="color-options-container">
|
|
||||||
{% for color in form.fields.interior.queryset %}
|
|
||||||
<div class="color-card">
|
|
||||||
<label class="color-option">
|
|
||||||
<input class="color-radio" type="radio" name="interior" value="{{ color.id }}" {% if color.id == form.instance.interior.id %}checked{% endif %}>
|
|
||||||
<div class="color-display" style="background-color: rgb({{ color.rgb }})">
|
|
||||||
<span class="color-name">{{ color.get_local_name }}</span>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% if item %}
|
|
||||||
<h3 class="mt-4">List of Items</h3>
|
|
||||||
<ul class="list-group">
|
|
||||||
<li class="list-group-item">
|
|
||||||
<span class="badge bg-primary">{{ item.item_model }}</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
{% endif %}
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="csv_file" class="form-label">CSV File</label>
|
|
||||||
<input type="file" class="form-control" id="csv_file" name="csv_file" accept=".csv" required>
|
|
||||||
<div class="form-text">
|
|
||||||
CSV should include columns: vin
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-primary">Upload</button>
|
|
||||||
<a href="{{ request.META.HTTP_REFERER }}" class="btn btn-secondary">Cancel</a>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -4,9 +4,9 @@
|
|||||||
{% block title %}
|
{% block title %}
|
||||||
{# Check if an 'object' exists in the context #}
|
{# Check if an 'object' exists in the context #}
|
||||||
{% if object %}
|
{% if object %}
|
||||||
{% trans 'Update Customer'%}
|
{% trans 'Update Customer'%}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% trans 'Add New Customer'%}
|
{% trans 'Add New Customer'%}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|||||||
@ -110,11 +110,11 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</table>
|
</table>
|
||||||
<div class="d-flex justify-content-end mt-3">
|
<div class="d-flex justify-content-end mt-3">
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
{% if is_paginated %}
|
{% if is_paginated %}
|
||||||
{% include 'partials/pagination.html' %}
|
{% include 'partials/pagination.html' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% include 'modal/delete_modal.html' %}
|
{% include 'modal/delete_modal.html' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% load i18n static custom_filters%}
|
{% load i18n static custom_filters%}
|
||||||
{%block title%}{%trans 'Profile'%} {%endblock%}
|
{%block title%}{%trans 'Profile'%} {%endblock%}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|||||||
@ -19,7 +19,7 @@
|
|||||||
<i class="fa fa-save"></i> {{ _("Save") }}
|
<i class="fa fa-save"></i> {{ _("Save") }}
|
||||||
</button>
|
</button>
|
||||||
<a href="{{request.META.HTTP_REFERER}}" class="btn btn-sm btn-phoenix-danger"><i class="fa-solid fa-ban me-1"></i>{% trans "Cancel" %}</a>
|
<a href="{{request.META.HTTP_REFERER}}" class="btn btn-sm btn-phoenix-danger"><i class="fa-solid fa-ban me-1"></i>{% trans "Cancel" %}</a>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|||||||
@ -137,7 +137,7 @@
|
|||||||
<!-- End of Main Content-->
|
<!-- End of Main Content-->
|
||||||
<!-- ===============================================-->
|
<!-- ===============================================-->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="offcanvas offcanvas-end settings-panel border-0" id="settings-offcanvas" tabindex="-1" aria-labelledby="settings-offcanvas">
|
<div class="offcanvas offcanvas-end settings-panel border-0" id="settings-offcanvas" tabindex="-1" aria-labelledby="settings-offcanvas">
|
||||||
<div class="offcanvas-header align-items-start border-bottom flex-column border-translucent">
|
<div class="offcanvas-header align-items-start border-bottom flex-column border-translucent">
|
||||||
|
|||||||
@ -4,9 +4,9 @@
|
|||||||
{% block title %}
|
{% block title %}
|
||||||
{# Check if an 'object' exists in the context #}
|
{# Check if an 'object' exists in the context #}
|
||||||
{% if object %}
|
{% if object %}
|
||||||
{% trans 'Update Group'%}
|
{% trans 'Update Group'%}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% trans 'Add New Group'%}
|
{% trans 'Add New Group'%}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|||||||
@ -4,42 +4,42 @@
|
|||||||
{% block title %}Haikal Bot{% endblock %}
|
{% block title %}Haikal Bot{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.3.2/papaparse.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.3.2/papaparse.min.js"></script>
|
||||||
|
|
||||||
<div class="container mt-5">
|
<div class="container mt-5">
|
||||||
<div class="card shadow-sm">
|
<div class="card shadow-sm">
|
||||||
<div class="card-header d-flex justify-content-between align-items-center">
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
<h5 class="mb-0"><i class="fas fa-robot me-2"></i>{% trans "HaikalBot" %}</h5>
|
<h5 class="mb-0"><i class="fas fa-robot me-2"></i>{% trans "HaikalBot" %}</h5>
|
||||||
<div>
|
<div>
|
||||||
<button id="export-btn" class="btn btn-sm btn-phoenix-secondary" style="display:none;">
|
<button id="export-btn" class="btn btn-sm btn-phoenix-secondary" style="display:none;">
|
||||||
{% trans "Export CSV" %}
|
{% trans "Export CSV" %}
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body" style="max-height: 60vh; overflow-y: auto;" id="chat-history"></div>
|
||||||
|
<div class="card-footer bg-white border-top">
|
||||||
|
<form id="chat-form" class="d-flex align-items-center gap-2">
|
||||||
|
<button type="button" class="btn btn-light" id="mic-btn"><i class="fas fa-microphone"></i></button>
|
||||||
|
<input type="text" class="form-control" id="chat-input" placeholder="{% trans 'Type your question...' %}" required />
|
||||||
|
<button type="submit" class="btn btn-phoenix-primary"><i class="fas fa-paper-plane"></i></button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div id="chart-container" style="display:none;" class="p-4 border-top">
|
||||||
|
<canvas id="chart-canvas" height="200px"></canvas>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body" style="max-height: 60vh; overflow-y: auto;" id="chat-history"></div>
|
|
||||||
<div class="card-footer bg-white border-top">
|
|
||||||
<form id="chat-form" class="d-flex align-items-center gap-2">
|
|
||||||
<button type="button" class="btn btn-light" id="mic-btn"><i class="fas fa-microphone"></i></button>
|
|
||||||
<input type="text" class="form-control" id="chat-input" placeholder="{% trans 'Type your question...' %}" required />
|
|
||||||
<button type="submit" class="btn btn-phoenix-primary"><i class="fas fa-paper-plane"></i></button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div id="chart-container" style="display:none;" class="p-4 border-top">
|
|
||||||
<canvas id="chart-canvas" height="200px"></canvas>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const chatHistory = document.getElementById('chat-history');
|
const chatHistory = document.getElementById('chat-history');
|
||||||
const chartContainer = document.getElementById('chart-container');
|
const chartContainer = document.getElementById('chart-container');
|
||||||
const chartCanvas = document.getElementById('chart-canvas');
|
const chartCanvas = document.getElementById('chart-canvas');
|
||||||
const exportBtn = document.getElementById('export-btn');
|
const exportBtn = document.getElementById('export-btn');
|
||||||
let chartInstance = null;
|
let chartInstance = null;
|
||||||
let latestDataTable = null;
|
let latestDataTable = null;
|
||||||
|
|
||||||
function getCookie(name) {
|
function getCookie(name) {
|
||||||
let cookieValue = null;
|
let cookieValue = null;
|
||||||
if (document.cookie && document.cookie !== "") {
|
if (document.cookie && document.cookie !== "") {
|
||||||
const cookies = document.cookie.split(";");
|
const cookies = document.cookie.split(";");
|
||||||
@ -54,135 +54,135 @@ function getCookie(name) {
|
|||||||
return cookieValue;
|
return cookieValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
function speak(text) {
|
function speak(text) {
|
||||||
const utterance = new SpeechSynthesisUtterance(text);
|
const utterance = new SpeechSynthesisUtterance(text);
|
||||||
utterance.lang = document.documentElement.lang || "en";
|
utterance.lang = document.documentElement.lang || "en";
|
||||||
window.speechSynthesis.speak(utterance);
|
window.speechSynthesis.speak(utterance);
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderTable(data) {
|
function renderTable(data) {
|
||||||
latestDataTable = data;
|
latestDataTable = data;
|
||||||
exportBtn.style.display = 'inline-block';
|
exportBtn.style.display = 'inline-block';
|
||||||
const headers = Object.keys(data[0]);
|
const headers = Object.keys(data[0]);
|
||||||
let html = '<div class="table-responsive"><table class="table table-bordered table-striped"><thead><tr>';
|
let html = '<div class="table-responsive"><table class="table table-bordered table-striped"><thead><tr>';
|
||||||
headers.forEach(h => html += `<th>${h}</th>`);
|
headers.forEach(h => html += `<th>${h}</th>`);
|
||||||
html += '</tr></thead><tbody>';
|
html += '</tr></thead><tbody>';
|
||||||
data.forEach(row => {
|
data.forEach(row => {
|
||||||
html += '<tr>' + headers.map(h => `<td>${row[h]}</td>`).join('') + '</tr>';
|
html += '<tr>' + headers.map(h => `<td>${row[h]}</td>`).join('') + '</tr>';
|
||||||
});
|
});
|
||||||
html += '</tbody></table></div>';
|
html += '</tbody></table></div>';
|
||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
function appendMessage(role, htmlContent) {
|
function appendMessage(role, htmlContent) {
|
||||||
const align = role === 'AI' ? 'bg-secondary-light' : 'bg-primary-light';
|
const align = role === 'AI' ? 'bg-secondary-light' : 'bg-primary-light';
|
||||||
chatHistory.innerHTML += `
|
chatHistory.innerHTML += `
|
||||||
<div class="mb-3 p-3 rounded ${align}">
|
<div class="mb-3 p-3 rounded ${align}">
|
||||||
<strong>${role}:</strong><br>${htmlContent}
|
<strong>${role}:</strong><br>${htmlContent}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
chatHistory.scrollTop = chatHistory.scrollHeight;
|
chatHistory.scrollTop = chatHistory.scrollHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('chat-form').addEventListener('submit', async function(e) {
|
document.getElementById('chat-form').addEventListener('submit', async function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const input = document.getElementById('chat-input');
|
const input = document.getElementById('chat-input');
|
||||||
const prompt = input.value.trim();
|
const prompt = input.value.trim();
|
||||||
const csrfToken = getCookie("csrftoken");
|
const csrfToken = getCookie("csrftoken");
|
||||||
|
|
||||||
if (!prompt) return;
|
if (!prompt) return;
|
||||||
|
|
||||||
appendMessage('You', prompt);
|
appendMessage('You', prompt);
|
||||||
input.value = "";
|
input.value = "";
|
||||||
chartContainer.style.display = 'none';
|
chartContainer.style.display = 'none';
|
||||||
exportBtn.style.display = 'none';
|
exportBtn.style.display = 'none';
|
||||||
if (chartInstance) {
|
if (chartInstance) {
|
||||||
chartInstance.destroy();
|
chartInstance.destroy();
|
||||||
chartInstance = null;
|
chartInstance = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch("{% url 'haikalbot' %}", {
|
const response = await fetch("{% url 'haikalbot' %}", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
"X-CSRFToken": csrfToken
|
"X-CSRFToken": csrfToken
|
||||||
},
|
},
|
||||||
body: new URLSearchParams({ prompt })
|
body: new URLSearchParams({ prompt })
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
|
|
||||||
// Show chart if available
|
// Show chart if available
|
||||||
if (result.chart && result.chart.type && result.chart.labels && result.chart.data) {
|
if (result.chart && result.chart.type && result.chart.labels && result.chart.data) {
|
||||||
chartInstance = new Chart(chartCanvas, {
|
chartInstance = new Chart(chartCanvas, {
|
||||||
type: result.chart.type,
|
type: result.chart.type,
|
||||||
data: {
|
data: {
|
||||||
labels: result.chart.labels,
|
labels: result.chart.labels,
|
||||||
datasets: [{
|
datasets: [{
|
||||||
label: result.chart.labels.join(", "),
|
label: result.chart.labels.join(", "),
|
||||||
data: result.chart.data,
|
data: result.chart.data,
|
||||||
backgroundColor: result.chart.backgroundColor || []
|
backgroundColor: result.chart.backgroundColor || []
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
responsive: true,
|
responsive: true,
|
||||||
plugins: {
|
plugins: {
|
||||||
title: {
|
title: {
|
||||||
display: true,
|
display: true,
|
||||||
text: result.chart.type.toUpperCase()
|
text: result.chart.type.toUpperCase()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
chartContainer.style.display = 'block';
|
||||||
});
|
appendMessage('AI', `{% trans "Chart displayed below." %}`);
|
||||||
chartContainer.style.display = 'block';
|
|
||||||
appendMessage('AI', `{% trans "Chart displayed below." %}`);
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Table if list of objects
|
// Table if list of objects
|
||||||
if (Array.isArray(result.data) && result.data.length && typeof result.data[0] === 'object') {
|
if (Array.isArray(result.data) && result.data.length && typeof result.data[0] === 'object') {
|
||||||
const tableHTML = renderTable(result.data);
|
const tableHTML = renderTable(result.data);
|
||||||
appendMessage('AI', tableHTML);
|
appendMessage('AI', tableHTML);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
const content = typeof result.data === 'object'
|
const content = typeof result.data === 'object'
|
||||||
? `<pre>${JSON.stringify(result.data, null, 2)}</pre>`
|
? `<pre>${JSON.stringify(result.data, null, 2)}</pre>`
|
||||||
: `<p>${result.data}</p>`;
|
: `<p>${result.data}</p>`;
|
||||||
appendMessage('AI', content);
|
appendMessage('AI', content);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('export-btn').addEventListener('click', () => {
|
document.getElementById('export-btn').addEventListener('click', () => {
|
||||||
if (!latestDataTable) return;
|
if (!latestDataTable) return;
|
||||||
const csv = Papa.unparse(latestDataTable);
|
const csv = Papa.unparse(latestDataTable);
|
||||||
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
|
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.href = url;
|
a.href = url;
|
||||||
a.download = 'haikal_data.csv';
|
a.download = 'haikal_data.csv';
|
||||||
document.body.appendChild(a);
|
document.body.appendChild(a);
|
||||||
a.click();
|
a.click();
|
||||||
document.body.removeChild(a);
|
document.body.removeChild(a);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Voice input (speech-to-text)
|
// Voice input (speech-to-text)
|
||||||
document.getElementById('mic-btn').addEventListener('click', () => {
|
document.getElementById('mic-btn').addEventListener('click', () => {
|
||||||
const recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
|
const recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
|
||||||
recognition.lang = document.documentElement.lang || "en";
|
recognition.lang = document.documentElement.lang || "en";
|
||||||
recognition.interimResults = false;
|
recognition.interimResults = false;
|
||||||
recognition.maxAlternatives = 1;
|
recognition.maxAlternatives = 1;
|
||||||
|
|
||||||
recognition.onresult = (event) => {
|
recognition.onresult = (event) => {
|
||||||
const speech = event.results[0][0].transcript;
|
const speech = event.results[0][0].transcript;
|
||||||
document.getElementById('chat-input').value = speech;
|
document.getElementById('chat-input').value = speech;
|
||||||
};
|
};
|
||||||
|
|
||||||
recognition.onerror = (e) => {
|
recognition.onerror = (e) => {
|
||||||
console.error('Speech recognition error', e);
|
console.error('Speech recognition error', e);
|
||||||
};
|
};
|
||||||
|
|
||||||
recognition.start();
|
recognition.start();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -6,355 +6,355 @@
|
|||||||
{% endblock title %}
|
{% endblock title %}
|
||||||
|
|
||||||
{% block description %}
|
{% block description %}
|
||||||
AI assistant
|
AI assistant
|
||||||
{% endblock description %}
|
{% endblock description %}
|
||||||
|
|
||||||
{% block customCSS %}
|
{% block customCSS %}
|
||||||
<style>
|
<style>
|
||||||
.chart-container {
|
.chart-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
.chat-container {
|
.chat-container {
|
||||||
max-width: 800px;
|
max-width: 800px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
.chat-textarea {
|
.chat-textarea {
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
resize: none;
|
resize: none;
|
||||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||||
border: 1px solid #dee2e6;
|
border: 1px solid #dee2e6;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
}
|
}
|
||||||
.chat-textarea:focus {
|
.chat-textarea:focus {
|
||||||
border-color: #86b7fe;
|
border-color: #86b7fe;
|
||||||
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
|
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
.send-button {
|
.send-button {
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
padding: 10px 25px;
|
padding: 10px 25px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
}
|
}
|
||||||
.textarea-container {
|
.textarea-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
.textarea-footer {
|
.textarea-footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
color: #6c757d;
|
color: #6c757d;
|
||||||
}
|
}
|
||||||
.character-count.warning {
|
.character-count.warning {
|
||||||
color: #dc3545;
|
color: #dc3545;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock customCSS %}
|
{% endblock customCSS %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||||
<div class="card shadow-none mb-3">
|
<div class="card shadow-none mb-3">
|
||||||
<div class="card-header d-flex justify-content-between align-items-center">
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
||||||
<img class="d-dark-none" src="{% static 'images/favicons/haikalbot_v1.png' %}" alt="{% trans 'home' %}" width="32" />
|
<img class="d-dark-none" src="{% static 'images/favicons/haikalbot_v1.png' %}" alt="{% trans 'home' %}" width="32" />
|
||||||
<img class="d-light-none" src="{% static 'images/favicons/haikalbot_v2.png' %}" alt="{% trans 'home' %}" width="32" />
|
<img class="d-light-none" src="{% static 'images/favicons/haikalbot_v2.png' %}" alt="{% trans 'home' %}" width="32" />
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex gap-3">
|
<div class="d-flex gap-3">
|
||||||
<span id="clearChatBtn" class="translate-middle-y cursor-pointer" title="{% if LANGUAGE_CODE == 'ar' %}مسح المحادثة{% else %}Clear Chat{% endif %}">
|
<span id="clearChatBtn" class="translate-middle-y cursor-pointer" title="{% if LANGUAGE_CODE == 'ar' %}مسح المحادثة{% else %}Clear Chat{% endif %}">
|
||||||
<i class="fas fa-trash-alt text-danger"></i>
|
<i class="fas fa-trash-alt text-danger"></i>
|
||||||
</span>
|
</span>
|
||||||
<span id="exportChatBtn" class="translate-middle-y cursor-pointer" title="{% if LANGUAGE_CODE == 'ar' %}تصدير المحادثة{% else %}Export Chat{% endif %}">
|
<span id="exportChatBtn" class="translate-middle-y cursor-pointer" title="{% if LANGUAGE_CODE == 'ar' %}تصدير المحادثة{% else %}Export Chat{% endif %}">
|
||||||
<i class="fas fa-download text-success"></i>
|
<i class="fas fa-download text-success"></i>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body p-0">
|
<div class="card-body p-0">
|
||||||
<div id="chatMessages" class="overflow-auto p-3" style="height: 60vh;"></div>
|
<div id="chatMessages" class="overflow-auto p-3" style="height: 60vh;"></div>
|
||||||
<div class="bg-100 border-top p-3">
|
<div class="bg-100 border-top p-3">
|
||||||
<div class="d-flex gap-2 flex-wrap mb-3" id="suggestionChips">
|
<div class="d-flex gap-2 flex-wrap mb-3" id="suggestionChips">
|
||||||
<button class="btn btn-sm btn-phoenix-primary suggestion-chip">{{ _("How many cars are in inventory")}}?</button>
|
<button class="btn btn-sm btn-phoenix-primary suggestion-chip">{{ _("How many cars are in inventory")}}?</button>
|
||||||
<button class="btn btn-sm btn-phoenix-primary suggestion-chip">{{ _("Show me sales analysis")}}</button>
|
<button class="btn btn-sm btn-phoenix-primary suggestion-chip">{{ _("Show me sales analysis")}}</button>
|
||||||
<button class="btn btn-sm btn-phoenix-primary suggestion-chip">{{ _("What are the best-selling cars")}}?</button>
|
<button class="btn btn-sm btn-phoenix-primary suggestion-chip">{{ _("What are the best-selling cars")}}?</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="chat-container">
|
<div class="chat-container">
|
||||||
<div class="textarea-container mb-3">
|
<div class="textarea-container mb-3">
|
||||||
<label for="messageInput"></label>
|
<label for="messageInput"></label>
|
||||||
<textarea class="form-control chat-textarea" id="messageInput" rows="3" placeholder="{{ _("Type your message here")}}..." maxlength="400"></textarea>
|
<textarea class="form-control chat-textarea" id="messageInput" rows="3" placeholder="{{ _("Type your message here")}}..." maxlength="400"></textarea>
|
||||||
<div class="textarea-footer">
|
<div class="textarea-footer">
|
||||||
<div class="character-count">
|
<div class="character-count">
|
||||||
<span id="charCount">0</span>/400
|
<span id="charCount">0</span>/400
|
||||||
|
</div>
|
||||||
|
<span class="send-button position-absolute top-50 end-0 translate-middle-y cursor-pointer" id="sendMessageBtn" disabled>
|
||||||
|
<i class="fas fa-paper-plane text-body"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span class="send-button position-absolute top-50 end-0 translate-middle-y cursor-pointer" id="sendMessageBtn" disabled>
|
|
||||||
<i class="fas fa-paper-plane text-body"></i>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Global configuration
|
// Global configuration
|
||||||
const MAX_MESSAGE_LENGTH = 400;
|
const MAX_MESSAGE_LENGTH = 400;
|
||||||
const WARNING_THRESHOLD = 350;
|
const WARNING_THRESHOLD = 350;
|
||||||
const isArabic = '{{ LANGUAGE_CODE }}' === 'ar';
|
const isArabic = '{{ LANGUAGE_CODE }}' === 'ar';
|
||||||
|
|
||||||
// Chart rendering function
|
// Chart rendering function
|
||||||
function renderInsightChart(labels, data, chartType = 'bar', title = 'Insight Chart') {
|
function renderInsightChart(labels, data, chartType = 'bar', title = 'Insight Chart') {
|
||||||
const canvasId = 'chart_' + Date.now();
|
const canvasId = 'chart_' + Date.now();
|
||||||
const chartHtml = `<div class="chart-container"><canvas id="${canvasId}"></canvas></div>`;
|
const chartHtml = `<div class="chart-container"><canvas id="${canvasId}"></canvas></div>`;
|
||||||
$('#chatMessages').append(chartHtml);
|
$('#chatMessages').append(chartHtml);
|
||||||
|
|
||||||
new Chart(document.getElementById(canvasId), {
|
new Chart(document.getElementById(canvasId), {
|
||||||
type: chartType,
|
type: chartType,
|
||||||
data: {
|
data: {
|
||||||
labels: labels,
|
labels: labels,
|
||||||
datasets: [{
|
datasets: [{
|
||||||
label: title,
|
label: title,
|
||||||
data: data,
|
data: data,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
backgroundColor: 'rgba(75, 192, 192, 0.5)',
|
backgroundColor: 'rgba(75, 192, 192, 0.5)',
|
||||||
borderColor: 'rgba(75, 192, 192, 1)',
|
borderColor: 'rgba(75, 192, 192, 1)',
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
responsive: true,
|
responsive: true,
|
||||||
plugins: {
|
plugins: {
|
||||||
legend: { display: true },
|
legend: { display: true },
|
||||||
title: { display: true, text: title }
|
title: { display: true, text: title }
|
||||||
},
|
},
|
||||||
scales: {
|
scales: {
|
||||||
y: { beginAtZero: true }
|
y: { beginAtZero: true }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
// DOM elements
|
// DOM elements
|
||||||
const messageInput = $('#messageInput');
|
const messageInput = $('#messageInput');
|
||||||
const charCount = $('#charCount');
|
const charCount = $('#charCount');
|
||||||
const sendMessageBtn = $('#sendMessageBtn');
|
const sendMessageBtn = $('#sendMessageBtn');
|
||||||
const chatMessages = $('#chatMessages');
|
const chatMessages = $('#chatMessages');
|
||||||
const clearChatBtn = $('#clearChatBtn');
|
const clearChatBtn = $('#clearChatBtn');
|
||||||
const exportChatBtn = $('#exportChatBtn');
|
const exportChatBtn = $('#exportChatBtn');
|
||||||
const suggestionChips = $('.suggestion-chip');
|
const suggestionChips = $('.suggestion-chip');
|
||||||
|
|
||||||
// Initialize character count
|
// Initialize character count
|
||||||
updateCharacterCount();
|
updateCharacterCount();
|
||||||
|
|
||||||
// Event handlers
|
// Event handlers
|
||||||
messageInput.on('input', handleInput);
|
messageInput.on('input', handleInput);
|
||||||
messageInput.on('keydown', handleKeyDown);
|
messageInput.on('keydown', handleKeyDown);
|
||||||
sendMessageBtn.on('click', sendMessage);
|
sendMessageBtn.on('click', sendMessage);
|
||||||
suggestionChips.on('click', handleSuggestionClick);
|
suggestionChips.on('click', handleSuggestionClick);
|
||||||
clearChatBtn.on('click', clearChat);
|
clearChatBtn.on('click', clearChat);
|
||||||
exportChatBtn.on('click', exportChat);
|
exportChatBtn.on('click', exportChat);
|
||||||
|
|
||||||
// Input handling
|
// Input handling
|
||||||
function handleInput() {
|
function handleInput() {
|
||||||
updateCharacterCount();
|
updateCharacterCount();
|
||||||
autoResizeTextarea();
|
autoResizeTextarea();
|
||||||
toggleSendButton();
|
toggleSendButton();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleKeyDown(e) {
|
function handleKeyDown(e) {
|
||||||
if (e.key === 'Enter' && !e.shiftKey) {
|
if (e.key === 'Enter' && !e.shiftKey) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!sendMessageBtn.prop('disabled')) {
|
if (!sendMessageBtn.prop('disabled')) {
|
||||||
|
sendMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSuggestionClick() {
|
||||||
|
messageInput.val($(this).text().trim());
|
||||||
|
updateCharacterCount();
|
||||||
|
autoResizeTextarea();
|
||||||
|
sendMessageBtn.prop('disabled', false);
|
||||||
sendMessage();
|
sendMessage();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleSuggestionClick() {
|
|
||||||
messageInput.val($(this).text().trim());
|
|
||||||
updateCharacterCount();
|
|
||||||
autoResizeTextarea();
|
|
||||||
sendMessageBtn.prop('disabled', false);
|
|
||||||
sendMessage();
|
|
||||||
}
|
|
||||||
|
|
||||||
// UI utilities
|
// UI utilities
|
||||||
function updateCharacterCount() {
|
function updateCharacterCount() {
|
||||||
const currentLength = messageInput.val().length;
|
const currentLength = messageInput.val().length;
|
||||||
charCount.text(currentLength);
|
charCount.text(currentLength);
|
||||||
|
|
||||||
if (currentLength > WARNING_THRESHOLD) {
|
if (currentLength > WARNING_THRESHOLD) {
|
||||||
charCount.parent().addClass('warning');
|
charCount.parent().addClass('warning');
|
||||||
} else {
|
} else {
|
||||||
charCount.parent().removeClass('warning');
|
charCount.parent().removeClass('warning');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function autoResizeTextarea() {
|
function autoResizeTextarea() {
|
||||||
messageInput[0].style.height = 'auto';
|
messageInput[0].style.height = 'auto';
|
||||||
messageInput[0].style.height = (messageInput[0].scrollHeight) + 'px';
|
messageInput[0].style.height = (messageInput[0].scrollHeight) + 'px';
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleSendButton() {
|
function toggleSendButton() {
|
||||||
sendMessageBtn.prop('disabled', !messageInput.val().trim());
|
sendMessageBtn.prop('disabled', !messageInput.val().trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chat actions
|
// Chat actions
|
||||||
function sendMessage() {
|
function sendMessage() {
|
||||||
const message = messageInput.val().trim();
|
const message = messageInput.val().trim();
|
||||||
if (!message) return;
|
if (!message) return;
|
||||||
|
|
||||||
// Add user message to chat
|
// Add user message to chat
|
||||||
addMessage(message, true);
|
addMessage(message, true);
|
||||||
|
|
||||||
// Clear input and reset UI
|
// Clear input and reset UI
|
||||||
resetInputUI();
|
resetInputUI();
|
||||||
|
|
||||||
// Show typing indicator
|
// Show typing indicator
|
||||||
showTypingIndicator();
|
showTypingIndicator();
|
||||||
|
|
||||||
// Send to backend
|
// Send to backend
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: '{% url "haikalbot:haikalbot" %}',
|
url: '{% url "haikalbot:haikalbot" %}',
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
data: JSON.stringify({
|
data: JSON.stringify({
|
||||||
prompt: message,
|
prompt: message,
|
||||||
language: '{{ LANGUAGE_CODE }}'
|
language: '{{ LANGUAGE_CODE }}'
|
||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
'X-CSRFToken': '{{ csrf_token }}'
|
'X-CSRFToken': '{{ csrf_token }}'
|
||||||
},
|
},
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
processBotResponse(response);
|
processBotResponse(response);
|
||||||
},
|
},
|
||||||
error: function(xhr, status, error) {
|
error: function(xhr, status, error) {
|
||||||
handleRequestError(error);
|
handleRequestError(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetInputUI() {
|
function resetInputUI() {
|
||||||
messageInput.val('').css('height', 'auto');
|
messageInput.val('').css('height', 'auto');
|
||||||
charCount.text('0').parent().removeClass('warning');
|
charCount.text('0').parent().removeClass('warning');
|
||||||
sendMessageBtn.prop('disabled', true);
|
sendMessageBtn.prop('disabled', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function processBotResponse(response) {
|
function processBotResponse(response) {
|
||||||
hideTypingIndicator();
|
hideTypingIndicator();
|
||||||
|
|
||||||
// Debug response structure
|
// Debug response structure
|
||||||
console.log("API Response:", response);
|
console.log("API Response:", response);
|
||||||
|
|
||||||
let botResponse = '';
|
let botResponse = '';
|
||||||
|
|
||||||
// Check for direct response first
|
// Check for direct response first
|
||||||
if (response.response) {
|
if (response.response) {
|
||||||
botResponse = response.response;
|
botResponse = response.response;
|
||||||
}
|
}
|
||||||
// Then check for insights data
|
// Then check for insights data
|
||||||
else if (hasInsightsData(response)) {
|
else if (hasInsightsData(response)) {
|
||||||
botResponse = formatInsightsResponse(response);
|
botResponse = formatInsightsResponse(response);
|
||||||
}
|
}
|
||||||
// Fallback
|
// Fallback
|
||||||
else {
|
else {
|
||||||
botResponse = isArabic ?
|
botResponse = isArabic ?
|
||||||
'عذرًا، لم أتمكن من معالجة طلبك. يبدو أن هيكل الاستجابة غير متوقع.' :
|
'عذرًا، لم أتمكن من معالجة طلبك. يبدو أن هيكل الاستجابة غير متوقع.' :
|
||||||
'Sorry, I couldn\'t process your request. The response structure appears unexpected.';
|
'Sorry, I couldn\'t process your request. The response structure appears unexpected.';
|
||||||
console.error("Unexpected response structure:", response);
|
console.error("Unexpected response structure:", response);
|
||||||
}
|
}
|
||||||
|
|
||||||
addMessage(botResponse, false);
|
addMessage(botResponse, false);
|
||||||
scrollToBottom();
|
scrollToBottom();
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasInsightsData(response) {
|
function hasInsightsData(response) {
|
||||||
return response.insights || response['التحليلات'] ||
|
return response.insights || response['التحليلات'] ||
|
||||||
response.recommendations || response['التوصيات'];
|
response.recommendations || response['التوصيات'];
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleRequestError(error) {
|
function handleRequestError(error) {
|
||||||
hideTypingIndicator();
|
hideTypingIndicator();
|
||||||
const errorMsg = isArabic ?
|
const errorMsg = isArabic ?
|
||||||
'عذرًا، حدث خطأ أثناء معالجة طلبك. يرجى المحاولة مرة أخرى.' :
|
'عذرًا، حدث خطأ أثناء معالجة طلبك. يرجى المحاولة مرة أخرى.' :
|
||||||
'Sorry, an error occurred while processing your request. Please try again.';
|
'Sorry, an error occurred while processing your request. Please try again.';
|
||||||
addMessage(errorMsg, false);
|
addMessage(errorMsg, false);
|
||||||
console.error('API Error:', error);
|
console.error('API Error:', error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chat management
|
// Chat management
|
||||||
function clearChat() {
|
function clearChat() {
|
||||||
if (confirm(isArabic ?
|
if (confirm(isArabic ?
|
||||||
'هل أنت متأكد من أنك تريد مسح المحادثة؟' :
|
'هل أنت متأكد من أنك تريد مسح المحادثة؟' :
|
||||||
'Are you sure you want to clear the chat?')) {
|
'Are you sure you want to clear the chat?')) {
|
||||||
const welcomeMessage = chatMessages.children().first();
|
const welcomeMessage = chatMessages.children().first();
|
||||||
chatMessages.empty().append(welcomeMessage);
|
chatMessages.empty().append(welcomeMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function exportChat() {
|
function exportChat() {
|
||||||
let chatContent = '';
|
let chatContent = '';
|
||||||
$('.message').each(function() {
|
$('.message').each(function() {
|
||||||
const isUser = $(this).hasClass('user-message');
|
const isUser = $(this).hasClass('user-message');
|
||||||
const sender = isUser ?
|
const sender = isUser ?
|
||||||
(isArabic ? 'أنت' : 'You') :
|
(isArabic ? 'أنت' : 'You') :
|
||||||
(isArabic ? 'المساعد الذكي' : 'AI Assistant');
|
(isArabic ? 'المساعد الذكي' : 'AI Assistant');
|
||||||
const text = $(this).find('.chat-message').text().trim();
|
const text = $(this).find('.chat-message').text().trim();
|
||||||
const time = $(this).find('.text-400').text().trim();
|
const time = $(this).find('.text-400').text().trim();
|
||||||
|
|
||||||
chatContent += `${sender} (${time}):\n${text}\n\n`;
|
chatContent += `${sender} (${time}):\n${text}\n\n`;
|
||||||
});
|
});
|
||||||
|
|
||||||
downloadTextFile(chatContent, 'chat-export-' + new Date().toISOString().slice(0, 10) + '.txt');
|
downloadTextFile(chatContent, 'chat-export-' + new Date().toISOString().slice(0, 10) + '.txt');
|
||||||
}
|
}
|
||||||
|
|
||||||
function downloadTextFile(content, filename) {
|
function downloadTextFile(content, filename) {
|
||||||
const blob = new Blob([content], { type: 'text/plain' });
|
const blob = new Blob([content], { type: 'text/plain' });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.href = url;
|
a.href = url;
|
||||||
a.download = filename;
|
a.download = filename;
|
||||||
document.body.appendChild(a);
|
document.body.appendChild(a);
|
||||||
a.click();
|
a.click();
|
||||||
document.body.removeChild(a);
|
document.body.removeChild(a);
|
||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Message display functions
|
// Message display functions
|
||||||
function addMessage(text, isUser) {
|
function addMessage(text, isUser) {
|
||||||
const time = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
const time = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
||||||
const messageClass = isUser ? 'user-message justify-content-between' : '';
|
const messageClass = isUser ? 'user-message justify-content-between' : '';
|
||||||
|
|
||||||
const avatarHtml = getAvatarHtml(isUser);
|
const avatarHtml = getAvatarHtml(isUser);
|
||||||
const messageHtml = getMessageHtml(text, isUser, time);
|
const messageHtml = getMessageHtml(text, isUser, time);
|
||||||
|
|
||||||
const fullMessageHtml = `
|
const fullMessageHtml = `
|
||||||
<div class="message d-flex mb-3 ${messageClass}">
|
<div class="message d-flex mb-3 ${messageClass}">
|
||||||
${avatarHtml}
|
${avatarHtml}
|
||||||
${messageHtml}
|
${messageHtml}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
chatMessages.append(fullMessageHtml);
|
chatMessages.append(fullMessageHtml);
|
||||||
scrollToBottom();
|
scrollToBottom();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAvatarHtml(isUser) {
|
function getAvatarHtml(isUser) {
|
||||||
if (isUser) {
|
if (isUser) {
|
||||||
return `
|
return `
|
||||||
<div class="avatar avatar-l ms-3 order-1">
|
<div class="avatar avatar-l ms-3 order-1">
|
||||||
<div class="avatar-name rounded-circle">
|
<div class="avatar-name rounded-circle">
|
||||||
<span><i class="fas fa-user"></i></span>
|
<span><i class="fas fa-user"></i></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
return `
|
return `
|
||||||
<div class="me-3">
|
<div class="me-3">
|
||||||
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
||||||
<img class="d-dark-none" src="{% static 'images/favicons/haikalbot_v1.png' %}" width="32" />
|
<img class="d-dark-none" src="{% static 'images/favicons/haikalbot_v1.png' %}" width="32" />
|
||||||
@ -362,11 +362,11 @@ $(document).ready(function() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMessageHtml(text, isUser, time) {
|
function getMessageHtml(text, isUser, time) {
|
||||||
if (isUser) {
|
if (isUser) {
|
||||||
return `
|
return `
|
||||||
<div class="flex-1 order-0">
|
<div class="flex-1 order-0">
|
||||||
<div class="w-xxl-75 ms-auto">
|
<div class="w-xxl-75 ms-auto">
|
||||||
<div class="d-flex hover-actions-trigger align-items-center">
|
<div class="d-flex hover-actions-trigger align-items-center">
|
||||||
@ -385,11 +385,11 @@ $(document).ready(function() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const processedText = marked.parse(text);
|
const processedText = marked.parse(text);
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<div class="w-xxl-75">
|
<div class="w-xxl-75">
|
||||||
<div class="d-flex hover-actions-trigger align-items-center">
|
<div class="d-flex hover-actions-trigger align-items-center">
|
||||||
@ -408,10 +408,10 @@ $(document).ready(function() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function showTypingIndicator() {
|
function showTypingIndicator() {
|
||||||
const typingHtml = `
|
const typingHtml = `
|
||||||
<div class="message d-flex mb-3" id="typingIndicator">
|
<div class="message d-flex mb-3" id="typingIndicator">
|
||||||
<div class="avatar avatar-l me-3">
|
<div class="avatar avatar-l me-3">
|
||||||
<div class="avatar-name rounded-circle">
|
<div class="avatar-name rounded-circle">
|
||||||
@ -425,97 +425,97 @@ $(document).ready(function() {
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
chatMessages.append(typingHtml);
|
chatMessages.append(typingHtml);
|
||||||
scrollToBottom();
|
scrollToBottom();
|
||||||
}
|
}
|
||||||
|
|
||||||
function hideTypingIndicator() {
|
function hideTypingIndicator() {
|
||||||
$('#typingIndicator').remove();
|
$('#typingIndicator').remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
function scrollToBottom() {
|
function scrollToBottom() {
|
||||||
chatMessages.scrollTop(chatMessages[0].scrollHeight);
|
chatMessages.scrollTop(chatMessages[0].scrollHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insights formatting
|
// Insights formatting
|
||||||
function formatInsightsResponse(response) {
|
function formatInsightsResponse(response) {
|
||||||
console.log("Formatting insights response:", response);
|
console.log("Formatting insights response:", response);
|
||||||
|
|
||||||
let formattedResponse = '';
|
let formattedResponse = '';
|
||||||
|
|
||||||
// Get data using both possible key formats
|
// Get data using both possible key formats
|
||||||
const insightsData = response.insights || response['التحليلات'] || [];
|
const insightsData = response.insights || response['التحليلات'] || [];
|
||||||
const recommendationsData = response.recommendations || response['التوصيات'] || [];
|
const recommendationsData = response.recommendations || response['التوصيات'] || [];
|
||||||
|
|
||||||
// Process insights
|
// Process insights
|
||||||
if (insightsData.length > 0) {
|
if (insightsData.length > 0) {
|
||||||
formattedResponse += isArabic ? '## نتائج التحليل\n\n' : '## Analysis Results\n\n';
|
formattedResponse += isArabic ? '## نتائج التحليل\n\n' : '## Analysis Results\n\n';
|
||||||
|
|
||||||
insightsData.forEach(insight => {
|
insightsData.forEach(insight => {
|
||||||
if (insight.type) {
|
if (insight.type) {
|
||||||
formattedResponse += `### ${insight.type}\n\n`;
|
formattedResponse += `### ${insight.type}\n\n`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (insight.results && Array.isArray(insight.results)) {
|
if (insight.results && Array.isArray(insight.results)) {
|
||||||
insight.results.forEach(result => {
|
insight.results.forEach(result => {
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
formattedResponse += `- **${result.model || ''}**: ${result.error}\n`;
|
formattedResponse += `- **${result.model || ''}**: ${result.error}\n`;
|
||||||
} else if (result.count !== undefined) {
|
} else if (result.count !== undefined) {
|
||||||
formattedResponse += `- **${result.model || ''}**: ${result.count}\n`;
|
formattedResponse += `- **${result.model || ''}**: ${result.count}\n`;
|
||||||
} else if (result.value !== undefined) {
|
} else if (result.value !== undefined) {
|
||||||
const field = getLocalizedValue(result, 'field', 'الحقل');
|
const field = getLocalizedValue(result, 'field', 'الحقل');
|
||||||
const statType = getLocalizedValue(result, 'statistic_type', 'نوع_الإحصاء');
|
const statType = getLocalizedValue(result, 'statistic_type', 'نوع_الإحصاء');
|
||||||
formattedResponse += `- **${result.model || ''}**: ${statType} ${isArabic ? 'لـ' : 'of'} ${field} = ${result.value}\n`;
|
formattedResponse += `- **${result.model || ''}**: ${statType} ${isArabic ? 'لـ' : 'of'} ${field} = ${result.value}\n`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
formattedResponse += '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (insight.relationships && Array.isArray(insight.relationships)) {
|
||||||
|
formattedResponse += isArabic ? '### العلاقات\n\n' : '### Relationships\n\n';
|
||||||
|
insight.relationships.forEach(rel => {
|
||||||
|
const from = getLocalizedValue(rel, 'from', 'من');
|
||||||
|
const to = getLocalizedValue(rel, 'to', 'إلى');
|
||||||
|
const type = getLocalizedValue(rel, 'type', 'نوع');
|
||||||
|
formattedResponse += `- ${from} → ${to} (${type})\n`;
|
||||||
|
});
|
||||||
|
formattedResponse += '\n';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
formattedResponse += '\n';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (insight.relationships && Array.isArray(insight.relationships)) {
|
|
||||||
formattedResponse += isArabic ? '### العلاقات\n\n' : '### Relationships\n\n';
|
|
||||||
insight.relationships.forEach(rel => {
|
|
||||||
const from = getLocalizedValue(rel, 'from', 'من');
|
|
||||||
const to = getLocalizedValue(rel, 'to', 'إلى');
|
|
||||||
const type = getLocalizedValue(rel, 'type', 'نوع');
|
|
||||||
formattedResponse += `- ${from} → ${to} (${type})\n`;
|
|
||||||
});
|
|
||||||
formattedResponse += '\n';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process recommendations
|
// Process recommendations
|
||||||
if (recommendationsData.length > 0) {
|
if (recommendationsData.length > 0) {
|
||||||
formattedResponse += isArabic ? '## التوصيات\n\n' : '## Recommendations\n\n';
|
formattedResponse += isArabic ? '## التوصيات\n\n' : '## Recommendations\n\n';
|
||||||
recommendationsData.forEach(rec => {
|
recommendationsData.forEach(rec => {
|
||||||
formattedResponse += `- ${rec}\n`;
|
formattedResponse += `- ${rec}\n`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return formattedResponse.trim() ||
|
||||||
|
(isArabic ? 'تم تحليل البيانات بنجاح ولكن لا توجد نتائج للعرض.' : 'Data analyzed successfully but no results to display.');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLocalizedValue(obj, englishKey, arabicKey) {
|
||||||
|
return isArabic ? (obj[arabicKey] || obj[englishKey] || '') : (obj[englishKey] || obj[arabicKey] || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).on('click', '.copy-btn', function() {
|
||||||
|
const text = $(this).closest('.d-flex').find('.chat-message').text().trim();
|
||||||
|
navigator.clipboard.writeText(text).then(() => {
|
||||||
|
showCopySuccess($(this));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
return formattedResponse.trim() ||
|
function showCopySuccess(button) {
|
||||||
(isArabic ? 'تم تحليل البيانات بنجاح ولكن لا توجد نتائج للعرض.' : 'Data analyzed successfully but no results to display.');
|
const originalIcon = button.html();
|
||||||
}
|
button.html('<i class="fas fa-check"></i>');
|
||||||
|
setTimeout(() => {
|
||||||
|
button.html(originalIcon);
|
||||||
|
}, 1500);
|
||||||
|
}
|
||||||
|
|
||||||
function getLocalizedValue(obj, englishKey, arabicKey) {
|
scrollToBottom();
|
||||||
return isArabic ? (obj[arabicKey] || obj[englishKey] || '') : (obj[englishKey] || obj[arabicKey] || '');
|
|
||||||
}
|
|
||||||
|
|
||||||
$(document).on('click', '.copy-btn', function() {
|
|
||||||
const text = $(this).closest('.d-flex').find('.chat-message').text().trim();
|
|
||||||
navigator.clipboard.writeText(text).then(() => {
|
|
||||||
showCopySuccess($(this));
|
|
||||||
});
|
});
|
||||||
});
|
</script>
|
||||||
|
|
||||||
function showCopySuccess(button) {
|
|
||||||
const originalIcon = button.html();
|
|
||||||
button.html('<i class="fas fa-check"></i>');
|
|
||||||
setTimeout(() => {
|
|
||||||
button.html(originalIcon);
|
|
||||||
}, 1500);
|
|
||||||
}
|
|
||||||
|
|
||||||
scrollToBottom();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
@ -342,18 +342,18 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="navbar-vertical-footer">
|
|
||||||
<button class="btn navbar-vertical-toggle border-0 fw-semibold w-100 white-space-nowrap d-flex align-items-center">
|
|
||||||
<span class="uil uil-left-arrow-to-left fs-8"></span><span class="uil uil-arrow-from-right fs-8"></span><span class="navbar-vertical-footer-text ms-2">Collapsed View</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="navbar-vertical-footer">
|
||||||
|
<button class="btn navbar-vertical-toggle border-0 fw-semibold w-100 white-space-nowrap d-flex align-items-center">
|
||||||
|
<span class="uil uil-left-arrow-to-left fs-8"></span><span class="uil uil-arrow-from-right fs-8"></span><span class="navbar-vertical-footer-text ms-2">Collapsed View</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|||||||
@ -20,7 +20,7 @@
|
|||||||
type="radio"
|
type="radio"
|
||||||
name="exterior"
|
name="exterior"
|
||||||
value="{{ color.id }}" {% if color.id == form.instance.exterior.id %}checked{% endif %}>
|
value="{{ color.id }}" {% if color.id == form.instance.exterior.id %}checked{% endif %}>
|
||||||
|
|
||||||
<div class="card-body color-display"
|
<div class="card-body color-display"
|
||||||
style="background-color: rgb({{ color.rgb }})">
|
style="background-color: rgb({{ color.rgb }})">
|
||||||
<div class="">
|
<div class="">
|
||||||
|
|||||||
@ -3,37 +3,37 @@
|
|||||||
{% block title %}{{ _("Car Details") }}{% endblock %}
|
{% block title %}{{ _("Car Details") }}{% endblock %}
|
||||||
{% block customCSS %}
|
{% block customCSS %}
|
||||||
<style>
|
<style>
|
||||||
.disabled{
|
.disabled{
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
.car_status {
|
.car_status {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 13%;
|
top: 13%;
|
||||||
left: 90%;
|
left: 90%;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock customCSS %}
|
{% endblock customCSS %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
{% if not car.ready and not car.status == 'sold' %}
|
{% if not car.ready and not car.status == 'sold' %}
|
||||||
<div class="alert alert-outline-warning d-flex align-items-center"
|
<div class="alert alert-outline-warning d-flex align-items-center"
|
||||||
role="alert">
|
role="alert">
|
||||||
<i class="fa-solid fa-circle-info fs-6"></i>
|
<i class="fa-solid fa-circle-info fs-6"></i>
|
||||||
{%if not car.finances and not car.colors%}
|
{%if not car.finances and not car.colors%}
|
||||||
<p class="mb-0 flex-1">
|
<p class="mb-0 flex-1">
|
||||||
{{ _("This car information is not complete , please add colors and finances both before making it ready for sale .") }}
|
{{ _("This car information is not complete , please add colors and finances both before making it ready for sale .") }}
|
||||||
</p>
|
</p>
|
||||||
{% elif car.finances and not car.colors %}
|
{% elif car.finances and not car.colors %}
|
||||||
<p class="mb-0 flex-1">
|
<p class="mb-0 flex-1">
|
||||||
{{ _("This car information is not complete , please add colors before making it ready for sale .") }}
|
{{ _("This car information is not complete , please add colors before making it ready for sale .") }}
|
||||||
</p>
|
</p>
|
||||||
{%else%}
|
{%else%}
|
||||||
<p class="mb-0 flex-1">
|
<p class="mb-0 flex-1">
|
||||||
{{ _("This car information is not complete , please add finances before making it ready for sale .") }}
|
{{ _("This car information is not complete , please add finances before making it ready for sale .") }}
|
||||||
</p>
|
</p>
|
||||||
{%endif%}
|
{%endif%}
|
||||||
<button class="btn-close"
|
<button class="btn-close"
|
||||||
type="button"
|
type="button"
|
||||||
@ -277,13 +277,13 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
<span class="badge bg-danger">{% trans "Cannot Edit, Car in Transfer." %}</span>
|
<span class="badge bg-danger">{% trans "Cannot Edit, Car in Transfer." %}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>{% trans "No finance details available." %}</p>
|
<p>{% trans "No finance details available." %}</p>
|
||||||
{% if perms.inventory.add_carfinance %}
|
{% if perms.inventory.add_carfinance %}
|
||||||
<a href="{% url 'car_finance_create' car.slug %}"
|
<a href="{% url 'car_finance_create' car.slug %}"
|
||||||
class="btn btn-phoenix-success btn-sm mb-3">{% trans "Add" %}</a>
|
class="btn btn-phoenix-success btn-sm mb-3">{% trans "Add" %}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</table>
|
</table>
|
||||||
@ -318,7 +318,7 @@
|
|||||||
style="background-color: rgb({{ car.colors.interior.rgb }})"></div>
|
style="background-color: rgb({{ car.colors.interior.rgb }})"></div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
{% if not car.get_transfer %}
|
{% if not car.get_transfer %}
|
||||||
@ -332,16 +332,16 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
<tr>
|
<tr>
|
||||||
|
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
<p>{% trans "No color details available." %}</p>
|
<p>{% trans "No color details available." %}</p>
|
||||||
{% if perms.inventory.add_carcolors %}
|
{% if perms.inventory.add_carcolors %}
|
||||||
<a class="btn btn-phoenix-success btn-sm mb-3" href="{% url 'add_color' car.slug %}">{{ _("Add Color") }}</a>
|
<a class="btn btn-phoenix-success btn-sm mb-3" href="{% url 'add_color' car.slug %}">{{ _("Add Color") }}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<!--test-->
|
<!--test-->
|
||||||
|
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -390,19 +390,19 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
{% if perms.inventory.change_carreservation %}
|
{% if perms.inventory.change_carreservation %}
|
||||||
<button type="button"
|
<button type="button"
|
||||||
class="btn btn-sm btn-phoenix-success"
|
class="btn btn-sm btn-phoenix-success"
|
||||||
data-bs-toggle="modal"
|
data-bs-toggle="modal"
|
||||||
data-bs-target="#reserveModal">
|
data-bs-target="#reserveModal">
|
||||||
{% trans 'Reserve' %}
|
{% trans 'Reserve' %}
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</table>
|
</table>
|
||||||
@ -570,152 +570,152 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
const csrftoken = getCookie("csrftoken");
|
const csrftoken = getCookie("csrftoken");
|
||||||
const ajaxUrl = "{% url 'ajax_handler' %}";
|
const ajaxUrl = "{% url 'ajax_handler' %}";
|
||||||
|
|
||||||
const customCardModal = document.getElementById("customCardModal");
|
const customCardModal = document.getElementById("customCardModal");
|
||||||
const modalBody = customCardModal.querySelector(".modal-body");
|
const modalBody = customCardModal.querySelector(".modal-body");
|
||||||
|
|
||||||
// When the modal is triggered, load the form
|
// When the modal is triggered, load the form
|
||||||
customCardModal.addEventListener("show.bs.modal", function () {
|
customCardModal.addEventListener("show.bs.modal", function () {
|
||||||
const url = "{% url 'add_custom_card' car.slug %}";
|
const url = "{% url 'add_custom_card' car.slug %}";
|
||||||
|
|
||||||
fetch(url)
|
fetch(url)
|
||||||
.then((response) => response.text())
|
.then((response) => response.text())
|
||||||
.then((html) => {
|
.then((html) => {
|
||||||
modalBody.innerHTML = html;
|
modalBody.innerHTML = html;
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
modalBody.innerHTML = '<p class="text-danger">Error loading form. Please try again later.</p>';
|
modalBody.innerHTML = '<p class="text-danger">Error loading form. Please try again later.</p>';
|
||||||
console.error("Error loading form:", error);
|
console.error("Error loading form:", error);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
customCardModal.addEventListener("hidden.bs.modal", function () {
|
customCardModal.addEventListener("hidden.bs.modal", function () {
|
||||||
modalBody.innerHTML = "";
|
modalBody.innerHTML = "";
|
||||||
});
|
});
|
||||||
|
|
||||||
const registrationModal = document.getElementById("registrationModal");
|
const registrationModal = document.getElementById("registrationModal");
|
||||||
const modalBody_r = registrationModal.querySelector(".modal-body");
|
const modalBody_r = registrationModal.querySelector(".modal-body");
|
||||||
|
|
||||||
// When the modal is triggered, load the form
|
// When the modal is triggered, load the form
|
||||||
registrationModal.addEventListener("show.bs.modal", function () {
|
registrationModal.addEventListener("show.bs.modal", function () {
|
||||||
const url = "{% url 'add_registration' car.slug %}";
|
const url = "{% url 'add_registration' car.slug %}";
|
||||||
|
|
||||||
fetch(url)
|
fetch(url)
|
||||||
.then((response) => response.text())
|
.then((response) => response.text())
|
||||||
.then((html) => {
|
.then((html) => {
|
||||||
modalBody_r.innerHTML = html;
|
modalBody_r.innerHTML = html;
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
modalBody_r.innerHTML = '<p class="text-danger">{{_("Error loading form. Please try again later")}}.</p>';
|
modalBody_r.innerHTML = '<p class="text-danger">{{_("Error loading form. Please try again later")}}.</p>';
|
||||||
console.error("Error loading form:", error);
|
console.error("Error loading form:", error);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
registrationModal.addEventListener("hidden.bs.modal", function () {
|
registrationModal.addEventListener("hidden.bs.modal", function () {
|
||||||
modalBody_r.innerHTML = "";
|
modalBody_r.innerHTML = "";
|
||||||
});
|
});
|
||||||
|
|
||||||
const showSpecificationButton = document.getElementById("specification-btn");
|
const showSpecificationButton = document.getElementById("specification-btn");
|
||||||
const specificationsContent = document.getElementById("specificationsContent");
|
const specificationsContent = document.getElementById("specificationsContent");
|
||||||
|
|
||||||
showSpecificationButton.addEventListener("click", function () {
|
showSpecificationButton.addEventListener("click", function () {
|
||||||
specificationsContent.innerHTML = "";
|
specificationsContent.innerHTML = "";
|
||||||
fetch(`${ajaxUrl}?action=get_specifications&trim_id={{ car.id_car_trim.id_car_trim }}`, {
|
fetch(`${ajaxUrl}?action=get_specifications&trim_id={{ car.id_car_trim.id_car_trim }}`, {
|
||||||
headers: {
|
headers: {
|
||||||
"X-Requested-With": "XMLHttpRequest",
|
"X-Requested-With": "XMLHttpRequest",
|
||||||
"X-CSRFToken": csrftoken,
|
"X-CSRFToken": csrftoken,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (data.length > 0) {
|
if (data.length > 0) {
|
||||||
data.forEach(function (parent) {
|
data.forEach(function (parent) {
|
||||||
// Create a section container
|
// Create a section container
|
||||||
const section = document.createElement("div");
|
const section = document.createElement("div");
|
||||||
section.classList.add("mb-4", "p-3", "border", "rounded");
|
section.classList.add("mb-4", "p-3", "border", "rounded");
|
||||||
|
|
||||||
// Add section title
|
// Add section title
|
||||||
const sectionTitle = document.createElement("h4");
|
const sectionTitle = document.createElement("h4");
|
||||||
sectionTitle.classList.add("mb-3", "fw-bold");
|
sectionTitle.classList.add("mb-3", "fw-bold");
|
||||||
sectionTitle.textContent = parent.parent_name;
|
sectionTitle.textContent = parent.parent_name;
|
||||||
section.appendChild(sectionTitle);
|
section.appendChild(sectionTitle);
|
||||||
|
|
||||||
// Create a table for the specifications
|
// Create a table for the specifications
|
||||||
const specsTable = document.createElement("div");
|
const specsTable = document.createElement("div");
|
||||||
specsTable.classList.add("row");
|
specsTable.classList.add("row");
|
||||||
|
|
||||||
parent.specifications.forEach(function (specification) {
|
parent.specifications.forEach(function (specification) {
|
||||||
// Create a row for each specification
|
// Create a row for each specification
|
||||||
const specRow = document.createElement("div");
|
const specRow = document.createElement("div");
|
||||||
specRow.classList.add("row", "mb-2");
|
specRow.classList.add("row", "mb-2");
|
||||||
|
|
||||||
// Left Column: Spec name
|
// Left Column: Spec name
|
||||||
const specName = document.createElement("div");
|
const specName = document.createElement("div");
|
||||||
specName.classList.add("col-6", "text-muted");
|
specName.classList.add("col-6", "text-muted");
|
||||||
specName.textContent = specification.s_name;
|
specName.textContent = specification.s_name;
|
||||||
|
|
||||||
// Right Column: Spec value + unit
|
// Right Column: Spec value + unit
|
||||||
const specValue = document.createElement("div");
|
const specValue = document.createElement("div");
|
||||||
specValue.classList.add("col-6", "text-end");
|
specValue.classList.add("col-6", "text-end");
|
||||||
specValue.textContent = `${specification.s_value} ${specification.s_unit || ""}`;
|
specValue.textContent = `${specification.s_value} ${specification.s_unit || ""}`;
|
||||||
|
|
||||||
specRow.appendChild(specName);
|
specRow.appendChild(specName);
|
||||||
specRow.appendChild(specValue);
|
specRow.appendChild(specValue);
|
||||||
|
|
||||||
specsTable.appendChild(specRow);
|
specsTable.appendChild(specRow);
|
||||||
});
|
});
|
||||||
|
|
||||||
section.appendChild(specsTable);
|
section.appendChild(specsTable);
|
||||||
specificationsContent.appendChild(section);
|
specificationsContent.appendChild(section);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
specificationsContent.innerHTML = '<p>{% trans "No specifications available." %}</p>';
|
specificationsContent.innerHTML = '<p>{% trans "No specifications available." %}</p>';
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
specificationsContent.innerHTML = '<p>{% trans "Error loading specifications." %}</p>';
|
specificationsContent.innerHTML = '<p>{% trans "Error loading specifications." %}</p>';
|
||||||
console.error("Error fetching specifications:", error);
|
console.error("Error fetching specifications:", error);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
document.querySelectorAll(".reserve-btn").forEach((button) => {
|
|
||||||
button.addEventListener("click", async function () {
|
|
||||||
try {
|
|
||||||
const response = await fetch(`{% url 'reserve_car' car.slug %}`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"X-CSRFToken": csrfToken,
|
|
||||||
"X-Requested-With": "XMLHttpRequest",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
const data = await response.json();
|
|
||||||
if (data.success) {
|
|
||||||
this.textContent = "Reserved";
|
|
||||||
this.classList.remove("btn-phoenix-success");
|
|
||||||
this.classList.add("btn-phoenix-danger");
|
|
||||||
this.disabled = true;
|
|
||||||
alert("Car reserved successfully.");
|
|
||||||
} else {
|
|
||||||
alert(data.message || "Failed to reserve the car.");
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error:", error);
|
|
||||||
alert("An error occurred. Please try again.");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if('{{car.status}}' == "sold"){
|
document.querySelectorAll(".reserve-btn").forEach((button) => {
|
||||||
document.querySelector(".row-fluid").querySelectorAll("button").forEach((button) => {
|
button.addEventListener("click", async function () {
|
||||||
button.classList.add("d-none");
|
try {
|
||||||
});
|
const response = await fetch(`{% url 'reserve_car' car.slug %}`, {
|
||||||
document.querySelector(".row-fluid").querySelectorAll("a").forEach((button) => {
|
method: "POST",
|
||||||
button.classList.add("d-none");
|
headers: {
|
||||||
});
|
"X-CSRFToken": csrfToken,
|
||||||
}
|
"X-Requested-With": "XMLHttpRequest",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
if (data.success) {
|
||||||
|
this.textContent = "Reserved";
|
||||||
|
this.classList.remove("btn-phoenix-success");
|
||||||
|
this.classList.add("btn-phoenix-danger");
|
||||||
|
this.disabled = true;
|
||||||
|
alert("Car reserved successfully.");
|
||||||
|
} else {
|
||||||
|
alert(data.message || "Failed to reserve the car.");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error:", error);
|
||||||
|
alert("An error occurred. Please try again.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if('{{car.status}}' == "sold"){
|
||||||
|
document.querySelector(".row-fluid").querySelectorAll("button").forEach((button) => {
|
||||||
|
button.classList.add("d-none");
|
||||||
|
});
|
||||||
|
document.querySelector(".row-fluid").querySelectorAll("a").forEach((button) => {
|
||||||
|
button.classList.add("d-none");
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -3,16 +3,16 @@
|
|||||||
{%block title%} {%trans 'Add New Car'%} {%endblock%}
|
{%block title%} {%trans 'Add New Car'%} {%endblock%}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<style>
|
<style>
|
||||||
#video {
|
#video {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 480px;
|
max-width: 480px;
|
||||||
height: auto;
|
height: auto;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
.disabled{
|
.disabled{
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<!-- JavaScript Section -->
|
<!-- JavaScript Section -->
|
||||||
<script src="{% static 'vendors/zxing/index.min.js' %}"></script>
|
<script src="{% static 'vendors/zxing/index.min.js' %}"></script>
|
||||||
@ -314,335 +314,335 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
function getCookie(name) {
|
function getCookie(name) {
|
||||||
let cookieValue = null;
|
let cookieValue = null;
|
||||||
if (document.cookie && document.cookie !== "") {
|
if (document.cookie && document.cookie !== "") {
|
||||||
const cookies = document.cookie.split(";");
|
const cookies = document.cookie.split(";");
|
||||||
for (let cookie of cookies) {
|
for (let cookie of cookies) {
|
||||||
cookie = cookie.trim();
|
cookie = cookie.trim();
|
||||||
if (cookie.substring(0, name.length + 1) === name + "=") {
|
if (cookie.substring(0, name.length + 1) === name + "=") {
|
||||||
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cookieValue;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return cookieValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
const csrfToken = getCookie("csrftoken");
|
const csrfToken = getCookie("csrftoken");
|
||||||
|
|
||||||
const vinInput = document.getElementById("{{ form.vin.id_for_label }}");
|
const vinInput = document.getElementById("{{ form.vin.id_for_label }}");
|
||||||
const decodeVinBtn = document.getElementById("decodeVinBtn");
|
const decodeVinBtn = document.getElementById("decodeVinBtn");
|
||||||
const makeSelect = document.getElementById("{{ form.id_car_make.id_for_label }}");
|
const makeSelect = document.getElementById("{{ form.id_car_make.id_for_label }}");
|
||||||
const modelSelect = document.getElementById("{{ form.id_car_model.id_for_label }}");
|
const modelSelect = document.getElementById("{{ form.id_car_model.id_for_label }}");
|
||||||
const yearSelect = document.getElementById("{{ form.year.id_for_label }}");
|
const yearSelect = document.getElementById("{{ form.year.id_for_label }}");
|
||||||
const serieSelect = document.getElementById("{{ form.id_car_serie.id_for_label }}");
|
const serieSelect = document.getElementById("{{ form.id_car_serie.id_for_label }}");
|
||||||
const trimSelect = document.getElementById("{{ form.id_car_trim.id_for_label }}");
|
const trimSelect = document.getElementById("{{ form.id_car_trim.id_for_label }}");
|
||||||
const equipmentSelect = document.getElementById("equipment_id")
|
const equipmentSelect = document.getElementById("equipment_id")
|
||||||
const showSpecificationButton = document.getElementById("specification-btn");
|
const showSpecificationButton = document.getElementById("specification-btn");
|
||||||
const showEquipmentButton = document.getElementById("options-btn")
|
const showEquipmentButton = document.getElementById("options-btn")
|
||||||
const specificationsContent = document.getElementById("specificationsContent");
|
const specificationsContent = document.getElementById("specificationsContent");
|
||||||
const optionsContent = document.getElementById("optionsContent")
|
const optionsContent = document.getElementById("optionsContent")
|
||||||
const generationContainer = document.getElementById("generation-div")
|
const generationContainer = document.getElementById("generation-div")
|
||||||
|
|
||||||
const ajaxUrl = "{% url 'ajax_handler' %}";
|
const ajaxUrl = "{% url 'ajax_handler' %}";
|
||||||
|
|
||||||
const closeButton = document.querySelector(".btn-close");
|
const closeButton = document.querySelector(".btn-close");
|
||||||
const scanVinBtn = document.getElementById("scan-vin-btn");
|
const scanVinBtn = document.getElementById("scan-vin-btn");
|
||||||
const videoElement = document.getElementById("video");
|
const videoElement = document.getElementById("video");
|
||||||
const resultDisplay = document.getElementById("result");
|
const resultDisplay = document.getElementById("result");
|
||||||
const fallbackButton = document.getElementById("ocr-fallback-btn");
|
const fallbackButton = document.getElementById("ocr-fallback-btn");
|
||||||
let codeReader;
|
let codeReader;
|
||||||
codeReader = new ZXing.BrowserMultiFormatReader();
|
codeReader = new ZXing.BrowserMultiFormatReader();
|
||||||
let currentStream = null;
|
let currentStream = null;
|
||||||
|
|
||||||
function closeModal() {
|
function closeModal() {
|
||||||
stopScanner();
|
stopScanner();
|
||||||
try {
|
try {
|
||||||
const scannerModal = document.getElementById("scannerModal");
|
const scannerModal = document.getElementById("scannerModal");
|
||||||
if (scannerModal) {
|
if (scannerModal) {
|
||||||
document.activeElement.blur();
|
document.activeElement.blur();
|
||||||
scannerModal.setAttribute("inert", "true");
|
scannerModal.setAttribute("inert", "true");
|
||||||
const modalInstance = bootstrap.Modal.getInstance(scannerModal);
|
const modalInstance = bootstrap.Modal.getInstance(scannerModal);
|
||||||
if (modalInstance) modalInstance.hide();
|
if (modalInstance) modalInstance.hide();
|
||||||
if (scanVinBtn) scanVinBtn.focus();
|
if (scanVinBtn) scanVinBtn.focus();
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error closing scanner modal:", err);
|
console.error("Error closing scanner modal:", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function decodeVin() {
|
async function decodeVin() {
|
||||||
const vinNumber = vinInput.value.trim();
|
const vinNumber = vinInput.value.trim();
|
||||||
if (vinNumber.length !== 17) {
|
if (vinNumber.length !== 17) {
|
||||||
Swal.fire("error", "{% trans 'Please enter a valid VIN.' %}");
|
Swal.fire("error", "{% trans 'Please enter a valid VIN.' %}");
|
||||||
/*alert("{% trans 'Please enter a valid VIN.' %}");*/
|
/*alert("{% trans 'Please enter a valid VIN.' %}");*/
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
showLoading();
|
showLoading();
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${ajaxUrl}?action=decode_vin&vin_no=${vinNumber}`, {
|
const response = await fetch(`${ajaxUrl}?action=decode_vin&vin_no=${vinNumber}`, {
|
||||||
headers: {
|
headers: {
|
||||||
"X-Requested-With": "XMLHttpRequest",
|
"X-Requested-With": "XMLHttpRequest",
|
||||||
"X-CSRFToken": csrfToken,
|
"X-CSRFToken": csrfToken,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
hideLoading();
|
hideLoading();
|
||||||
await updateFields(data.data);
|
await updateFields(data.data);
|
||||||
} else {
|
} else {
|
||||||
hideLoading();
|
hideLoading();
|
||||||
Swal.fire("{% trans 'error' %}", data.error);
|
Swal.fire("{% trans 'error' %}", data.error);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error decoding VIN:", error);
|
console.error("Error decoding VIN:", error);
|
||||||
hideLoading();
|
hideLoading();
|
||||||
Swal.fire("error", "{% trans 'An error occurred while decoding the VIN.' %}");
|
Swal.fire("error", "{% trans 'An error occurred while decoding the VIN.' %}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateFields(vinData) {
|
async function updateFields(vinData) {
|
||||||
console.log(vinData);
|
console.log(vinData);
|
||||||
if (vinData.make_id) {
|
if (vinData.make_id) {
|
||||||
makeSelect.value = vinData.make_id;
|
makeSelect.value = vinData.make_id;
|
||||||
document.getElementById("make-check").innerHTML = "✓";
|
document.getElementById("make-check").innerHTML = "✓";
|
||||||
await loadModels(vinData.make_id);
|
await loadModels(vinData.make_id);
|
||||||
}
|
}
|
||||||
if (vinData.model_id) {
|
if (vinData.model_id) {
|
||||||
modelSelect.value = vinData.model_id;
|
modelSelect.value = vinData.model_id;
|
||||||
document.getElementById("model-check").innerHTML = "✓";
|
document.getElementById("model-check").innerHTML = "✓";
|
||||||
|
|
||||||
await loadSeries(vinData.model_id, vinData.year);
|
await loadSeries(vinData.model_id, vinData.year);
|
||||||
}
|
}
|
||||||
if (vinData.year) {
|
if (vinData.year) {
|
||||||
yearSelect.value = vinData.year;
|
yearSelect.value = vinData.year;
|
||||||
document.getElementById("year-check").innerHTML = "✓";
|
document.getElementById("year-check").innerHTML = "✓";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the scanner
|
// Start the scanner
|
||||||
async function startScanner() {
|
async function startScanner() {
|
||||||
codeReader
|
codeReader
|
||||||
.decodeFromVideoDevice(null, videoElement, async (result, err) => {
|
.decodeFromVideoDevice(null, videoElement, async (result, err) => {
|
||||||
let res = await result;
|
let res = await result;
|
||||||
if (result) {
|
if (result) {
|
||||||
vinInput.value = result.text;
|
vinInput.value = result.text;
|
||||||
closeModal();
|
closeModal();
|
||||||
await decodeVin();
|
await decodeVin();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.catch(console.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
function captureAndOCR() {
|
function captureAndOCR() {
|
||||||
const canvas = document.createElement("canvas");
|
const canvas = document.createElement("canvas");
|
||||||
const context = canvas.getContext("2d");
|
const context = canvas.getContext("2d");
|
||||||
canvas.width = videoElement.videoWidth;
|
canvas.width = videoElement.videoWidth;
|
||||||
canvas.height = videoElement.videoHeight;
|
canvas.height = videoElement.videoHeight;
|
||||||
context.drawImage(videoElement, 0, 0, canvas.width, canvas.height);
|
context.drawImage(videoElement, 0, 0, canvas.width, canvas.height);
|
||||||
Tesseract.recognize(canvas.toDataURL("image/png"), "eng")
|
Tesseract.recognize(canvas.toDataURL("image/png"), "eng")
|
||||||
.then(({ data: { text } }) => {
|
.then(({ data: { text } }) => {
|
||||||
const vin = text.match(/[A-HJ-NPR-Z0-9]{17}/);
|
const vin = text.match(/[A-HJ-NPR-Z0-9]{17}/);
|
||||||
if (vin) vinInput.value = vin[0];
|
if (vin) vinInput.value = vin[0];
|
||||||
closeModal();
|
closeModal();
|
||||||
decodeVin();
|
decodeVin();
|
||||||
})
|
})
|
||||||
.catch((err) => console.error("OCR Error:", err));
|
.catch((err) => console.error("OCR Error:", err));
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopScanner() {
|
function stopScanner() {
|
||||||
if (currentStream) {
|
if (currentStream) {
|
||||||
currentStream.getTracks().forEach((track) => track.stop());
|
currentStream.getTracks().forEach((track) => track.stop());
|
||||||
currentStream = null;
|
currentStream = null;
|
||||||
|
}
|
||||||
|
codeReader.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetDropdown(dropdown, placeholder) {
|
||||||
|
dropdown.innerHTML = `<option value="">${placeholder}</option>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadModels(makeId) {
|
||||||
|
resetDropdown(modelSelect, '{% trans "Select" %}');
|
||||||
|
const response = await fetch(`${ajaxUrl}?action=get_models&make_id=${makeId}`, {
|
||||||
|
headers: {
|
||||||
|
"X-Requested-With": "XMLHttpRequest",
|
||||||
|
"X-CSRFToken": csrfToken,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
data.forEach((model) => {
|
||||||
|
const option = document.createElement("option");
|
||||||
|
option.value = model.id_car_model;
|
||||||
|
option.textContent = document.documentElement.lang === "en" ? model.name : model.arabic_name;
|
||||||
|
modelSelect.appendChild(option);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadSeries(modelId, year) {
|
||||||
|
resetDropdown(serieSelect, '{% trans "Select" %}');
|
||||||
|
resetDropdown(trimSelect, '{% trans "Select" %}');
|
||||||
|
specificationsContent.innerHTML = "";
|
||||||
|
|
||||||
|
const response = await fetch(`${ajaxUrl}?action=get_series&model_id=${modelId}&year=${year}`, {
|
||||||
|
headers: {
|
||||||
|
"X-Requested-With": "XMLHttpRequest",
|
||||||
|
"X-CSRFToken": csrfToken,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
console.log(data)
|
||||||
|
data.forEach((serie) => {
|
||||||
|
|
||||||
|
const option = document.createElement("option");
|
||||||
|
option.value = serie.id_car_serie;
|
||||||
|
option.textContent = document.documentElement.lang === "en" ? serie.name : serie.name;
|
||||||
|
generationContainer.innerHTML = serie.generation_name
|
||||||
|
serieSelect.appendChild(option);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadTrims(serie_id, model_id) {
|
||||||
|
resetDropdown(trimSelect, '{% trans "Select" %}');
|
||||||
|
specificationsContent.innerHTML = "";
|
||||||
|
const response = await fetch(`${ajaxUrl}?action=get_trims&serie_id=${serie_id}&model_id=${model_id}`, {
|
||||||
|
headers: {
|
||||||
|
"X-Requested-With": "XMLHttpRequest",
|
||||||
|
"X-CSRFToken": csrfToken,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
data.forEach((trim) => {
|
||||||
|
const option = document.createElement("option");
|
||||||
|
option.value = trim.id_car_trim;
|
||||||
|
option.textContent = document.documentElement.lang === "en" ? trim.name : trim.name;
|
||||||
|
trimSelect.appendChild(option);
|
||||||
|
});
|
||||||
|
showSpecificationButton.disabled = !this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadEquipment(trimId){
|
||||||
|
resetDropdown(equipmentSelect, '{% trans "Select" %}');
|
||||||
|
optionsContent.innerHTML = "";
|
||||||
|
const response = await fetch(`${ajaxUrl}?action=get_equipments&trim_id=${trimId}`, {
|
||||||
|
headers: {
|
||||||
|
'X-Requested-With': 'XMLHttpRequest',
|
||||||
|
'X-CSRFToken': csrfToken
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
data.forEach((equipment) => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = equipment.id_car_equipment;
|
||||||
|
option.textContent = equipment.name;
|
||||||
|
equipmentSelect.appendChild(option);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadSpecifications(trimId) {
|
||||||
|
specificationsContent.innerHTML = "";
|
||||||
|
const response = await fetch(`${ajaxUrl}?action=get_specifications&trim_id=${trimId}`, {
|
||||||
|
headers: {
|
||||||
|
"X-Requested-With": "XMLHttpRequest",
|
||||||
|
"X-CSRFToken": csrfToken,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
data.forEach((spec) => {
|
||||||
|
const parentDiv = document.createElement("div");
|
||||||
|
parentDiv.innerHTML = `<strong>${spec.parent_name}</strong>`;
|
||||||
|
spec.specifications.forEach((s) => {
|
||||||
|
const specDiv = document.createElement("div");
|
||||||
|
specDiv.innerHTML = `• ${s.s_name}: ${s.s_value} ${s.s_unit}`;
|
||||||
|
parentDiv.appendChild(specDiv);
|
||||||
|
});
|
||||||
|
specificationsContent.appendChild(parentDiv);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadOptions(equipmentId) {
|
||||||
|
optionsContent.innerHTML = "";
|
||||||
|
const response = await fetch(`${ajaxUrl}?action=get_options&equipment_id=${equipmentId}`, {
|
||||||
|
headers: {
|
||||||
|
"X-Requested-With": "XMLHttpRequest",
|
||||||
|
"X-CSRFToken": csrfToken,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
data.forEach((parent) => {
|
||||||
|
const parentDiv = document.createElement("div");
|
||||||
|
parentDiv.innerHTML = `<strong>${parent.parent_name}</strong>`;
|
||||||
|
parent.options.forEach((option) => {
|
||||||
|
const optDiv = document.createElement("div");
|
||||||
|
optDiv.innerHTML = `• ${option.option_name}`;
|
||||||
|
parentDiv.appendChild(optDiv);
|
||||||
|
});
|
||||||
|
optionsContent.appendChild(parentDiv);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
scanVinBtn.addEventListener("click", () => {
|
||||||
|
resultDisplay.textContent = "";
|
||||||
|
startScanner();
|
||||||
|
});
|
||||||
|
|
||||||
|
fallbackButton.addEventListener("click", () => {
|
||||||
|
captureAndOCR();
|
||||||
|
})
|
||||||
|
|
||||||
|
serieSelect.addEventListener("change", () => {
|
||||||
|
const serie_id = serieSelect.value;
|
||||||
|
const model_id = modelSelect.value;
|
||||||
|
if (serie_id && model_id) loadTrims(serie_id, model_id);
|
||||||
|
})
|
||||||
|
|
||||||
|
trimSelect.addEventListener("change", () => {
|
||||||
|
const trimId = trimSelect.value
|
||||||
|
showSpecificationButton.disabled = !trimId
|
||||||
|
showEquipmentButton.disabled = !trimId
|
||||||
|
if (trimId) loadSpecifications(trimId)
|
||||||
|
loadEquipment(trimId)
|
||||||
|
})
|
||||||
|
|
||||||
|
equipmentSelect.addEventListener("change", () => {
|
||||||
|
const equipmentId = equipmentSelect.value
|
||||||
|
if (equipmentId) loadOptions(equipmentId)
|
||||||
|
})
|
||||||
|
|
||||||
|
closeButton.addEventListener("click", closeModal);
|
||||||
|
makeSelect.addEventListener("change", (e) => {
|
||||||
|
loadModels(e.target.value, modelSelect.value);
|
||||||
|
})
|
||||||
|
modelSelect.addEventListener("change", (e) => {
|
||||||
|
loadSeries(e.target.value, yearSelect.value);
|
||||||
|
})
|
||||||
|
decodeVinBtn.addEventListener("click", decodeVin);
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
function showLoading() {
|
||||||
|
Swal.fire({
|
||||||
|
title: "{% trans 'Please Wait' %}",
|
||||||
|
text: "{% trans 'Loading' %}...",
|
||||||
|
allowOutsideClick: false,
|
||||||
|
didOpen: () => {
|
||||||
|
Swal.showLoading();
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
codeReader.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetDropdown(dropdown, placeholder) {
|
function hideLoading() {
|
||||||
dropdown.innerHTML = `<option value="">${placeholder}</option>`;
|
Swal.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadModels(makeId) {
|
function notify(tag, msg) {
|
||||||
resetDropdown(modelSelect, '{% trans "Select" %}');
|
Swal.fire({
|
||||||
const response = await fetch(`${ajaxUrl}?action=get_models&make_id=${makeId}`, {
|
icon: tag,
|
||||||
headers: {
|
titleText: msg,
|
||||||
"X-Requested-With": "XMLHttpRequest",
|
});
|
||||||
"X-CSRFToken": csrfToken,
|
}
|
||||||
},
|
|
||||||
});
|
|
||||||
const data = await response.json();
|
|
||||||
data.forEach((model) => {
|
|
||||||
const option = document.createElement("option");
|
|
||||||
option.value = model.id_car_model;
|
|
||||||
option.textContent = document.documentElement.lang === "en" ? model.name : model.arabic_name;
|
|
||||||
modelSelect.appendChild(option);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadSeries(modelId, year) {
|
|
||||||
resetDropdown(serieSelect, '{% trans "Select" %}');
|
|
||||||
resetDropdown(trimSelect, '{% trans "Select" %}');
|
|
||||||
specificationsContent.innerHTML = "";
|
|
||||||
|
|
||||||
const response = await fetch(`${ajaxUrl}?action=get_series&model_id=${modelId}&year=${year}`, {
|
|
||||||
headers: {
|
|
||||||
"X-Requested-With": "XMLHttpRequest",
|
|
||||||
"X-CSRFToken": csrfToken,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const data = await response.json();
|
|
||||||
console.log(data)
|
|
||||||
data.forEach((serie) => {
|
|
||||||
|
|
||||||
const option = document.createElement("option");
|
|
||||||
option.value = serie.id_car_serie;
|
|
||||||
option.textContent = document.documentElement.lang === "en" ? serie.name : serie.name;
|
|
||||||
generationContainer.innerHTML = serie.generation_name
|
|
||||||
serieSelect.appendChild(option);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadTrims(serie_id, model_id) {
|
|
||||||
resetDropdown(trimSelect, '{% trans "Select" %}');
|
|
||||||
specificationsContent.innerHTML = "";
|
|
||||||
const response = await fetch(`${ajaxUrl}?action=get_trims&serie_id=${serie_id}&model_id=${model_id}`, {
|
|
||||||
headers: {
|
|
||||||
"X-Requested-With": "XMLHttpRequest",
|
|
||||||
"X-CSRFToken": csrfToken,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const data = await response.json();
|
|
||||||
data.forEach((trim) => {
|
|
||||||
const option = document.createElement("option");
|
|
||||||
option.value = trim.id_car_trim;
|
|
||||||
option.textContent = document.documentElement.lang === "en" ? trim.name : trim.name;
|
|
||||||
trimSelect.appendChild(option);
|
|
||||||
});
|
|
||||||
showSpecificationButton.disabled = !this.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadEquipment(trimId){
|
|
||||||
resetDropdown(equipmentSelect, '{% trans "Select" %}');
|
|
||||||
optionsContent.innerHTML = "";
|
|
||||||
const response = await fetch(`${ajaxUrl}?action=get_equipments&trim_id=${trimId}`, {
|
|
||||||
headers: {
|
|
||||||
'X-Requested-With': 'XMLHttpRequest',
|
|
||||||
'X-CSRFToken': csrfToken
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const data = await response.json();
|
|
||||||
data.forEach((equipment) => {
|
|
||||||
const option = document.createElement('option');
|
|
||||||
option.value = equipment.id_car_equipment;
|
|
||||||
option.textContent = equipment.name;
|
|
||||||
equipmentSelect.appendChild(option);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadSpecifications(trimId) {
|
|
||||||
specificationsContent.innerHTML = "";
|
|
||||||
const response = await fetch(`${ajaxUrl}?action=get_specifications&trim_id=${trimId}`, {
|
|
||||||
headers: {
|
|
||||||
"X-Requested-With": "XMLHttpRequest",
|
|
||||||
"X-CSRFToken": csrfToken,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const data = await response.json();
|
|
||||||
data.forEach((spec) => {
|
|
||||||
const parentDiv = document.createElement("div");
|
|
||||||
parentDiv.innerHTML = `<strong>${spec.parent_name}</strong>`;
|
|
||||||
spec.specifications.forEach((s) => {
|
|
||||||
const specDiv = document.createElement("div");
|
|
||||||
specDiv.innerHTML = `• ${s.s_name}: ${s.s_value} ${s.s_unit}`;
|
|
||||||
parentDiv.appendChild(specDiv);
|
|
||||||
});
|
|
||||||
specificationsContent.appendChild(parentDiv);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadOptions(equipmentId) {
|
|
||||||
optionsContent.innerHTML = "";
|
|
||||||
const response = await fetch(`${ajaxUrl}?action=get_options&equipment_id=${equipmentId}`, {
|
|
||||||
headers: {
|
|
||||||
"X-Requested-With": "XMLHttpRequest",
|
|
||||||
"X-CSRFToken": csrfToken,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const data = await response.json();
|
|
||||||
data.forEach((parent) => {
|
|
||||||
const parentDiv = document.createElement("div");
|
|
||||||
parentDiv.innerHTML = `<strong>${parent.parent_name}</strong>`;
|
|
||||||
parent.options.forEach((option) => {
|
|
||||||
const optDiv = document.createElement("div");
|
|
||||||
optDiv.innerHTML = `• ${option.option_name}`;
|
|
||||||
parentDiv.appendChild(optDiv);
|
|
||||||
});
|
|
||||||
optionsContent.appendChild(parentDiv);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
scanVinBtn.addEventListener("click", () => {
|
|
||||||
resultDisplay.textContent = "";
|
|
||||||
startScanner();
|
|
||||||
});
|
|
||||||
|
|
||||||
fallbackButton.addEventListener("click", () => {
|
|
||||||
captureAndOCR();
|
|
||||||
})
|
|
||||||
|
|
||||||
serieSelect.addEventListener("change", () => {
|
|
||||||
const serie_id = serieSelect.value;
|
|
||||||
const model_id = modelSelect.value;
|
|
||||||
if (serie_id && model_id) loadTrims(serie_id, model_id);
|
|
||||||
})
|
|
||||||
|
|
||||||
trimSelect.addEventListener("change", () => {
|
|
||||||
const trimId = trimSelect.value
|
|
||||||
showSpecificationButton.disabled = !trimId
|
|
||||||
showEquipmentButton.disabled = !trimId
|
|
||||||
if (trimId) loadSpecifications(trimId)
|
|
||||||
loadEquipment(trimId)
|
|
||||||
})
|
|
||||||
|
|
||||||
equipmentSelect.addEventListener("change", () => {
|
|
||||||
const equipmentId = equipmentSelect.value
|
|
||||||
if (equipmentId) loadOptions(equipmentId)
|
|
||||||
})
|
|
||||||
|
|
||||||
closeButton.addEventListener("click", closeModal);
|
|
||||||
makeSelect.addEventListener("change", (e) => {
|
|
||||||
loadModels(e.target.value, modelSelect.value);
|
|
||||||
})
|
|
||||||
modelSelect.addEventListener("change", (e) => {
|
|
||||||
loadSeries(e.target.value, yearSelect.value);
|
|
||||||
})
|
|
||||||
decodeVinBtn.addEventListener("click", decodeVin);
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
function showLoading() {
|
|
||||||
Swal.fire({
|
|
||||||
title: "{% trans 'Please Wait' %}",
|
|
||||||
text: "{% trans 'Loading' %}...",
|
|
||||||
allowOutsideClick: false,
|
|
||||||
didOpen: () => {
|
|
||||||
Swal.showLoading();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideLoading() {
|
|
||||||
Swal.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
function notify(tag, msg) {
|
|
||||||
Swal.fire({
|
|
||||||
icon: tag,
|
|
||||||
titleText: msg,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -2,15 +2,15 @@
|
|||||||
{% load i18n static custom_filters %}
|
{% load i18n static custom_filters %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<style>
|
<style>
|
||||||
#video {
|
#video {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 480px;
|
max-width: 480px;
|
||||||
height: auto;
|
height: auto;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
.modal-dialog {
|
.modal-dialog {
|
||||||
max-width: 95%;
|
max-width: 95%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{% include 'partials/form_errors.html' %}
|
{% include 'partials/form_errors.html' %}
|
||||||
<!-- JavaScript Section -->
|
<!-- JavaScript Section -->
|
||||||
@ -298,331 +298,331 @@
|
|||||||
<!-- CAR FORM -->
|
<!-- CAR FORM -->
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
function getCookie(name) {
|
function getCookie(name) {
|
||||||
let cookieValue = null;
|
let cookieValue = null;
|
||||||
if (document.cookie && document.cookie !== "") {
|
if (document.cookie && document.cookie !== "") {
|
||||||
const cookies = document.cookie.split(";");
|
const cookies = document.cookie.split(";");
|
||||||
for (let cookie of cookies) {
|
for (let cookie of cookies) {
|
||||||
cookie = cookie.trim();
|
cookie = cookie.trim();
|
||||||
if (cookie.substring(0, name.length + 1) === name + "=") {
|
if (cookie.substring(0, name.length + 1) === name + "=") {
|
||||||
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cookieValue;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return cookieValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
const csrfToken = getCookie("token");
|
const csrfToken = getCookie("token");
|
||||||
|
|
||||||
const vinInput = document.getElementById("{{ form.vin.id_for_label }}");
|
const vinInput = document.getElementById("{{ form.vin.id_for_label }}");
|
||||||
const decodeVinBtn = document.getElementById("decodeVinBtn");
|
const decodeVinBtn = document.getElementById("decodeVinBtn");
|
||||||
const makeSelect = document.getElementById("{{ form.id_car_make.id_for_label }}");
|
const makeSelect = document.getElementById("{{ form.id_car_make.id_for_label }}");
|
||||||
const modelSelect = document.getElementById("{{ form.id_car_model.id_for_label }}");
|
const modelSelect = document.getElementById("{{ form.id_car_model.id_for_label }}");
|
||||||
const yearSelect = document.getElementById("{{ form.year.id_for_label }}");
|
const yearSelect = document.getElementById("{{ form.year.id_for_label }}");
|
||||||
const serieSelect = document.getElementById("{{ form.id_car_serie.id_for_label }}");
|
const serieSelect = document.getElementById("{{ form.id_car_serie.id_for_label }}");
|
||||||
const trimSelect = document.getElementById("{{ form.id_car_trim.id_for_label }}");
|
const trimSelect = document.getElementById("{{ form.id_car_trim.id_for_label }}");
|
||||||
const equipmentSelect = document.getElementById("equipment_id")
|
const equipmentSelect = document.getElementById("equipment_id")
|
||||||
const showSpecificationButton = document.getElementById("specification-btn");
|
const showSpecificationButton = document.getElementById("specification-btn");
|
||||||
const showEquipmentButton = document.getElementById("options-btn")
|
const showEquipmentButton = document.getElementById("options-btn")
|
||||||
const specificationsContent = document.getElementById("specificationsContent");
|
const specificationsContent = document.getElementById("specificationsContent");
|
||||||
const optionsContent = document.getElementById("optionsContent")
|
const optionsContent = document.getElementById("optionsContent")
|
||||||
const generationContainer = document.getElementById("generation-div")
|
const generationContainer = document.getElementById("generation-div")
|
||||||
|
|
||||||
const ajaxUrl = "{% url 'ajax_handler' %}";
|
const ajaxUrl = "{% url 'ajax_handler' %}";
|
||||||
|
|
||||||
const closeButton = document.querySelector(".btn-close");
|
const closeButton = document.querySelector(".btn-close");
|
||||||
const scanVinBtn = document.getElementById("scan-vin-btn");
|
const scanVinBtn = document.getElementById("scan-vin-btn");
|
||||||
const videoElement = document.getElementById("video");
|
const videoElement = document.getElementById("video");
|
||||||
const resultDisplay = document.getElementById("result");
|
const resultDisplay = document.getElementById("result");
|
||||||
const fallbackButton = document.getElementById("ocr-fallback-btn");
|
const fallbackButton = document.getElementById("ocr-fallback-btn");
|
||||||
let codeReader;
|
let codeReader;
|
||||||
codeReader = new ZXing.BrowserMultiFormatReader();
|
codeReader = new ZXing.BrowserMultiFormatReader();
|
||||||
let currentStream = null;
|
let currentStream = null;
|
||||||
|
|
||||||
function closeModal() {
|
function closeModal() {
|
||||||
stopScanner();
|
stopScanner();
|
||||||
try {
|
try {
|
||||||
const scannerModal = document.getElementById("scannerModal");
|
const scannerModal = document.getElementById("scannerModal");
|
||||||
if (scannerModal) {
|
if (scannerModal) {
|
||||||
document.activeElement.blur();
|
document.activeElement.blur();
|
||||||
scannerModal.setAttribute("inert", "true");
|
scannerModal.setAttribute("inert", "true");
|
||||||
const modalInstance = bootstrap.Modal.getInstance(scannerModal);
|
const modalInstance = bootstrap.Modal.getInstance(scannerModal);
|
||||||
if (modalInstance) modalInstance.hide();
|
if (modalInstance) modalInstance.hide();
|
||||||
if (scanVinBtn) scanVinBtn.focus();
|
if (scanVinBtn) scanVinBtn.focus();
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error closing scanner modal:", err);
|
console.error("Error closing scanner modal:", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function decodeVin() {
|
async function decodeVin() {
|
||||||
const vinNumber = vinInput.value.trim();
|
const vinNumber = vinInput.value.trim();
|
||||||
if (vinNumber.length !== 17) {
|
if (vinNumber.length !== 17) {
|
||||||
Swal.fire("error", "{% trans 'Please enter a valid VIN.' %}");
|
Swal.fire("error", "{% trans 'Please enter a valid VIN.' %}");
|
||||||
/*alert("{% trans 'Please enter a valid VIN.' %}");*/
|
/*alert("{% trans 'Please enter a valid VIN.' %}");*/
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
showLoading();
|
showLoading();
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${ajaxUrl}?action=decode_vin&vin_no=${vinNumber}`, {
|
const response = await fetch(`${ajaxUrl}?action=decode_vin&vin_no=${vinNumber}`, {
|
||||||
headers: {
|
headers: {
|
||||||
"X-Requested-With": "XMLHttpRequest",
|
"X-Requested-With": "XMLHttpRequest",
|
||||||
"X-CSRFToken": csrfToken,
|
"X-CSRFToken": csrfToken,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
hideLoading();
|
hideLoading();
|
||||||
await updateFields(data.data);
|
await updateFields(data.data);
|
||||||
} else {
|
} else {
|
||||||
hideLoading();
|
hideLoading();
|
||||||
Swal.fire("{% trans 'error' %}", data.error);
|
Swal.fire("{% trans 'error' %}", data.error);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error decoding VIN:", error);
|
console.error("Error decoding VIN:", error);
|
||||||
hideLoading();
|
hideLoading();
|
||||||
Swal.fire("error", "{% trans 'An error occurred while decoding the VIN.' %}");
|
Swal.fire("error", "{% trans 'An error occurred while decoding the VIN.' %}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateFields(vinData) {
|
async function updateFields(vinData) {
|
||||||
console.log(vinData);
|
console.log(vinData);
|
||||||
if (vinData.make_id) {
|
if (vinData.make_id) {
|
||||||
makeSelect.value = vinData.make_id;
|
makeSelect.value = vinData.make_id;
|
||||||
document.getElementById("make-check").innerHTML = "✓";
|
document.getElementById("make-check").innerHTML = "✓";
|
||||||
await loadModels(vinData.make_id);
|
await loadModels(vinData.make_id);
|
||||||
}
|
}
|
||||||
if (vinData.model_id) {
|
if (vinData.model_id) {
|
||||||
modelSelect.value = vinData.model_id;
|
modelSelect.value = vinData.model_id;
|
||||||
document.getElementById("model-check").innerHTML = "✓";
|
document.getElementById("model-check").innerHTML = "✓";
|
||||||
await loadSeries(vinData.model_id, vinData.year);
|
await loadSeries(vinData.model_id, vinData.year);
|
||||||
}
|
}
|
||||||
if (vinData.year) {
|
if (vinData.year) {
|
||||||
yearSelect.value = vinData.year;
|
yearSelect.value = vinData.year;
|
||||||
document.getElementById("year-check").innerHTML = "✓";
|
document.getElementById("year-check").innerHTML = "✓";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the scanner
|
// Start the scanner
|
||||||
async function startScanner() {
|
async function startScanner() {
|
||||||
codeReader
|
codeReader
|
||||||
.decodeFromVideoDevice(null, videoElement, async (result, err) => {
|
.decodeFromVideoDevice(null, videoElement, async (result, err) => {
|
||||||
let res = await result;
|
let res = await result;
|
||||||
if (result) {
|
if (result) {
|
||||||
vinInput.value = result.text;
|
vinInput.value = result.text;
|
||||||
closeModal();
|
closeModal();
|
||||||
await decodeVin();
|
await decodeVin();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.catch(console.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
function captureAndOCR() {
|
function captureAndOCR() {
|
||||||
const canvas = document.createElement("canvas");
|
const canvas = document.createElement("canvas");
|
||||||
const context = canvas.getContext("2d");
|
const context = canvas.getContext("2d");
|
||||||
canvas.width = videoElement.videoWidth;
|
canvas.width = videoElement.videoWidth;
|
||||||
canvas.height = videoElement.videoHeight;
|
canvas.height = videoElement.videoHeight;
|
||||||
context.drawImage(videoElement, 0, 0, canvas.width, canvas.height);
|
context.drawImage(videoElement, 0, 0, canvas.width, canvas.height);
|
||||||
Tesseract.recognize(canvas.toDataURL("image/png"), "eng")
|
Tesseract.recognize(canvas.toDataURL("image/png"), "eng")
|
||||||
.then(({ data: { text } }) => {
|
.then(({ data: { text } }) => {
|
||||||
const vin = text.match(/[A-HJ-NPR-Z0-9]{17}/);
|
const vin = text.match(/[A-HJ-NPR-Z0-9]{17}/);
|
||||||
if (vin) vinInput.value = vin[0];
|
if (vin) vinInput.value = vin[0];
|
||||||
closeModal();
|
closeModal();
|
||||||
decodeVin();
|
decodeVin();
|
||||||
})
|
})
|
||||||
.catch((err) => console.error("OCR Error:", err));
|
.catch((err) => console.error("OCR Error:", err));
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopScanner() {
|
function stopScanner() {
|
||||||
if (currentStream) {
|
if (currentStream) {
|
||||||
currentStream.getTracks().forEach((track) => track.stop());
|
currentStream.getTracks().forEach((track) => track.stop());
|
||||||
currentStream = null;
|
currentStream = null;
|
||||||
|
}
|
||||||
|
codeReader.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetDropdown(dropdown, placeholder) {
|
||||||
|
dropdown.innerHTML = `<option value="">${placeholder}</option>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadModels(makeId) {
|
||||||
|
resetDropdown(modelSelect, '{% trans "Select" %}');
|
||||||
|
const response = await fetch(`${ajaxUrl}?action=get_models&make_id=${makeId}`, {
|
||||||
|
headers: {
|
||||||
|
"X-Requested-With": "XMLHttpRequest",
|
||||||
|
"X-CSRFToken": csrfToken,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
data.forEach((model) => {
|
||||||
|
const option = document.createElement("option");
|
||||||
|
option.value = model.id_car_model;
|
||||||
|
option.textContent = document.documentElement.lang === "en" ? model.name : model.arabic_name;
|
||||||
|
modelSelect.appendChild(option);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadSeries(modelId, year) {
|
||||||
|
resetDropdown(serieSelect, '{% trans "Select" %}');
|
||||||
|
resetDropdown(trimSelect, '{% trans "Select" %}');
|
||||||
|
specificationsContent.innerHTML = "";
|
||||||
|
const response = await fetch(`${ajaxUrl}?action=get_series&model_id=${modelId}&year=${year}`, {
|
||||||
|
headers: {
|
||||||
|
"X-Requested-With": "XMLHttpRequest",
|
||||||
|
"X-CSRFToken": csrfToken,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
data.forEach((serie) => {
|
||||||
|
|
||||||
|
const option = document.createElement("option");
|
||||||
|
option.value = serie.id_car_serie;
|
||||||
|
option.textContent = document.documentElement.lang === "en" ? serie.name : serie.name;
|
||||||
|
generationContainer.innerHTML = serie.generation_name
|
||||||
|
serieSelect.appendChild(option);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadTrims(serie_id, model_id) {
|
||||||
|
resetDropdown(trimSelect, '{% trans "Select" %}');
|
||||||
|
specificationsContent.innerHTML = "";
|
||||||
|
const response = await fetch(`${ajaxUrl}?action=get_trims&serie_id=${serie_id}&model_id=${model_id}`, {
|
||||||
|
headers: {
|
||||||
|
"X-Requested-With": "XMLHttpRequest",
|
||||||
|
"X-CSRFToken": csrfToken,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
data.forEach((trim) => {
|
||||||
|
const option = document.createElement("option");
|
||||||
|
option.value = trim.id_car_trim;
|
||||||
|
option.textContent = document.documentElement.lang === "en" ? trim.name : trim.name;
|
||||||
|
trimSelect.appendChild(option);
|
||||||
|
});
|
||||||
|
showSpecificationButton.disabled = !this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadEquipment(trimId){
|
||||||
|
resetDropdown(equipmentSelect, '{% trans "Select" %}');
|
||||||
|
optionsContent.innerHTML = "";
|
||||||
|
const response = await fetch(`${ajaxUrl}?action=get_equipments&trim_id=${trimId}`, {
|
||||||
|
headers: {
|
||||||
|
'X-Requested-With': 'XMLHttpRequest',
|
||||||
|
'X-CSRFToken': csrfToken
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
data.forEach((equipment) => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = equipment.id_car_equipment;
|
||||||
|
option.textContent = equipment.name;
|
||||||
|
equipmentSelect.appendChild(option);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadSpecifications(trimId) {
|
||||||
|
specificationsContent.innerHTML = "";
|
||||||
|
const response = await fetch(`${ajaxUrl}?action=get_specifications&trim_id=${trimId}`, {
|
||||||
|
headers: {
|
||||||
|
"X-Requested-With": "XMLHttpRequest",
|
||||||
|
"X-CSRFToken": csrfToken,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
data.forEach((spec) => {
|
||||||
|
const parentDiv = document.createElement("div");
|
||||||
|
parentDiv.innerHTML = `<strong>${spec.parent_name}</strong>`;
|
||||||
|
spec.specifications.forEach((s) => {
|
||||||
|
const specDiv = document.createElement("div");
|
||||||
|
specDiv.innerHTML = `• ${s.s_name}: ${s.s_value} ${s.s_unit}`;
|
||||||
|
parentDiv.appendChild(specDiv);
|
||||||
|
});
|
||||||
|
specificationsContent.appendChild(parentDiv);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadOptions(equipmentId) {
|
||||||
|
optionsContent.innerHTML = "";
|
||||||
|
const response = await fetch(`${ajaxUrl}?action=get_options&equipment_id=${equipmentId}`, {
|
||||||
|
headers: {
|
||||||
|
"X-Requested-With": "XMLHttpRequest",
|
||||||
|
"X-CSRFToken": csrfToken,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
data.forEach((parent) => {
|
||||||
|
const parentDiv = document.createElement("div");
|
||||||
|
parentDiv.innerHTML = `<strong>${parent.parent_name}</strong>`;
|
||||||
|
parent.options.forEach((option) => {
|
||||||
|
const optDiv = document.createElement("div");
|
||||||
|
optDiv.innerHTML = `• ${option.option_name}`;
|
||||||
|
parentDiv.appendChild(optDiv);
|
||||||
|
});
|
||||||
|
optionsContent.appendChild(parentDiv);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
scanVinBtn.addEventListener("click", () => {
|
||||||
|
resultDisplay.textContent = "";
|
||||||
|
startScanner();
|
||||||
|
});
|
||||||
|
|
||||||
|
fallbackButton.addEventListener("click", () => {
|
||||||
|
captureAndOCR();
|
||||||
|
})
|
||||||
|
|
||||||
|
serieSelect.addEventListener("change", () => {
|
||||||
|
const serie_id = serieSelect.value;
|
||||||
|
const model_id = modelSelect.value;
|
||||||
|
if (serie_id && model_id) loadTrims(serie_id, model_id);
|
||||||
|
})
|
||||||
|
|
||||||
|
trimSelect.addEventListener("change", () => {
|
||||||
|
const trimId = trimSelect.value
|
||||||
|
showSpecificationButton.disabled = !trimId
|
||||||
|
showEquipmentButton.disabled = !trimId
|
||||||
|
if (trimId) loadSpecifications(trimId)
|
||||||
|
})
|
||||||
|
|
||||||
|
equipmentSelect.addEventListener("change", () => {
|
||||||
|
const equipmentId = equipmentSelect.value
|
||||||
|
if (equipmentId) loadOptions(equipmentId)
|
||||||
|
})
|
||||||
|
|
||||||
|
closeButton.addEventListener("click", closeModal);
|
||||||
|
makeSelect.addEventListener("change", (e) => {
|
||||||
|
loadModels(e.target.value, modelSelect.value);
|
||||||
|
})
|
||||||
|
modelSelect.addEventListener("change", (e) => {
|
||||||
|
loadSeries(e.target.value, yearSelect.value);
|
||||||
|
})
|
||||||
|
decodeVinBtn.addEventListener("click", decodeVin);
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
function showLoading() {
|
||||||
|
Swal.fire({
|
||||||
|
title: "{% trans 'Please Wait' %}",
|
||||||
|
text: "{% trans 'Loading' %}...",
|
||||||
|
allowOutsideClick: false,
|
||||||
|
didOpen: () => {
|
||||||
|
Swal.showLoading();
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
codeReader.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetDropdown(dropdown, placeholder) {
|
function hideLoading() {
|
||||||
dropdown.innerHTML = `<option value="">${placeholder}</option>`;
|
Swal.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadModels(makeId) {
|
function notify(tag, msg) {
|
||||||
resetDropdown(modelSelect, '{% trans "Select" %}');
|
Swal.fire({
|
||||||
const response = await fetch(`${ajaxUrl}?action=get_models&make_id=${makeId}`, {
|
icon: tag,
|
||||||
headers: {
|
titleText: msg,
|
||||||
"X-Requested-With": "XMLHttpRequest",
|
});
|
||||||
"X-CSRFToken": csrfToken,
|
}
|
||||||
},
|
|
||||||
});
|
|
||||||
const data = await response.json();
|
|
||||||
data.forEach((model) => {
|
|
||||||
const option = document.createElement("option");
|
|
||||||
option.value = model.id_car_model;
|
|
||||||
option.textContent = document.documentElement.lang === "en" ? model.name : model.arabic_name;
|
|
||||||
modelSelect.appendChild(option);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadSeries(modelId, year) {
|
|
||||||
resetDropdown(serieSelect, '{% trans "Select" %}');
|
|
||||||
resetDropdown(trimSelect, '{% trans "Select" %}');
|
|
||||||
specificationsContent.innerHTML = "";
|
|
||||||
const response = await fetch(`${ajaxUrl}?action=get_series&model_id=${modelId}&year=${year}`, {
|
|
||||||
headers: {
|
|
||||||
"X-Requested-With": "XMLHttpRequest",
|
|
||||||
"X-CSRFToken": csrfToken,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const data = await response.json();
|
|
||||||
data.forEach((serie) => {
|
|
||||||
|
|
||||||
const option = document.createElement("option");
|
|
||||||
option.value = serie.id_car_serie;
|
|
||||||
option.textContent = document.documentElement.lang === "en" ? serie.name : serie.name;
|
|
||||||
generationContainer.innerHTML = serie.generation_name
|
|
||||||
serieSelect.appendChild(option);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadTrims(serie_id, model_id) {
|
|
||||||
resetDropdown(trimSelect, '{% trans "Select" %}');
|
|
||||||
specificationsContent.innerHTML = "";
|
|
||||||
const response = await fetch(`${ajaxUrl}?action=get_trims&serie_id=${serie_id}&model_id=${model_id}`, {
|
|
||||||
headers: {
|
|
||||||
"X-Requested-With": "XMLHttpRequest",
|
|
||||||
"X-CSRFToken": csrfToken,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const data = await response.json();
|
|
||||||
data.forEach((trim) => {
|
|
||||||
const option = document.createElement("option");
|
|
||||||
option.value = trim.id_car_trim;
|
|
||||||
option.textContent = document.documentElement.lang === "en" ? trim.name : trim.name;
|
|
||||||
trimSelect.appendChild(option);
|
|
||||||
});
|
|
||||||
showSpecificationButton.disabled = !this.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadEquipment(trimId){
|
|
||||||
resetDropdown(equipmentSelect, '{% trans "Select" %}');
|
|
||||||
optionsContent.innerHTML = "";
|
|
||||||
const response = await fetch(`${ajaxUrl}?action=get_equipments&trim_id=${trimId}`, {
|
|
||||||
headers: {
|
|
||||||
'X-Requested-With': 'XMLHttpRequest',
|
|
||||||
'X-CSRFToken': csrfToken
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const data = await response.json();
|
|
||||||
data.forEach((equipment) => {
|
|
||||||
const option = document.createElement('option');
|
|
||||||
option.value = equipment.id_car_equipment;
|
|
||||||
option.textContent = equipment.name;
|
|
||||||
equipmentSelect.appendChild(option);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadSpecifications(trimId) {
|
|
||||||
specificationsContent.innerHTML = "";
|
|
||||||
const response = await fetch(`${ajaxUrl}?action=get_specifications&trim_id=${trimId}`, {
|
|
||||||
headers: {
|
|
||||||
"X-Requested-With": "XMLHttpRequest",
|
|
||||||
"X-CSRFToken": csrfToken,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const data = await response.json();
|
|
||||||
data.forEach((spec) => {
|
|
||||||
const parentDiv = document.createElement("div");
|
|
||||||
parentDiv.innerHTML = `<strong>${spec.parent_name}</strong>`;
|
|
||||||
spec.specifications.forEach((s) => {
|
|
||||||
const specDiv = document.createElement("div");
|
|
||||||
specDiv.innerHTML = `• ${s.s_name}: ${s.s_value} ${s.s_unit}`;
|
|
||||||
parentDiv.appendChild(specDiv);
|
|
||||||
});
|
|
||||||
specificationsContent.appendChild(parentDiv);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadOptions(equipmentId) {
|
|
||||||
optionsContent.innerHTML = "";
|
|
||||||
const response = await fetch(`${ajaxUrl}?action=get_options&equipment_id=${equipmentId}`, {
|
|
||||||
headers: {
|
|
||||||
"X-Requested-With": "XMLHttpRequest",
|
|
||||||
"X-CSRFToken": csrfToken,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const data = await response.json();
|
|
||||||
data.forEach((parent) => {
|
|
||||||
const parentDiv = document.createElement("div");
|
|
||||||
parentDiv.innerHTML = `<strong>${parent.parent_name}</strong>`;
|
|
||||||
parent.options.forEach((option) => {
|
|
||||||
const optDiv = document.createElement("div");
|
|
||||||
optDiv.innerHTML = `• ${option.option_name}`;
|
|
||||||
parentDiv.appendChild(optDiv);
|
|
||||||
});
|
|
||||||
optionsContent.appendChild(parentDiv);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
scanVinBtn.addEventListener("click", () => {
|
|
||||||
resultDisplay.textContent = "";
|
|
||||||
startScanner();
|
|
||||||
});
|
|
||||||
|
|
||||||
fallbackButton.addEventListener("click", () => {
|
|
||||||
captureAndOCR();
|
|
||||||
})
|
|
||||||
|
|
||||||
serieSelect.addEventListener("change", () => {
|
|
||||||
const serie_id = serieSelect.value;
|
|
||||||
const model_id = modelSelect.value;
|
|
||||||
if (serie_id && model_id) loadTrims(serie_id, model_id);
|
|
||||||
})
|
|
||||||
|
|
||||||
trimSelect.addEventListener("change", () => {
|
|
||||||
const trimId = trimSelect.value
|
|
||||||
showSpecificationButton.disabled = !trimId
|
|
||||||
showEquipmentButton.disabled = !trimId
|
|
||||||
if (trimId) loadSpecifications(trimId)
|
|
||||||
})
|
|
||||||
|
|
||||||
equipmentSelect.addEventListener("change", () => {
|
|
||||||
const equipmentId = equipmentSelect.value
|
|
||||||
if (equipmentId) loadOptions(equipmentId)
|
|
||||||
})
|
|
||||||
|
|
||||||
closeButton.addEventListener("click", closeModal);
|
|
||||||
makeSelect.addEventListener("change", (e) => {
|
|
||||||
loadModels(e.target.value, modelSelect.value);
|
|
||||||
})
|
|
||||||
modelSelect.addEventListener("change", (e) => {
|
|
||||||
loadSeries(e.target.value, yearSelect.value);
|
|
||||||
})
|
|
||||||
decodeVinBtn.addEventListener("click", decodeVin);
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
function showLoading() {
|
|
||||||
Swal.fire({
|
|
||||||
title: "{% trans 'Please Wait' %}",
|
|
||||||
text: "{% trans 'Loading' %}...",
|
|
||||||
allowOutsideClick: false,
|
|
||||||
didOpen: () => {
|
|
||||||
Swal.showLoading();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideLoading() {
|
|
||||||
Swal.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
function notify(tag, msg) {
|
|
||||||
Swal.fire({
|
|
||||||
icon: tag,
|
|
||||||
titleText: msg,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -133,9 +133,9 @@
|
|||||||
<span class="badge badge-phoenix fs-10 badge-phoenix-primary"><span class="badge-label">{{ _("Sold") }}</span><span class="ms-1" data-feather="package" style="height:13px;width:13px;"></span></span>
|
<span class="badge badge-phoenix fs-10 badge-phoenix-primary"><span class="badge-label">{{ _("Sold") }}</span><span class="ms-1" data-feather="package" style="height:13px;width:13px;"></span></span>
|
||||||
{% elif car.status == "hold" %}
|
{% elif car.status == "hold" %}
|
||||||
<span class="badge badge-phoenix fs-10 badge-phoenix-warning"><span class="badge-label">{{ _("Hold") }}</span><span class="ms-1"
|
<span class="badge badge-phoenix fs-10 badge-phoenix-warning"><span class="badge-label">{{ _("Hold") }}</span><span class="ms-1"
|
||||||
data-feather="alert-octagon"
|
data-feather="alert-octagon"
|
||||||
style="height:13px;
|
style="height:13px;
|
||||||
width:13px"></span></span>
|
width:13px"></span></span>
|
||||||
{% elif car.status == "reserved" %}
|
{% elif car.status == "reserved" %}
|
||||||
<span class="badge badge-phoenix fs-10 badge-phoenix-danger"><span class="badge-label">{{ _("Reserved") }}</span><span class="ms-1" data-feather="info" style="height:13px;width:13px;"></span></span>
|
<span class="badge badge-phoenix fs-10 badge-phoenix-danger"><span class="badge-label">{{ _("Reserved") }}</span><span class="ms-1" data-feather="info" style="height:13px;width:13px;"></span></span>
|
||||||
{% elif car.status == "damaged" %}
|
{% elif car.status == "damaged" %}
|
||||||
|
|||||||
@ -3,23 +3,23 @@
|
|||||||
{%block title%} {%trans 'Stocks'%} {%endblock%}
|
{%block title%} {%trans 'Stocks'%} {%endblock%}
|
||||||
{% block customCSS %}
|
{% block customCSS %}
|
||||||
<style>
|
<style>
|
||||||
.htmx-indicator{
|
.htmx-indicator{
|
||||||
opacity:0;
|
opacity:0;
|
||||||
transition: opacity 500ms ease-in;
|
transition: opacity 500ms ease-in;
|
||||||
}
|
}
|
||||||
.htmx-request .htmx-indicator{
|
.htmx-request .htmx-indicator{
|
||||||
opacity:1;
|
opacity:1;
|
||||||
}
|
}
|
||||||
.htmx-request.htmx-indicator{
|
.htmx-request.htmx-indicator{
|
||||||
opacity:1;
|
opacity:1;
|
||||||
}
|
}
|
||||||
.on-before-request{
|
.on-before-request{
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
.transition {
|
.transition {
|
||||||
transition: all ease-in 1s ;
|
transition: all ease-in 1s ;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock customCSS %}
|
{% endblock customCSS %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
@ -145,10 +145,16 @@
|
|||||||
hx-on::after-request="filter_after_request()">{{ _("Search") }}</button>
|
hx-on::after-request="filter_after_request()">{{ _("Search") }}</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<form class="update-price-form d-flex flex-row align-items-center ms-auto w-25 d-none" action="" method="post" style="float:right;">
|
<form hx-boost='true' action="{% url 'bulk_update_car_price' %}" method="post"
|
||||||
<input class="form-control me-2" type="number" placeholder='{{ _("Search") }}' name="price" aria-label="Price">
|
hx-include=".car-checkbox"
|
||||||
<button class="btn btn-outline-primary" type="submit">{{ _("Search") }}</button>
|
class="update-price-form d-flex flex-row align-items-center ms-auto w-25 d-none" style="float:right;">
|
||||||
</form>
|
{% csrf_token %}
|
||||||
|
<div class="form-floating me-2">
|
||||||
|
<input class="form-control" type="number" placeholder='{{ _("Search") }}' name="price" aria-label="Price" id="price">
|
||||||
|
<label for="price">{{ _("Price") }}</label>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-outline-primary" type="submit">{{ _("Search") }}</button>
|
||||||
|
</form>
|
||||||
<div class="table-responsive scrollbar transition">
|
<div class="table-responsive scrollbar transition">
|
||||||
<div class="d-flex flex-wrap align-items-center justify-content-between py-3 pe-0 fs-9">
|
<div class="d-flex flex-wrap align-items-center justify-content-between py-3 pe-0 fs-9">
|
||||||
<div class="d-flex"
|
<div class="d-flex"
|
||||||
@ -183,24 +189,24 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody class="list" id="project-list-table-body">
|
<tbody class="list" id="project-list-table-body">
|
||||||
{% for car in cars %}
|
{% for car in cars %}
|
||||||
<tr class="position-static">
|
<tr class="position-static">
|
||||||
<td class="align-middle white-space-nowrap">
|
<td class="align-middle white-space-nowrap">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input car-checkbox" type="checkbox" name="car" id="car-{{car.pk}}">
|
<input class="form-check-input car-checkbox" type="checkbox" name="car" value="{{ car.pk }}" id="car-{{car.pk}}">
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle white-space-nowrap ps-1">
|
<td class="align-middle white-space-nowrap ps-1">
|
||||||
<a class="fw-bold" href="{% url 'car_detail' car.slug %}">{{ car.vin }}</a>
|
<a class="fw-bold" href="{% url 'car_detail' car.slug %}">{{ car.vin }}</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle white-space-nowrap">
|
<td class="align-middle white-space-nowrap">
|
||||||
{% if car.id_car_make %}
|
{% if car.id_car_make %}
|
||||||
<p class="text-body mb-0">{{ car.id_car_make.get_local_name|default:car.id_car_make.name }}</p>
|
<p class="text-body mb-0">{{ car.id_car_make.get_local_name|default:car.id_car_make.name }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle white-space-nowrap">
|
<td class="align-middle white-space-nowrap">
|
||||||
{% if car.id_car_model %}
|
{% if car.id_car_model %}
|
||||||
<p class="text-body mb-0">{{ car.id_car_model.get_local_name|default:car.id_car_model.name }}</p>
|
<p class="text-body mb-0">{{ car.id_car_model.get_local_name|default:car.id_car_model.name }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle white-space-nowrap">
|
<td class="align-middle white-space-nowrap">
|
||||||
<p class="text-body mb-0">{{ car.year }}</p>
|
<p class="text-body mb-0">{{ car.year }}</p>
|
||||||
@ -238,9 +244,9 @@
|
|||||||
</td>
|
</td>
|
||||||
<td class="align-middle product white-space-nowrap">
|
<td class="align-middle product white-space-nowrap">
|
||||||
{% if not car.ready %}
|
{% if not car.ready %}
|
||||||
<span class="text-danger"> {{ _("NO") }} </span>
|
<span class="text-danger"> {{ _("NO") }} </span>
|
||||||
{%else%}
|
{%else%}
|
||||||
<span class="text-success"> {{ _("YES") }} </span>
|
<span class="text-success"> {{ _("YES") }} </span>
|
||||||
{%endif%}
|
{%endif%}
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle text-end white-space-nowrap pe-0 action">
|
<td class="align-middle text-end white-space-nowrap pe-0 action">
|
||||||
@ -275,56 +281,69 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block customJS %}
|
{% block customJS %}
|
||||||
<script>
|
<script>
|
||||||
links = document.querySelectorAll('.nav-link')
|
links = document.querySelectorAll('.nav-link')
|
||||||
links.forEach(link => {
|
|
||||||
link.addEventListener('click', () => {
|
|
||||||
links.forEach(link => {
|
links.forEach(link => {
|
||||||
link.classList.remove('active')
|
link.addEventListener('click', () => {
|
||||||
|
links.forEach(link => {
|
||||||
|
link.classList.remove('active')
|
||||||
|
})
|
||||||
|
link.classList.add('active')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
link.classList.add('active')
|
function on_before_request() {
|
||||||
})
|
document.querySelector('.table').classList.toggle('on-before-request')
|
||||||
})
|
document.querySelector('.model-select').classList.add('on-after-request')
|
||||||
function on_before_request() {
|
}
|
||||||
document.querySelector('.table').classList.toggle('on-before-request')
|
function on_after_request() {
|
||||||
document.querySelector('.model-select').classList.add('on-after-request')
|
document.querySelector('.table').classList.remove('on-before-request')
|
||||||
}
|
document.querySelector('.model-select').classList.remove('on-after-request')
|
||||||
function on_after_request() {
|
}
|
||||||
document.querySelector('.table').classList.remove('on-before-request')
|
function toggle_filter(){
|
||||||
document.querySelector('.model-select').classList.remove('on-after-request')
|
document.querySelector('.filter').classList.toggle('d-none')
|
||||||
}
|
document.querySelector('.filter-icon').classList.toggle("fa-caret-down");
|
||||||
function toggle_filter(){
|
document.querySelector('.filter-icon').classList.toggle("fa-caret-up");
|
||||||
document.querySelector('.filter').classList.toggle('d-none')
|
}
|
||||||
document.querySelector('.filter-icon').classList.toggle("fa-caret-down");
|
function filter_before_request(){
|
||||||
document.querySelector('.filter-icon').classList.toggle("fa-caret-up");
|
document.querySelector('.model-select').setAttribute('disabled', true)
|
||||||
}
|
document.querySelector('.year').setAttribute('disabled', true)
|
||||||
function filter_before_request(){
|
document.querySelector('.car_status').setAttribute('disabled', true)
|
||||||
document.querySelector('.model-select').setAttribute('disabled', true)
|
}
|
||||||
document.querySelector('.year').setAttribute('disabled', true)
|
function filter_after_request(){
|
||||||
document.querySelector('.car_status').setAttribute('disabled', true)
|
document.querySelector('.model-select').removeAttribute('disabled')
|
||||||
}
|
document.querySelector('.year').removeAttribute('disabled')
|
||||||
function filter_after_request(){
|
document.querySelector('.car_status').removeAttribute('disabled')
|
||||||
document.querySelector('.model-select').removeAttribute('disabled')
|
}
|
||||||
document.querySelector('.year').removeAttribute('disabled')
|
|
||||||
document.querySelector('.car_status').removeAttribute('disabled')
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById('select-all').addEventListener('change', function() {
|
document.getElementById('select-all').addEventListener('change', function() {
|
||||||
const checkboxes = document.querySelectorAll('#project-list-table-body input[type="checkbox"]');
|
const checkboxes = document.querySelectorAll('#project-list-table-body input[type="checkbox"]');
|
||||||
checkboxes.forEach(checkbox => {
|
if (this.checked) {
|
||||||
checkbox.checked = this.checked;
|
checkboxes.forEach(checkbox => checkbox.checked = true);
|
||||||
});
|
} else {
|
||||||
});
|
checkboxes.forEach(checkbox => checkbox.checked = false);
|
||||||
document.getElementById('car-checkbox').addEventListener('change', function() {
|
}
|
||||||
const form = document.querySelector('.update-price-form');
|
updateFormVisibility();
|
||||||
if (this.checked) {
|
});
|
||||||
form.classList.remove('d-none');
|
|
||||||
} else {
|
|
||||||
form.classList.add('d-none');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
const cbox = document.querySelectorAll('.car-checkbox');
|
||||||
{% endblock customJS %}
|
cbox.forEach(checkbox => {
|
||||||
|
checkbox.addEventListener('change', function() {
|
||||||
|
updateFormVisibility();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function updateFormVisibility() {
|
||||||
|
const form = document.querySelector('.update-price-form');
|
||||||
|
const checkedCount = document.querySelectorAll('.car-checkbox:checked').length;
|
||||||
|
const submitButton = form.querySelector('button[type="submit"]');
|
||||||
|
if (checkedCount > 0) {
|
||||||
|
form.classList.remove('d-none');
|
||||||
|
submitButton.textContent = `Update Cost Price (${checkedCount})`;
|
||||||
|
} else {
|
||||||
|
form.classList.add('d-none');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock customJS %}
|
||||||
|
|||||||
@ -45,7 +45,7 @@
|
|||||||
#065535
|
#065535
|
||||||
#000000
|
#000000
|
||||||
#133337
|
#133337
|
||||||
#ffc0cb</a>
|
#ffc0cb</a>
|
||||||
<br>
|
<br>
|
||||||
<span class="glyphicon glyphicon-star"></span>707
|
<span class="glyphicon glyphicon-star"></span>707
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -184,141 +184,141 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</main>
|
</main>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="button-row">
|
<div class="button-row">
|
||||||
<button id="download-pdf" class="btn btn-phoenix-primary">
|
<button id="download-pdf" class="btn btn-phoenix-primary">
|
||||||
<i class="fas fa-download"></i> {% trans 'Download transfer' %}
|
<i class="fas fa-download"></i> {% trans 'Download transfer' %}
|
||||||
</button>
|
</button>
|
||||||
<button id="accept"
|
<button id="accept"
|
||||||
class="btn btn-phoenix-success"
|
class="btn btn-phoenix-success"
|
||||||
data-bs-toggle="modal"
|
data-bs-toggle="modal"
|
||||||
data-bs-target="#acceptModal">
|
data-bs-target="#acceptModal">
|
||||||
<i class="fas fa-check-circle"></i> {% trans 'Accept transfer' %}
|
<i class="fas fa-check-circle"></i> {% trans 'Accept transfer' %}
|
||||||
</button>
|
</button>
|
||||||
<button id="reject"
|
<button id="reject"
|
||||||
class="btn btn-phoenix-danger"
|
class="btn btn-phoenix-danger"
|
||||||
data-bs-toggle="modal"
|
data-bs-toggle="modal"
|
||||||
data-bs-target="#rejectModal">
|
data-bs-target="#rejectModal">
|
||||||
<i class="fas fa-times-circle"></i> {% trans 'Reject transfer' %}
|
<i class="fas fa-times-circle"></i> {% trans 'Reject transfer' %}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<!-- Accept Modal -->
|
<!-- Accept Modal -->
|
||||||
<div class="modal fade"
|
<div class="modal fade"
|
||||||
id="acceptModal"
|
id="acceptModal"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
aria-labelledby="acceptModalLabel"
|
aria-labelledby="acceptModalLabel"
|
||||||
aria-hidden="true">
|
aria-hidden="true">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title" id="acceptModalLabel">{% trans 'Accept transfer' %}</h5>
|
<h5 class="modal-title" id="acceptModalLabel">{% trans 'Accept transfer' %}</h5>
|
||||||
<button type="button"
|
<button type="button"
|
||||||
class="btn-close"
|
class="btn-close"
|
||||||
data-bs-dismiss="modal"
|
data-bs-dismiss="modal"
|
||||||
aria-label="Close"></button>
|
aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">{% trans 'Are you sure you want to accept this transfer?' %}</div>
|
<div class="modal-body">{% trans 'Are you sure you want to accept this transfer?' %}</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-phoenix-secondary" data-bs-dismiss="modal">{% trans 'Cancel' %}</button>
|
<button type="button" class="btn btn-phoenix-secondary" data-bs-dismiss="modal">{% trans 'Cancel' %}</button>
|
||||||
<a class="btn btn-phoenix-success"
|
<a class="btn btn-phoenix-success"
|
||||||
href="{% url 'transfer_accept_reject' transfer.car.pk transfer.pk %}?status=accepted">Confirm</a>
|
href="{% url 'transfer_accept_reject' transfer.car.pk transfer.pk %}?status=accepted">Confirm</a>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<!-- Reject Modal -->
|
<!-- Reject Modal -->
|
||||||
<div class="modal fade"
|
<div class="modal fade"
|
||||||
id="rejectModal"
|
id="rejectModal"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
aria-labelledby="rejectModalLabel"
|
aria-labelledby="rejectModalLabel"
|
||||||
aria-hidden="true">
|
aria-hidden="true">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title" id="rejectModalLabel">{% trans 'Reject transfer' %}</h5>
|
<h5 class="modal-title" id="rejectModalLabel">{% trans 'Reject transfer' %}</h5>
|
||||||
<button type="button"
|
<button type="button"
|
||||||
class="btn-close"
|
class="btn-close"
|
||||||
data-bs-dismiss="modal"
|
data-bs-dismiss="modal"
|
||||||
aria-label="Close"></button>
|
aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">{% trans 'Are you sure you want to reject this transfer?' %}</div>
|
<div class="modal-body">{% trans 'Are you sure you want to reject this transfer?' %}</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-phoenix-secondary" data-bs-dismiss="modal">{% trans 'Cancel' %}</button>
|
<button type="button" class="btn btn-phoenix-secondary" data-bs-dismiss="modal">{% trans 'Cancel' %}</button>
|
||||||
<a class="btn btn-phoenix-success"
|
<a class="btn btn-phoenix-success"
|
||||||
href="{% url 'transfer_accept_reject' transfer.car.pk transfer.pk %}?status=rejected">Confirm</a>
|
href="{% url 'transfer_accept_reject' transfer.car.pk transfer.pk %}?status=rejected">Confirm</a>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="transfer-row" id="transfer-content">
|
</div>
|
||||||
|
<div class="transfer-row" id="transfer-content">
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="transfer-header">
|
<div class="transfer-header">
|
||||||
<svg width="101"
|
<svg width="101"
|
||||||
height="24"
|
height="24"
|
||||||
viewBox="0 0 101 24"
|
viewBox="0 0 101 24"
|
||||||
fill="none"
|
fill="none"
|
||||||
xmlns="http://www.w3.org/2000/svg">
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
<!-- SVG Paths -->
|
<!-- SVG Paths -->
|
||||||
</svg>
|
</svg>
|
||||||
<h1 style="margin-top: 10px;">
|
<h1 style="margin-top: 10px;">
|
||||||
<b>{% trans "Transfer" %}</b>
|
<b>{% trans "Transfer" %}</b>
|
||||||
</h1>
|
</h1>
|
||||||
<p>{% trans "Thank you for choosing us. We appreciate your business" %}</p>
|
<p>{% trans "Thank you for choosing us. We appreciate your business" %}</p>
|
||||||
</div>
|
|
||||||
<!-- Details -->
|
|
||||||
<div class="transfer-details">
|
|
||||||
<p>
|
|
||||||
<strong>{% trans "Date" %} :</strong> {{ transfer.created_at }}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<strong>{% trans "From" %} :</strong> {{ transfer.from_dealer }}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<strong>{% trans "To" %} :</strong> {{ transfer.to_dealer }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<!-- Items Table -->
|
|
||||||
<div class="transfer-table">
|
|
||||||
<table class="table table-bordered">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>{% trans "Item" %}</th>
|
|
||||||
<th class="text-center">{% trans "Quantity" %}</th>
|
|
||||||
<th class="text-center">{% trans "Unit Price" %}</th>
|
|
||||||
<th class="text-center">{% trans "Total" %}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>{{ transfer.car }}</td>
|
|
||||||
<td class="text-center">{{ transfer.quantity }}</td>
|
|
||||||
<td class="text-center">{{ transfer.car.finances.selling_price }}</td>
|
|
||||||
<td class="text-center">{{ transfer.total_price }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<!-- Total -->
|
|
||||||
<div class="transfer-total">
|
|
||||||
<p>
|
|
||||||
<strong>{% trans "Total Amount" %}:</strong> <span class="highlight">${{ transfer.total_price }}</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<!-- Footer Note -->
|
|
||||||
<div class="footer-note">
|
|
||||||
<p>
|
|
||||||
{% trans "If you have any questions, feel free to contact us at" %} <a href="mailto:support@example.com">support@tenhal.com</a>.
|
|
||||||
</p>
|
|
||||||
<p>{% trans "Thank you for your business" %}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
<!-- Details -->
|
||||||
|
<div class="transfer-details">
|
||||||
|
<p>
|
||||||
|
<strong>{% trans "Date" %} :</strong> {{ transfer.created_at }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>{% trans "From" %} :</strong> {{ transfer.from_dealer }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>{% trans "To" %} :</strong> {{ transfer.to_dealer }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<!-- Items Table -->
|
||||||
|
<div class="transfer-table">
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{% trans "Item" %}</th>
|
||||||
|
<th class="text-center">{% trans "Quantity" %}</th>
|
||||||
|
<th class="text-center">{% trans "Unit Price" %}</th>
|
||||||
|
<th class="text-center">{% trans "Total" %}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>{{ transfer.car }}</td>
|
||||||
|
<td class="text-center">{{ transfer.quantity }}</td>
|
||||||
|
<td class="text-center">{{ transfer.car.finances.selling_price }}</td>
|
||||||
|
<td class="text-center">{{ transfer.total_price }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!-- Total -->
|
||||||
|
<div class="transfer-total">
|
||||||
|
<p>
|
||||||
|
<strong>{% trans "Total Amount" %}:</strong> <span class="highlight">${{ transfer.total_price }}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<!-- Footer Note -->
|
||||||
|
<div class="footer-note">
|
||||||
|
<p>
|
||||||
|
{% trans "If you have any questions, feel free to contact us at" %} <a href="mailto:support@example.com">support@tenhal.com</a>.
|
||||||
|
</p>
|
||||||
|
<p>{% trans "Thank you for your business" %}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
<!-- Bootstrap JS (Optional) -->
|
<!-- Bootstrap JS (Optional) -->
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
<!-- jsPDF Library -->
|
<!-- jsPDF Library -->
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
document.getElementById('download-pdf').addEventListener('click', function () {
|
document.getElementById('download-pdf').addEventListener('click', function () {
|
||||||
const element = document.getElementById('transfer-content');
|
const element = document.getElementById('transfer-content');
|
||||||
|
|
||||||
@ -349,6 +349,6 @@
|
|||||||
// Handle the reject action here
|
// Handle the reject action here
|
||||||
$('#rejectModal').modal('hide');
|
$('#rejectModal').modal('hide');
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -13,9 +13,9 @@
|
|||||||
</div>
|
</div>
|
||||||
{% include "partials/search_box.html" %}
|
{% include "partials/search_box.html" %}
|
||||||
{% if page_obj.object_list %}
|
{% if page_obj.object_list %}
|
||||||
<div class="table-responsive px-1 scrollbar mt-3">
|
<div class="table-responsive px-1 scrollbar mt-3">
|
||||||
<table class="table align-items-center table-flush">
|
<table class="table align-items-center table-flush">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="bg-body-highlight">
|
<tr class="bg-body-highlight">
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Item Number" %}</th>
|
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Item Number" %}</th>
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Name" %}</th>
|
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Name" %}</th>
|
||||||
@ -24,50 +24,50 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody class="list">
|
<tbody class="list">
|
||||||
|
|
||||||
{% for expense in expenses %}
|
{% for expense in expenses %}
|
||||||
|
|
||||||
<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">
|
<td class="align-middle product white-space-nowrap">
|
||||||
{{ expense.item_number }}
|
{{ expense.item_number }}
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle product white-space-nowrap">
|
<td class="align-middle product white-space-nowrap">
|
||||||
{{ expense.name }}
|
{{ expense.name }}
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle product white-space-nowrap">
|
<td class="align-middle product white-space-nowrap">
|
||||||
{{ expense.uom }}
|
{{ expense.uom }}
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle product white-space-nowrap">
|
<td class="align-middle product white-space-nowrap">
|
||||||
<a href="{% url 'item_expense_update' expense.pk %}"
|
<a href="{% url 'item_expense_update' expense.pk %}"
|
||||||
class="btn btn-sm btn-phoenix-success">
|
class="btn btn-sm btn-phoenix-success">
|
||||||
{% trans "Update" %}
|
{% trans "Update" %}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle product white-space-nowrap">
|
<td class="align-middle product white-space-nowrap">
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle white-space-nowrap text-start">
|
<td class="align-middle white-space-nowrap text-start">
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="6" class="text-center text-muted">{% trans "No Accounts Found" %}</td>
|
<td colspan="6" class="text-center text-muted">{% trans "No Accounts Found" %}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex justify-content-end mt-3">
|
<div class="d-flex justify-content-end mt-3">
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
{% if is_paginated %}
|
{% if is_paginated %}
|
||||||
{% include 'partials/pagination.html' %}
|
{% include 'partials/pagination.html' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -6,9 +6,9 @@
|
|||||||
{% block title %}
|
{% block title %}
|
||||||
{# Check if an 'object' exists in the context #}
|
{# Check if an 'object' exists in the context #}
|
||||||
{% if object %}
|
{% if object %}
|
||||||
{% trans 'Update Service'%}
|
{% trans 'Update Service'%}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% trans 'Add New Service'%}
|
{% trans 'Add New Service'%}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|||||||
@ -7,68 +7,68 @@
|
|||||||
<div class="row mt-4">
|
<div class="row mt-4">
|
||||||
|
|
||||||
<div class="d-flex justify-content-between mb-2">
|
<div class="d-flex justify-content-between mb-2">
|
||||||
<h3 class="">{% trans "Services" %}</h3>
|
<h3 class="">{% trans "Services" %}</h3>
|
||||||
<a href="{% url 'item_service_create' %}" class="btn btn-md btn-phoenix-primary"><i class="fa fa-plus me-2"></i>{% trans "Add Service" %}</a>
|
<a href="{% url 'item_service_create' %}" class="btn btn-md btn-phoenix-primary"><i class="fa fa-plus me-2"></i>{% trans "Add Service" %}</a>
|
||||||
</div>
|
</div>
|
||||||
{% include "partials/search_box.html" %}
|
{% include "partials/search_box.html" %}
|
||||||
{% if page_obj.object_list %}
|
{% if page_obj.object_list %}
|
||||||
<div class="table-responsive px-1 scrollbar mt-3">
|
<div class="table-responsive px-1 scrollbar mt-3">
|
||||||
<table class="table align-items-center table-flush">
|
<table class="table align-items-center table-flush">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="bg-body-highlight">
|
<tr class="bg-body-highlight">
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Item Number" %}</th>
|
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Item Number" %}</th>
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Name" %}</th>
|
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Name" %}</th>
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Unit of Measure" %}</th>
|
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Unit of Measure" %}</th>
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Taxable" %}</th>
|
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Taxable" %}</th>
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Account" %}</th>
|
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Account" %}</th>
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Action" %}</th>
|
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Action" %}</th>
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tbody class="list">
|
|
||||||
|
|
||||||
{% for service in services %}
|
|
||||||
|
|
||||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
|
||||||
<td class="align-middle product white-space-nowrap">
|
|
||||||
{{ service.pk }}
|
|
||||||
</td>
|
|
||||||
<td class="align-middle product white-space-nowrap">
|
|
||||||
{{ service.get_local_name|default:service.name }}
|
|
||||||
</td>
|
|
||||||
<td class="align-middle product white-space-nowrap">
|
|
||||||
{{ service.get_uom_display }}
|
|
||||||
</td>
|
|
||||||
<td class="align-middle product white-space-nowrap">
|
|
||||||
{{ service.taxable|yesno }}
|
|
||||||
</td>
|
|
||||||
<td class="align-middle product white-space-nowrap">
|
|
||||||
{{ service.item.co }}
|
|
||||||
</td>
|
|
||||||
<td class="align-middle white-space-nowrap text-start">
|
|
||||||
<a href="{% url 'item_service_update' service.pk %}"
|
|
||||||
class="btn btn-sm btn-phoenix-success">
|
|
||||||
{% trans "Update" %}
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody class="list">
|
||||||
|
|
||||||
|
{% for service in services %}
|
||||||
|
|
||||||
|
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
||||||
|
<td class="align-middle product white-space-nowrap">
|
||||||
|
{{ service.pk }}
|
||||||
|
</td>
|
||||||
|
<td class="align-middle product white-space-nowrap">
|
||||||
|
{{ service.get_local_name|default:service.name }}
|
||||||
|
</td>
|
||||||
|
<td class="align-middle product white-space-nowrap">
|
||||||
|
{{ service.get_uom_display }}
|
||||||
|
</td>
|
||||||
|
<td class="align-middle product white-space-nowrap">
|
||||||
|
{{ service.taxable|yesno }}
|
||||||
|
</td>
|
||||||
|
<td class="align-middle product white-space-nowrap">
|
||||||
|
{{ service.item.co }}
|
||||||
|
</td>
|
||||||
|
<td class="align-middle white-space-nowrap text-start">
|
||||||
|
<a href="{% url 'item_service_update' service.pk %}"
|
||||||
|
class="btn btn-sm btn-phoenix-success">
|
||||||
|
{% trans "Update" %}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="6" class="text-center text-muted">{% trans "No Accounts Found" %}</td>
|
<td colspan="6" class="text-center text-muted">{% trans "No Accounts Found" %}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex justify-content-end mt-3">
|
<div class="d-flex justify-content-end mt-3">
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
{% if is_paginated %}
|
{% if is_paginated %}
|
||||||
{% include 'partials/pagination.html' %}
|
{% include 'partials/pagination.html' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -4,9 +4,9 @@
|
|||||||
{% block title %}
|
{% block title %}
|
||||||
{# Check if an 'object' exists in the context #}
|
{# Check if an 'object' exists in the context #}
|
||||||
{% if object %}
|
{% if object %}
|
||||||
{% trans 'Update Bank Account'%}
|
{% trans 'Update Bank Account'%}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% trans 'Add New Bank Account'%}
|
{% trans 'Add New Bank Account'%}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|||||||
@ -7,59 +7,59 @@
|
|||||||
<div class="row mt-4">
|
<div class="row mt-4">
|
||||||
|
|
||||||
<div class="d-flex justify-content-between mb-2">
|
<div class="d-flex justify-content-between mb-2">
|
||||||
<h3 class="">{% trans "Bank Accounts" %}</h3>
|
<h3 class="">{% trans "Bank Accounts" %}</h3>
|
||||||
<a href="{% url 'bank_account_create' %}" class="btn btn-md btn-phoenix-primary"><i class="fa fa-plus me-2"></i>{% trans "Add Bank Account" %}</a>
|
<a href="{% url 'bank_account_create' %}" class="btn btn-md btn-phoenix-primary"><i class="fa fa-plus me-2"></i>{% trans "Add Bank Account" %}</a>
|
||||||
</div>
|
</div>
|
||||||
{% include "partials/search_box.html" %}
|
{% include "partials/search_box.html" %}
|
||||||
{% if page_obj.object_list %}
|
{% if page_obj.object_list %}
|
||||||
<div class="table-responsive px-1 scrollbar mt-3">
|
<div class="table-responsive px-1 scrollbar mt-3">
|
||||||
<table class="table align-items-center table-flush">
|
<table class="table align-items-center table-flush">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="bg-body-highlight">
|
<tr class="bg-body-highlight">
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Name" %}</th>
|
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Name" %}</th>
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Account Number" %}</th>
|
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Account Number" %}</th>
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Type" %}</th>
|
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Type" %}</th>
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Action" %}</th>
|
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Action" %}</th>
|
||||||
</tr>
|
|
||||||
|
|
||||||
</thead>
|
|
||||||
<tbody class="list">
|
|
||||||
{% for bank in bank_accounts %}
|
|
||||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
|
||||||
<td class="align-middle product white-space-nowrap">
|
|
||||||
{{ bank.name }}
|
|
||||||
</td>
|
|
||||||
<td class="align-middle product white-space-nowrap">
|
|
||||||
{{ bank.account_number }}
|
|
||||||
</td>
|
|
||||||
<td class="align-middle product white-space-nowrap">
|
|
||||||
{{ bank.account_type|capfirst }}
|
|
||||||
</td>
|
|
||||||
<td class="align-middle product white-space-nowrap">
|
|
||||||
<a href="{% url 'bank_account_update' bank.pk %}"
|
|
||||||
class="btn btn-sm btn-phoenix-success">
|
|
||||||
{% trans "Update" %}
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
{% empty %}
|
</thead>
|
||||||
<tr>
|
<tbody class="list">
|
||||||
<td colspan="6" class="text-center text-muted">{% trans "No Accounts Found" %}</td>
|
{% for bank in bank_accounts %}
|
||||||
</tr>
|
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
||||||
{% endfor %}
|
<td class="align-middle product white-space-nowrap">
|
||||||
|
{{ bank.name }}
|
||||||
|
</td>
|
||||||
|
<td class="align-middle product white-space-nowrap">
|
||||||
|
{{ bank.account_number }}
|
||||||
|
</td>
|
||||||
|
<td class="align-middle product white-space-nowrap">
|
||||||
|
{{ bank.account_type|capfirst }}
|
||||||
|
</td>
|
||||||
|
<td class="align-middle product white-space-nowrap">
|
||||||
|
<a href="{% url 'bank_account_update' bank.pk %}"
|
||||||
|
class="btn btn-sm btn-phoenix-success">
|
||||||
|
{% trans "Update" %}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
</tbody>
|
{% empty %}
|
||||||
</table>
|
<tr>
|
||||||
</div>
|
<td colspan="6" class="text-center text-muted">{% trans "No Accounts Found" %}</td>
|
||||||
<div class="d-flex justify-content-end mt-3">
|
</tr>
|
||||||
<div class="d-flex">
|
{% endfor %}
|
||||||
{% if is_paginated %}
|
|
||||||
{% include 'partials/pagination.html' %}
|
</tbody>
|
||||||
{% endif %}
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="d-flex justify-content-end mt-3">
|
||||||
{% endif %}
|
<div class="d-flex">
|
||||||
|
{% if is_paginated %}
|
||||||
|
{% include 'partials/pagination.html' %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -1,20 +1,20 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{% trans 'Bills' %}
|
{% trans 'Bills' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block bills %}
|
{% block bills %}
|
||||||
<a class="nav-link active fw-bold">
|
<a class="nav-link active fw-bold">
|
||||||
{% trans 'Bills'|capfirst %}
|
{% trans 'Bills'|capfirst %}
|
||||||
<span class="visually-hidden">(current)</span>
|
<span class="visually-hidden">(current)</span>
|
||||||
</a>
|
</a>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<div class="row mt-4">
|
<div class="row mt-4">
|
||||||
<div class="d-flex justify-content-between mb-2">
|
<div class="d-flex justify-content-between mb-2">
|
||||||
<h3 class="">{% trans "Bills" %}</h3>
|
<h3 class="">{% trans "Bills" %}</h3>
|
||||||
<a href="{% url 'bill-create' entity.slug %}" class="btn btn-md btn-phoenix-primary"><i class="fa fa-plus me-2"></i>{% trans 'New Bill' %}</a>
|
<a href="{% url 'bill-create' entity.slug %}" class="btn btn-md btn-phoenix-primary"><i class="fa fa-plus me-2"></i>{% trans 'New Bill' %}</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
@ -52,51 +52,51 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody class="list">
|
<tbody class="list">
|
||||||
{% for bill in bills %}
|
{% for bill in bills %}
|
||||||
|
|
||||||
<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">
|
<td class="align-middle product white-space-nowrap">
|
||||||
{{ bill.bill_number }}
|
{{ bill.bill_number }}
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle product white-space-nowrap">
|
<td class="align-middle product white-space-nowrap">
|
||||||
{% if bill.is_draft %}
|
{% if bill.is_draft %}
|
||||||
<span class="badge badge-phoenix badge-phoenix-warning">
|
<span class="badge badge-phoenix badge-phoenix-warning">
|
||||||
{% elif bill.is_review %}
|
{% elif bill.is_review %}
|
||||||
<span class="badge badge-phoenix badge-phoenix-info">
|
<span class="badge badge-phoenix badge-phoenix-info">
|
||||||
{% elif bill.is_approved %}
|
{% elif bill.is_approved %}
|
||||||
<span class="badge badge-phoenix badge-phoenix-success">
|
|
||||||
{% elif bill.is_paid %}
|
|
||||||
<span class="badge badge-phoenix badge-phoenix-success">
|
<span class="badge badge-phoenix badge-phoenix-success">
|
||||||
{% elif bill.is_canceled %}
|
{% elif bill.is_paid %}
|
||||||
|
<span class="badge badge-phoenix badge-phoenix-success">
|
||||||
|
{% elif bill.is_canceled %}
|
||||||
<span class="badge badge-phoenix badge-phoenix-danger">
|
<span class="badge badge-phoenix badge-phoenix-danger">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{{ bill.bill_status }}
|
{{ bill.bill_status }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle product white-space-nowrap">
|
<td class="align-middle product white-space-nowrap">
|
||||||
{{bill.vendor.vendor_name}}
|
{{bill.vendor.vendor_name}}
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle product white-space-nowrap">
|
<td class="align-middle product white-space-nowrap">
|
||||||
<div class="btn-reveal-trigger position-static">
|
<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>
|
<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">
|
<div class="dropdown-menu dropdown-menu-end py-2">
|
||||||
<a href="{% url 'bill-detail' entity_slug=entity.slug bill_pk=bill.pk %}" class="dropdown-item text-success-dark">{% trans 'View' %}</a>
|
<a href="{% url 'bill-detail' entity_slug=entity.slug bill_pk=bill.pk %}" class="dropdown-item text-success-dark">{% trans 'View' %}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="5" class="text-center text-muted">
|
<td colspan="5" class="text-center text-muted">
|
||||||
{% trans 'No bill found.' %}
|
{% trans 'No bill found.' %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex justify-content-between mt-3"><span class="d-none d-sm-inline-block" data-list-info="data-list-info">{{ page_obj.start_index }} {{ _("to") }} {{ page_obj.end_index }}<span class="text-body-tertiary"> {{ _("Items of")}} </span>{{ page_obj.paginator.count }}</span>
|
<div class="d-flex justify-content-between mt-3"><span class="d-none d-sm-inline-block" data-list-info="data-list-info">{{ page_obj.start_index }} {{ _("to") }} {{ page_obj.end_index }}<span class="text-body-tertiary"> {{ _("Items of")}} </span>{{ page_obj.paginator.count }}</span>
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
|
|
||||||
{% if is_paginated %}
|
{% if is_paginated %}
|
||||||
|
|||||||
@ -59,97 +59,97 @@
|
|||||||
|
|
||||||
<div class="row mt-4">
|
<div class="row mt-4">
|
||||||
|
|
||||||
<div class="table-responsive px-1 scrollbar mt-3">
|
<div class="table-responsive px-1 scrollbar mt-3">
|
||||||
<table class="table align-items-center table-flush">
|
<table class="table align-items-center table-flush">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="bg-body-highlight">
|
<tr class="bg-body-highlight">
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{{ _('JE Number') }}</th>
|
<th class="sort white-space-nowrap align-middle" scope="col">{{ _('JE Number') }}</th>
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{{ _('Date') }}</th>
|
<th class="sort white-space-nowrap align-middle" scope="col">{{ _('Date') }}</th>
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{{ _('Debit') }}</th>
|
<th class="sort white-space-nowrap align-middle" scope="col">{{ _('Debit') }}</th>
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{{ _('Credit') }}</th>
|
<th class="sort white-space-nowrap align-middle" scope="col">{{ _('Credit') }}</th>
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{{ _('Description') }}</th>
|
<th class="sort white-space-nowrap align-middle" scope="col">{{ _('Description') }}</th>
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{{ _('Actions') }}</th>
|
<th class="sort white-space-nowrap align-middle" scope="col">{{ _('Actions') }}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="list">
|
<tbody class="list">
|
||||||
{% for tx in account.transactionmodel_set.all %}
|
{% for tx in account.transactionmodel_set.all %}
|
||||||
<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">
|
<td class="align-middle product white-space-nowrap">
|
||||||
{{ tx.journal_entry.je_number }}
|
{{ tx.journal_entry.je_number }}
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle product white-space-nowrap">
|
<td class="align-middle product white-space-nowrap">
|
||||||
{{ tx.journal_entry.timestamp }}
|
{{ tx.journal_entry.timestamp }}
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle product white-space-nowrap">
|
<td class="align-middle product white-space-nowrap">
|
||||||
{% if tx.tx_type == 'debit' %}
|
{% if tx.tx_type == 'debit' %}
|
||||||
<i class="fa-solid fa-circle-up"></i> {{ tx.amount }}
|
<i class="fa-solid fa-circle-up"></i> {{ tx.amount }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle product white-space-nowrap">
|
<td class="align-middle product white-space-nowrap">
|
||||||
{% if tx.tx_type == 'credit' %}
|
{% if tx.tx_type == 'credit' %}
|
||||||
<i class="fa-solid fa-circle-down"></i> {{ tx.amount }}
|
<i class="fa-solid fa-circle-down"></i> {{ tx.amount }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle product white-space-nowrap">
|
<td class="align-middle product white-space-nowrap">
|
||||||
{% if tx.description %}
|
{% if tx.description %}
|
||||||
{{ tx.description }}
|
{{ tx.description }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle white-space-nowrap text-start">
|
<td class="align-middle white-space-nowrap text-start">
|
||||||
<div class="btn-reveal-trigger position-static">
|
<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>
|
<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">
|
<div class="dropdown-menu dropdown-menu-end py-2">
|
||||||
<a class="dropdown-item" href="{% url 'payment_details' tx.journal_entry.pk %}">{% trans 'view'|capfirst %}</a>
|
<a class="dropdown-item" href="{% url 'payment_details' tx.journal_entry.pk %}">{% trans 'view'|capfirst %}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
<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">
|
<td class="align-middle product white-space-nowrap">
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle product white-space-nowrap">
|
<td class="align-middle product white-space-nowrap">
|
||||||
<span class="fw-bold fs-8">{{ _("Total") }}</span>
|
<span class="fw-bold fs-8">{{ _("Total") }}</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle product white-space-nowrap">
|
<td class="align-middle product white-space-nowrap">
|
||||||
<span class="fw-bold fs-8 text-success">{{ total_debits }} <span class="icon-saudi_riyal"></span></span>
|
<span class="fw-bold fs-8 text-success">{{ total_debits }} <span class="icon-saudi_riyal"></span></span>
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle product white-space-nowrap">
|
<td class="align-middle product white-space-nowrap">
|
||||||
<span class="fw-bold fs-8 text-danger">{{ total_credits }} <span class="icon-saudi_riyal"></span></span>
|
<span class="fw-bold fs-8 text-danger">{{ total_credits }} <span class="icon-saudi_riyal"></span></span>
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle product white-space-nowrap">
|
<td class="align-middle product white-space-nowrap">
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle white-space-nowrap text-start">
|
<td class="align-middle white-space-nowrap text-start">
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-3 d-flex">
|
||||||
</div>
|
<a class="btn btn-sm btn-phoenix-primary me-1" href="{% url 'account_update' account.pk %}">
|
||||||
|
|
||||||
<div class="mt-3 d-flex">
|
|
||||||
<a class="btn btn-sm btn-phoenix-primary me-1" href="{% url 'account_update' account.pk %}">
|
|
||||||
<!-- <i class="bi bi-pencil-square"></i> -->
|
<!-- <i class="bi bi-pencil-square"></i> -->
|
||||||
<i class="fa-solid fa-pen-to-square"></i> {{ _('Edit') }}
|
<i class="fa-solid fa-pen-to-square"></i> {{ _('Edit') }}
|
||||||
</a>
|
</a>
|
||||||
<a class="btn btn-sm btn-phoenix-danger me-1" data-bs-toggle="modal" data-bs-target="#deleteModal">
|
<a class="btn btn-sm btn-phoenix-danger me-1" data-bs-toggle="modal" data-bs-target="#deleteModal">
|
||||||
<!-- <i class="bi bi-trash-fill"></i> -->
|
<!-- <i class="bi bi-trash-fill"></i> -->
|
||||||
<i class="fa-solid fa-trash"></i> {{ _('Delete') }}
|
<i class="fa-solid fa-trash"></i> {{ _('Delete') }}
|
||||||
</a>
|
</a>
|
||||||
<a class="btn btn-sm btn-phoenix-secondary" href="{% url 'account_list' %}">
|
<a class="btn btn-sm btn-phoenix-secondary" href="{% url 'account_list' %}">
|
||||||
<!-- <i class="bi bi-arrow-left-square-fill"></i> -->
|
<!-- <i class="bi bi-arrow-left-square-fill"></i> -->
|
||||||
<i class="fa-regular fa-circle-left"></i> {% trans 'Back to List' %}
|
<i class="fa-regular fa-circle-left"></i> {% trans 'Back to List' %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<!--test-->
|
<!--test-->
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -5,9 +5,9 @@
|
|||||||
{% block title %}
|
{% block title %}
|
||||||
{# Check if an 'object' exists in the context #}
|
{# Check if an 'object' exists in the context #}
|
||||||
{% if object %}
|
{% if object %}
|
||||||
{% trans 'Update Account'%}
|
{% trans 'Update Account'%}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% trans 'Add New Account'%}
|
{% trans 'Add New Account'%}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|||||||
@ -10,8 +10,8 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row mt-4">
|
<div class="row mt-4">
|
||||||
<div class="d-flex justify-content-between mb-2">
|
<div class="d-flex justify-content-between mb-2">
|
||||||
<h3 class=""><i class="fa-solid fa-book"></i> {% trans "Accounts" %}</h3>
|
<h3 class=""><i class="fa-solid fa-book"></i> {% trans "Accounts" %}</h3>
|
||||||
<a href="{% url 'account_create' %}" class="btn btn-md btn-phoenix-primary"><i class="fa fa-plus me-2"></i>{% trans 'New Account' %}</a>
|
<a href="{% url 'account_create' %}" class="btn btn-md btn-phoenix-primary"><i class="fa fa-plus me-2"></i>{% trans 'New Account' %}</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Account Type Tabs -->
|
<!-- Account Type Tabs -->
|
||||||
@ -130,15 +130,15 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block customerJS %}
|
{% block customerJS %}
|
||||||
<script>
|
<script>
|
||||||
// Handle delete modal for all tables
|
// Handle delete modal for all tables
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
var deleteModal = document.getElementById('deleteModal');
|
var deleteModal = document.getElementById('deleteModal');
|
||||||
deleteModal.addEventListener('show.bs.modal', function(event) {
|
deleteModal.addEventListener('show.bs.modal', function(event) {
|
||||||
var button = event.relatedTarget;
|
var button = event.relatedTarget;
|
||||||
var accountId = button.closest('tr').getAttribute('data-account-id');
|
var accountId = button.closest('tr').getAttribute('data-account-id');
|
||||||
document.getElementById('deleteAccountBtn').href = `/accounts/delete/${accountId}/`;
|
document.getElementById('deleteAccountBtn').href = `/accounts/delete/${accountId}/`;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
</script>
|
||||||
</script>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -1,69 +1,69 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% if accounts %}
|
{% if accounts %}
|
||||||
<div class="table-responsive px-1 scrollbar mt-3">
|
<div class="table-responsive px-1 scrollbar mt-3">
|
||||||
<table class="table align-items-center table-flush">
|
<table class="table align-items-center table-flush">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="bg-body-highlight">
|
<tr class="bg-body-highlight">
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Account Name" %}</th>
|
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Account Name" %}</th>
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Code" %}</th>
|
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Code" %}</th>
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Balance Type" %}</th>
|
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Balance Type" %}</th>
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Active" %}</th>
|
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Active" %}</th>
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col"></th>
|
<th class="sort white-space-nowrap align-middle" scope="col"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="list">
|
<tbody class="list">
|
||||||
{% for account in accounts %}
|
{% for account in accounts %}
|
||||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static" data-account-id="{{ account.uuid }}">
|
<tr class="hover-actions-trigger btn-reveal-trigger position-static" data-account-id="{{ account.uuid }}">
|
||||||
<td class="align-middle product white-space-nowrap">
|
<td class="align-middle product white-space-nowrap">
|
||||||
{{ account.name }}
|
{{ account.name }}
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle product white-space-nowrap">
|
<td class="align-middle product white-space-nowrap">
|
||||||
{{ account.code }}
|
{{ account.code }}
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle product white-space-nowrap">
|
<td class="align-middle product white-space-nowrap">
|
||||||
{% if account.balance_type == 'debit' %}
|
{% if account.balance_type == 'debit' %}
|
||||||
<div class="badge badge-phoenix fs-10 badge-phoenix-success"><span class="fw-bold"><i class="fa-solid fa-circle-up"></i> {{ _("Debit") }}</span></div>
|
<div class="badge badge-phoenix fs-10 badge-phoenix-success"><span class="fw-bold"><i class="fa-solid fa-circle-up"></i> {{ _("Debit") }}</span></div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="badge badge-phoenix fs-10 badge-phoenix-danger"><span class="fw-bold"><i class="fa-solid fa-circle-down"></i> {{ _("Credit") }}</span></div>
|
<div class="badge badge-phoenix fs-10 badge-phoenix-danger"><span class="fw-bold"><i class="fa-solid fa-circle-down"></i> {{ _("Credit") }}</span></div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle product white-space-nowrap">
|
<td class="align-middle product white-space-nowrap">
|
||||||
{% if account.active %}
|
{% if account.active %}
|
||||||
<span class="fw-bold text-success fas fa-check-circle"></span>
|
<span class="fw-bold text-success fas fa-check-circle"></span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="fw-bold text-danger far fa-times-circle"></span>
|
<span class="fw-bold text-danger far fa-times-circle"></span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle white-space-nowrap text-start">
|
<td class="align-middle white-space-nowrap text-start">
|
||||||
<div class="btn-reveal-trigger position-static">
|
<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>
|
<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">
|
<div class="dropdown-menu dropdown-menu-end py-2">
|
||||||
<a href="{% url 'account_detail' account.uuid %}" class="dropdown-item text-success-dark">
|
<a href="{% url 'account_detail' account.uuid %}" class="dropdown-item text-success-dark">
|
||||||
{% trans "View" %}
|
{% trans "View" %}
|
||||||
</a>
|
</a>
|
||||||
<div class="dropdown-divider"></div>
|
<div class="dropdown-divider"></div>
|
||||||
<button class="dropdown-item text-danger" data-bs-toggle="modal" data-bs-target="#deleteModal">{% trans "Delete" %}</button>
|
<button class="dropdown-item text-danger" data-bs-toggle="modal" data-bs-target="#deleteModal">{% trans "Delete" %}</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</td>
|
||||||
</td>
|
</tr>
|
||||||
</tr>
|
{% empty %}
|
||||||
{% empty %}
|
<tr>
|
||||||
<tr>
|
<td colspan="6" class="text-center text-muted">{% trans "No Accounts Found" %}</td>
|
||||||
<td colspan="6" class="text-center text-muted">{% trans "No Accounts Found" %}</td>
|
</tr>
|
||||||
</tr>
|
{% endfor %}
|
||||||
{% endfor %}
|
</tbody>
|
||||||
</tbody>
|
</table>
|
||||||
</table>
|
</div>
|
||||||
</div>
|
<div class="d-flex justify-content-end mt-3">
|
||||||
<div class="d-flex justify-content-end mt-3">
|
<div class="d-flex">
|
||||||
<div class="d-flex">
|
{% if is_paginated %}
|
||||||
{% if is_paginated %}
|
{% include 'partials/pagination.html' %}
|
||||||
{% include 'partials/pagination.html' %}
|
{% endif %}
|
||||||
{% endif %}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="alert ">
|
<div class="alert ">
|
||||||
{% trans "No accounts found in this category." %}
|
{% trans "No accounts found in this category." %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -35,88 +35,88 @@
|
|||||||
<div class="row mt-4">
|
<div class="row mt-4">
|
||||||
|
|
||||||
<div class="d-flex justify-content-between mb-2">
|
<div class="d-flex justify-content-between mb-2">
|
||||||
<h3 class="">{% trans "Journal Entries" %}</h3>
|
<h3 class="">{% trans "Journal Entries" %}</h3>
|
||||||
<a href="{% url 'journalentry_create' ledger.pk %}" class="btn btn-md btn-phoenix-primary"><i class="fa fa-plus me-2"></i>{% trans "Add Journal Entry" %}</a>
|
<a href="{% url 'journalentry_create' ledger.pk %}" class="btn btn-md btn-phoenix-primary"><i class="fa fa-plus me-2"></i>{% trans "Add Journal Entry" %}</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="table-responsive px-1 scrollbar">
|
|
||||||
<table class="table align-items-center table-flush">
|
|
||||||
<thead>
|
<div class="table-responsive px-1 scrollbar">
|
||||||
<tr class="bg-body-highlight">
|
<table class="table align-items-center table-flush">
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Document Number" %}</th>
|
<thead>
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Timestamp" %}</th>
|
<tr class="bg-body-highlight">
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Activity" %}</th>
|
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Document Number" %}</th>
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Description" %}</th>
|
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Timestamp" %}</th>
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Posted" %}</th>
|
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Activity" %}</th>
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Locked" %}</th>
|
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Description" %}</th>
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Transaction Count" %}</th>
|
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Posted" %}</th>
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Action" %}</th>
|
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Locked" %}</th>
|
||||||
</tr>
|
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Transaction Count" %}</th>
|
||||||
</thead>
|
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Action" %}</th>
|
||||||
<tbody class="list">
|
</tr>
|
||||||
{% for je in journal_entries %}
|
</thead>
|
||||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
<tbody class="list">
|
||||||
<td class="align-middle product white-space-nowrap">{{ je.je_number }}</td>
|
{% for je in journal_entries %}
|
||||||
<td class="align-middle product white-space-nowrap">{{ je.timestamp }}</td>
|
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
||||||
<td class="align-middle product white-space-nowrap">
|
<td class="align-middle product white-space-nowrap">{{ je.je_number }}</td>
|
||||||
{% if je.get_activity_display %}
|
<td class="align-middle product white-space-nowrap">{{ je.timestamp }}</td>
|
||||||
{{ je.get_activity_display }}
|
<td class="align-middle product white-space-nowrap">
|
||||||
{% endif %}
|
{% if je.get_activity_display %}
|
||||||
</td>
|
{{ je.get_activity_display }}
|
||||||
<td class="align-middle product white-space-nowrap">{{ je.description }}</td>
|
{% endif %}
|
||||||
<td class="align-middle product white-space-nowrap">
|
</td>
|
||||||
{% if je.is_posted %}
|
<td class="align-middle product white-space-nowrap">{{ je.description }}</td>
|
||||||
<i class="fa-solid fa-square-check text-success"></i>
|
<td class="align-middle product white-space-nowrap">
|
||||||
{% else %}
|
{% if je.is_posted %}
|
||||||
<i class="fa-solid fa-circle-xmark text-danger"></i>
|
<i class="fa-solid fa-square-check text-success"></i>
|
||||||
{% endif %}
|
{% else %}
|
||||||
</td>
|
<i class="fa-solid fa-circle-xmark text-danger"></i>
|
||||||
<td class="align-middle product white-space-nowrap">
|
{% endif %}
|
||||||
{% if je.is_locked %}
|
</td>
|
||||||
<i class="fa-solid fa-lock text-success"></i>
|
<td class="align-middle product white-space-nowrap">
|
||||||
{% else %}
|
{% if je.is_locked %}
|
||||||
<i class="fa-solid fa-unlock text-danger"></i>
|
<i class="fa-solid fa-lock text-success"></i>
|
||||||
{% endif %}
|
{% else %}
|
||||||
</td>
|
<i class="fa-solid fa-unlock text-danger"></i>
|
||||||
<td class="align-middle product white-space-nowrap">
|
{% endif %}
|
||||||
{{ je.txs_count }}
|
</td>
|
||||||
</td>
|
<td class="align-middle product white-space-nowrap">
|
||||||
<td class="align-middle white-space-nowrap text-start">
|
{{ je.txs_count }}
|
||||||
<div class="btn-reveal-trigger position-static">
|
</td>
|
||||||
<button
|
<td class="align-middle white-space-nowrap text-start">
|
||||||
class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10"
|
<div class="btn-reveal-trigger position-static">
|
||||||
type="button"
|
<button
|
||||||
data-bs-toggle="dropdown"
|
class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10"
|
||||||
data-boundary="window"
|
type="button"
|
||||||
aria-haspopup="true"
|
data-bs-toggle="dropdown"
|
||||||
aria-expanded="false"
|
data-boundary="window"
|
||||||
data-bs-reference="parent">
|
aria-haspopup="true"
|
||||||
<span class="fas fa-ellipsis-h fs-10"></span>
|
aria-expanded="false"
|
||||||
</button>
|
data-bs-reference="parent">
|
||||||
<div class="dropdown-menu dropdown-menu-end py-2">
|
<span class="fas fa-ellipsis-h fs-10"></span>
|
||||||
<a class="dropdown-item" href="{% url 'journalentry_transactions' je.pk %}">{% trans "View" %}</a>
|
</button>
|
||||||
<a class="dropdown-item" href="{% url 'journalentry_txs' je.entity_slug je.ledger_id je.pk %}">{% trans "Transactions" %}</a>
|
<div class="dropdown-menu dropdown-menu-end py-2">
|
||||||
{% if je.can_delete %}
|
<a class="dropdown-item" href="{% url 'journalentry_transactions' je.pk %}">{% trans "View" %}</a>
|
||||||
<a class="dropdown-item" href="{% url 'journalentry_delete' je.pk %}">{% trans "Delete" %}</a>
|
<a class="dropdown-item" href="{% url 'journalentry_txs' je.entity_slug je.ledger_id je.pk %}">{% trans "Transactions" %}</a>
|
||||||
{% endif %}
|
{% if je.can_delete %}
|
||||||
|
<a class="dropdown-item" href="{% url 'journalentry_delete' je.pk %}">{% trans "Delete" %}</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</td>
|
||||||
</td>
|
</tr>
|
||||||
</tr>
|
{% empty %}
|
||||||
{% empty %}
|
<tr>
|
||||||
<tr>
|
<td colspan="8" class="text-center">{% trans "No Bank Accounts Found" %}</td>
|
||||||
<td colspan="8" class="text-center">{% trans "No Bank Accounts Found" %}</td>
|
</tr>
|
||||||
</tr>
|
{% endfor %}
|
||||||
{% endfor %}
|
</tbody>
|
||||||
</tbody>
|
</table>
|
||||||
</table>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<!--test-->
|
<!--test-->
|
||||||
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -9,8 +9,8 @@
|
|||||||
<div class="row mt-4">
|
<div class="row mt-4">
|
||||||
|
|
||||||
<div class="d-flex justify-content-between mb-2">
|
<div class="d-flex justify-content-between mb-2">
|
||||||
<h3 class="">{% trans "Ledger" %}</h3>
|
<h3 class="">{% trans "Ledger" %}</h3>
|
||||||
<a href="{% url 'ledger_create' %}" class="btn btn-md btn-phoenix-primary"><i class="fa fa-plus me-2"></i>{% trans 'Create Ledger' %}</a>
|
<a href="{% url 'ledger_create' %}" class="btn btn-md btn-phoenix-primary"><i class="fa fa-plus me-2"></i>{% trans 'Create Ledger' %}</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="table-responsive px-1 scrollbar mt-3">
|
<div class="table-responsive px-1 scrollbar mt-3">
|
||||||
<table class="table align-items-center table-flush">
|
<table class="table align-items-center table-flush">
|
||||||
@ -31,7 +31,7 @@
|
|||||||
|
|
||||||
<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">
|
<td class="align-middle product white-space-nowrap">
|
||||||
{% if ledger.invoicemodel %}
|
{% if ledger.invoicemodel %}
|
||||||
<a href="{% url 'invoice_detail' ledger.invoicemodel.pk %}">{{ ledger.get_wrapped_model_instance }}</a>
|
<a href="{% url 'invoice_detail' ledger.invoicemodel.pk %}">{{ ledger.get_wrapped_model_instance }}</a>
|
||||||
{% elif ledger.billmodel %}
|
{% elif ledger.billmodel %}
|
||||||
<a href="{% url 'bill_detail' ledger.billmodel.pk %}">{{ ledger.get_wrapped_model_instance }}</a>
|
<a href="{% url 'bill_detail' ledger.billmodel.pk %}">{{ ledger.get_wrapped_model_instance }}</a>
|
||||||
@ -40,7 +40,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle product white-space-nowrap">
|
<td class="align-middle product white-space-nowrap">
|
||||||
<a class="btn btn-sm btn-phoenix-primary"
|
<a class="btn btn-sm btn-phoenix-primary"
|
||||||
href="{% url 'journalentry_list' ledger.pk %}">
|
href="{% url 'journalentry_list' ledger.pk %}">
|
||||||
<i class="fa-solid fa-right-left"></i>
|
<i class="fa-solid fa-right-left"></i>
|
||||||
<span>
|
<span>
|
||||||
@ -50,17 +50,17 @@
|
|||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle product white-space-nowrap">
|
<td class="align-middle product white-space-nowrap">
|
||||||
{{ ledger.created |date }}
|
{{ ledger.created |date }}
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle product white-space-nowrap">
|
<td class="align-middle product white-space-nowrap">
|
||||||
{% if ledger.is_posted %}
|
{% if ledger.is_posted %}
|
||||||
<i class="fa-solid fa-square-check text-success"></i>
|
<i class="fa-solid fa-square-check text-success"></i>
|
||||||
{% else %}
|
{% else %}
|
||||||
<i class="fa-solid fa-circle-xmark text-danger"></i>
|
<i class="fa-solid fa-circle-xmark text-danger"></i>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle product white-space-nowrap">
|
<td class="align-middle product white-space-nowrap">
|
||||||
{% if ledger.is_locked %}
|
{% if ledger.is_locked %}
|
||||||
<i class="fa-solid fa-lock text-success"></i>
|
<i class="fa-solid fa-lock text-success"></i>
|
||||||
{% else %}
|
{% else %}
|
||||||
<i class="fa-solid fa-unlock text-danger"></i>
|
<i class="fa-solid fa-unlock text-danger"></i>
|
||||||
@ -121,12 +121,12 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex justify-content-end mt-3">
|
<div class="d-flex justify-content-end mt-3">
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
{% if is_paginated %}
|
{% if is_paginated %}
|
||||||
{% include 'partials/pagination.html' %}
|
{% include 'partials/pagination.html' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -4,9 +4,9 @@
|
|||||||
{% block title %}
|
{% block title %}
|
||||||
{# Check if an 'object' exists in the context #}
|
{# Check if an 'object' exists in the context #}
|
||||||
{% if object %}
|
{% if object %}
|
||||||
{% trans 'Update Organization'%}
|
{% trans 'Update Organization'%}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% trans 'Add New Organization'%}
|
{% trans 'Add New Organization'%}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|||||||
@ -132,12 +132,12 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex justify-content-end mt-3">
|
<div class="d-flex justify-content-end mt-3">
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
{% if is_paginated %}
|
{% if is_paginated %}
|
||||||
{% include 'partials/pagination.html' %}
|
{% include 'partials/pagination.html' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -2,134 +2,134 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container mt-4">
|
<div class="container mt-4">
|
||||||
<h2>{{ title }}</h2>
|
<h2>{{ title }}</h2>
|
||||||
|
|
||||||
<form method="post">
|
<form method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form.as_p }}
|
{{ form.as_p }}
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<h4>Items</h4>
|
<h4>Items</h4>
|
||||||
{{ formset.management_form }}
|
{{ formset.management_form }}
|
||||||
|
|
||||||
<div id="formset-container">
|
<div id="formset-container">
|
||||||
{% for form in formset %}
|
{% for form in formset %}
|
||||||
<div class="item-form row g-3 mb-3 align-items-end">
|
<div class="item-form row g-3 mb-3 align-items-end">
|
||||||
{{ form.id }}
|
{{ form.id }}
|
||||||
<div class="col-md-2">
|
<div class="col-md-2">
|
||||||
{{ form.make.label_tag }}
|
{{ form.make.label_tag }}
|
||||||
{{ form.make }}
|
{{ form.make }}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
{{ form.model.label_tag }}
|
||||||
|
{{ form.model }}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
{{ form.serie.label_tag }}
|
||||||
|
{{ form.serie }}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
{{ form.trim.label_tag }}
|
||||||
|
{{ form.trim }}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-1">
|
||||||
|
{{ form.year.label_tag }}
|
||||||
|
{{ form.year }}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
{{ form.color.label_tag }}
|
||||||
|
{{ form.color }}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
{{ form.expected_cost.label_tag }}
|
||||||
|
{{ form.expected_cost }}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-1">
|
||||||
|
<button type="button" class="btn btn-phoenix-danger btn-sm remove-row">Remove</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-2">
|
{% endfor %}
|
||||||
{{ form.model.label_tag }}
|
</div>
|
||||||
{{ form.model }}
|
|
||||||
</div>
|
|
||||||
<div class="col-md-2">
|
|
||||||
{{ form.serie.label_tag }}
|
|
||||||
{{ form.serie }}
|
|
||||||
</div>
|
|
||||||
<div class="col-md-2">
|
|
||||||
{{ form.trim.label_tag }}
|
|
||||||
{{ form.trim }}
|
|
||||||
</div>
|
|
||||||
<div class="col-md-1">
|
|
||||||
{{ form.year.label_tag }}
|
|
||||||
{{ form.year }}
|
|
||||||
</div>
|
|
||||||
<div class="col-md-2">
|
|
||||||
{{ form.color.label_tag }}
|
|
||||||
{{ form.color }}
|
|
||||||
</div>
|
|
||||||
<div class="col-md-2">
|
|
||||||
{{ form.expected_cost.label_tag }}
|
|
||||||
{{ form.expected_cost }}
|
|
||||||
</div>
|
|
||||||
<div class="col-md-1">
|
|
||||||
<button type="button" class="btn btn-phoenix-danger btn-sm remove-row">Remove</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="button" id="add-item" class="btn btn-phoenix-secondary btn-sm mb-3">+ Add Item</button>
|
<button type="button" id="add-item" class="btn btn-phoenix-secondary btn-sm mb-3">+ Add Item</button>
|
||||||
|
|
||||||
<div class="mt-3">
|
<div class="mt-3">
|
||||||
<button type="submit" class="btn btn-phoenix-primary">Save</button>
|
<button type="submit" class="btn btn-phoenix-primary">Save</button>
|
||||||
<a href="{% url 'purchase_order_list' %}" class="btn btn-phoenix-secondary">Cancel</a>
|
<a href="{% url 'purchase_order_list' %}" class="btn btn-phoenix-secondary">Cancel</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
const container = document.getElementById('formset-container');
|
const container = document.getElementById('formset-container');
|
||||||
const addBtn = document.getElementById('add-item');
|
const addBtn = document.getElementById('add-item');
|
||||||
const totalForms = document.querySelector('#id_form-TOTAL_FORMS');
|
const totalForms = document.querySelector('#id_form-TOTAL_FORMS');
|
||||||
const emptyForm = document.querySelector('.empty-form').cloneNode(true);
|
const emptyForm = document.querySelector('.empty-form').cloneNode(true);
|
||||||
|
|
||||||
function updateFormIndex(formRow, index) {
|
function updateFormIndex(formRow, index) {
|
||||||
formRow.querySelectorAll('input, select').forEach(input => {
|
formRow.querySelectorAll('input, select').forEach(input => {
|
||||||
input.name = input.name.replace('__prefix__', index);
|
input.name = input.name.replace('__prefix__', index);
|
||||||
input.id = input.id.replace('__prefix__', index);
|
input.id = input.id.replace('__prefix__', index);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
addBtn.addEventListener('click', function () {
|
addBtn.addEventListener('click', function () {
|
||||||
const currentCount = parseInt(totalForms.value);
|
const currentCount = parseInt(totalForms.value);
|
||||||
const newForm = emptyForm.cloneNode(true);
|
const newForm = emptyForm.cloneNode(true);
|
||||||
updateFormIndex(newForm, currentCount);
|
updateFormIndex(newForm, currentCount);
|
||||||
newForm.classList.remove('empty-form');
|
newForm.classList.remove('empty-form');
|
||||||
|
|
||||||
// Attach remove handler
|
// Attach remove handler
|
||||||
const removeBtn = newForm.querySelector('.remove-row');
|
const removeBtn = newForm.querySelector('.remove-row');
|
||||||
removeBtn.addEventListener('click', function () {
|
removeBtn.addEventListener('click', function () {
|
||||||
newForm.remove();
|
newForm.remove();
|
||||||
totalForms.value = parseInt(totalForms.value) - 1;
|
totalForms.value = parseInt(totalForms.value) - 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
container.appendChild(newForm);
|
container.appendChild(newForm);
|
||||||
totalForms.value = currentCount + 1;
|
totalForms.value = currentCount + 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
document.querySelectorAll('.remove-row').forEach(btn => {
|
document.querySelectorAll('.remove-row').forEach(btn => {
|
||||||
btn.addEventListener('click', function () {
|
btn.addEventListener('click', function () {
|
||||||
const row = this.closest('.item-form');
|
const row = this.closest('.item-form');
|
||||||
row.remove();
|
row.remove();
|
||||||
totalForms.value = parseInt(totalForms.value) - 1;
|
totalForms.value = parseInt(totalForms.value) - 1;
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
</script>
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- Empty form template -->
|
<!-- Empty form template -->
|
||||||
<div class="empty-form d-none">
|
<div class="empty-form d-none">
|
||||||
<div class="item-form row g-3 mb-3 align-items-end">
|
<div class="item-form row g-3 mb-3 align-items-end">
|
||||||
<div class="col-md-2">
|
<div class="col-md-2">
|
||||||
<select name="form-__prefix__-make" class="form-select make-select"></select>
|
<select name="form-__prefix__-make" class="form-select make-select"></select>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-2">
|
<div class="col-md-2">
|
||||||
<select name="form-__prefix__-model" class="form-select model-select"></select>
|
<select name="form-__prefix__-model" class="form-select model-select"></select>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-2">
|
<div class="col-md-2">
|
||||||
<select name="form-__prefix__-serie" class="form-select serie-select"></select>
|
<select name="form-__prefix__-serie" class="form-select serie-select"></select>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-2">
|
<div class="col-md-2">
|
||||||
<select name="form-__prefix__-trim" class="form-select trim-select"></select>
|
<select name="form-__prefix__-trim" class="form-select trim-select"></select>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-1">
|
<div class="col-md-1">
|
||||||
<input type="number" name="form-__prefix__-year" class="form-control">
|
<input type="number" name="form-__prefix__-year" class="form-control">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-2">
|
<div class="col-md-2">
|
||||||
<input type="text" name="form-__prefix__-color" class="form-control">
|
<input type="text" name="form-__prefix__-color" class="form-control">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-2">
|
<div class="col-md-2">
|
||||||
<input type="number" step="0.01" name="form-__prefix__-expected_cost" class="form-control">
|
<input type="number" step="0.01" name="form-__prefix__-expected_cost" class="form-control">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-1">
|
<div class="col-md-1">
|
||||||
<button type="button" class="btn btn-phoenix-danger btn-sm remove-row">Remove</button>
|
<button type="button" class="btn btn-phoenix-danger btn-sm remove-row">Remove</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -26,7 +26,7 @@
|
|||||||
<p class="lead">{{ _("Your payment was successful")}}. {{ _("Your order is being processed")}}.</p>
|
<p class="lead">{{ _("Your payment was successful")}}. {{ _("Your order is being processed")}}.</p>
|
||||||
{% if invoice %}
|
{% if invoice %}
|
||||||
<a href="{% url 'invoice_preview_html' invoice.pk %}" class="btn btn-phoenix-primary mt-3"><i class="fas fa-eye"></i>
|
<a href="{% url 'invoice_preview_html' invoice.pk %}" class="btn btn-phoenix-primary mt-3"><i class="fas fa-eye"></i>
|
||||||
{{ _("View Invoice")}}</a>
|
{{ _("View Invoice")}}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{% url 'home' %}" class="btn btn-phoenix-success mt-3">
|
<a href="{% url 'home' %}" class="btn btn-phoenix-success mt-3">
|
||||||
<i class="fas fa-home"></i> {{ _("Back to Home")}}
|
<i class="fas fa-home"></i> {{ _("Back to Home")}}
|
||||||
|
|||||||
@ -17,7 +17,7 @@
|
|||||||
<i class="fa fa-save me-1"></i>{{ _("Save") }}
|
<i class="fa fa-save me-1"></i>{{ _("Save") }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% if object_list %}
|
{% if object_list %}
|
||||||
|
|
||||||
|
|
||||||
{% block order_table %}
|
{% block order_table %}
|
||||||
<div class="table-responsive px-1 scrollbar mt-3">
|
<div class="table-responsive px-1 scrollbar mt-3">
|
||||||
|
|||||||
@ -1,116 +1,116 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load static i18n crispy_forms_tags %}
|
{% load static i18n crispy_forms_tags %}
|
||||||
{% block customCSS %}
|
{% block customCSS %}
|
||||||
<style>
|
<style>
|
||||||
.color-card {
|
.color-card {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
border: 2px solid transparent;
|
border: 2px solid transparent;
|
||||||
width: 80px; /* Increased from 3rem for better visibility */
|
width: 80px; /* Increased from 3rem for better visibility */
|
||||||
height: 80px; /* Increased from 3rem for better visibility */
|
height: 80px; /* Increased from 3rem for better visibility */
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-card:hover {
|
.color-card:hover {
|
||||||
transform: translateY(-3px);
|
transform: translateY(-3px);
|
||||||
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-option {
|
.color-option {
|
||||||
display: block;
|
display: block;
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-radio {
|
.color-radio {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-radio:checked + .color-display {
|
.color-radio:checked + .color-display {
|
||||||
border: 2px solid #0d6efd;
|
border: 2px solid #0d6efd;
|
||||||
box-shadow: 0 0 0 3px rgba(13, 110, 253, 0.25);
|
box-shadow: 0 0 0 3px rgba(13, 110, 253, 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-radio:focus + .color-display {
|
.color-radio:focus + .color-display {
|
||||||
border-color: #86b7fe;
|
border-color: #86b7fe;
|
||||||
box-shadow: 0 0 0 3px rgba(13, 110, 253, 0.25);
|
box-shadow: 0 0 0 3px rgba(13, 110, 253, 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-display {
|
.color-display {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-name {
|
.color-name {
|
||||||
background-color: rgba(255, 255, 255, 0.8);
|
background-color: rgba(255, 255, 255, 0.8);
|
||||||
padding: 2px 5px;
|
padding: 2px 5px;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Added for better layout of color options */
|
/* Added for better layout of color options */
|
||||||
.color-options-container {
|
.color-options-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<form action="" method="post">
|
<form action="" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="row g-4">
|
<div class="row g-4">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
{% include "purchase_orders/partials/po-select.html" with name="make" target="model" data=make_data pk=po_model.pk %}
|
{% include "purchase_orders/partials/po-select.html" with name="make" target="model" data=make_data pk=po_model.pk %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
{% include "purchase_orders/partials/po-select.html" with name="model" target="serie" data=model_data pk=po_model.pk %}
|
{% include "purchase_orders/partials/po-select.html" with name="model" target="serie" data=model_data pk=po_model.pk %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
{% include "purchase_orders/partials/po-select.html" with name="serie" target="trim" data=serie_data pk=po_model.pk %}
|
{% include "purchase_orders/partials/po-select.html" with name="serie" target="trim" data=serie_data pk=po_model.pk %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
{% include "purchase_orders/partials/po-select.html" with name="trim" target="none" data=trim_data pk=po_model.pk %}
|
{% include "purchase_orders/partials/po-select.html" with name="trim" target="none" data=trim_data pk=po_model.pk %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row g-4">
|
||||||
|
<div class="col">
|
||||||
|
{{form.vendor.label}}
|
||||||
|
{{form.vendor}}
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
{{form.year.label}}
|
||||||
|
{{form.year}}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="row g-4">
|
|
||||||
<div class="col">
|
|
||||||
{{form.vendor.label}}
|
|
||||||
{{form.vendor}}
|
|
||||||
</div>
|
|
||||||
<div class="col">
|
|
||||||
{{form.year.label}}
|
|
||||||
{{form.year}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="account">Account</label>
|
<label for="account">Account</label>
|
||||||
<select class="form-control" name="account" id="account">
|
<select class="form-control" name="account" id="account">
|
||||||
{% for account in inventory_accounts %}
|
{% for account in inventory_accounts %}
|
||||||
<option value="{{ account.pk }}">{{ account }}"></option>
|
<option value="{{ account.pk }}">{{ account }}"></option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="row g-4 mt-4">
|
<div class="row g-4 mt-4">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<p class="fs-5 mb-2">{% trans 'Exterior Colors' %}</p>
|
<p class="fs-5 mb-2">{% trans 'Exterior Colors' %}</p>
|
||||||
<div class="color-options-container">
|
<div class="color-options-container">
|
||||||
{% for color in form.fields.exterior.queryset %}
|
{% for color in form.fields.exterior.queryset %}
|
||||||
<div class="color-card">
|
<div class="color-card">
|
||||||
<label class="color-option">
|
<label class="color-option">
|
||||||
<input class="color-radio" type="radio" name="exterior" value="{{ color.id }}" {% if color.id == form.instance.exterior.id %}checked{% endif %}>
|
<input class="color-radio" type="radio" name="exterior" value="{{ color.id }}" {% if color.id == form.instance.exterior.id %}checked{% endif %}>
|
||||||
@ -119,26 +119,26 @@
|
|||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<p class="fs-5 mb-2">{% trans 'Interior Colors' %}</p>
|
<p class="fs-5 mb-2">{% trans 'Interior Colors' %}</p>
|
||||||
<div class="color-options-container">
|
<div class="color-options-container">
|
||||||
{% for color in form.fields.interior.queryset %}
|
{% for color in form.fields.interior.queryset %}
|
||||||
<div class="color-card">
|
<div class="color-card">
|
||||||
<label class="color-option">
|
<label class="color-option">
|
||||||
<input class="color-radio" type="radio" name="interior" value="{{ color.id }}" {% if color.id == form.instance.interior.id %}checked{% endif %}>
|
<input class="color-radio" type="radio" name="interior" value="{{ color.id }}" {% if color.id == form.instance.interior.id %}checked{% endif %}>
|
||||||
<div class="color-display" style="background-color: rgb({{ color.rgb }})">
|
<div class="color-display" style="background-color: rgb({{ color.rgb }})">
|
||||||
<span class="color-name">{{ color.get_local_name }}</span>
|
<span class="color-name">{{ color.get_local_name }}</span>
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
</label>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-primary mt-5">Add New Item To Inventory</button>
|
<button type="submit" class="btn btn-primary mt-5">Add New Item To Inventory</button>
|
||||||
</form>
|
</form>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
@ -2,18 +2,18 @@
|
|||||||
{% load django_ledger %}
|
{% load django_ledger %}
|
||||||
{% load widget_tweaks %}
|
{% load widget_tweaks %}
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
window.showPOModal = function(title, actionUrl, buttonText) {
|
window.showPOModal = function(title, actionUrl, buttonText) {
|
||||||
const modalEl = document.getElementById('POModal');
|
const modalEl = document.getElementById('POModal');
|
||||||
if (!modalEl) {
|
if (!modalEl) {
|
||||||
console.error('Modal element not found');
|
console.error('Modal element not found');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const modal = bootstrap.Modal.getOrCreateInstance(modalEl);
|
const modal = bootstrap.Modal.getOrCreateInstance(modalEl);
|
||||||
document.getElementById('POModalTitle').textContent = title;
|
document.getElementById('POModalTitle').textContent = title;
|
||||||
|
|
||||||
document.getElementById('POModalBody').innerHTML = `
|
document.getElementById('POModalBody').innerHTML = `
|
||||||
<div class="d-flex justify-content-center gap-3 py-3">
|
<div class="d-flex justify-content-center gap-3 py-3">
|
||||||
<a class="btn btn-phoenix-primary px-4" href="${actionUrl}">
|
<a class="btn btn-phoenix-primary px-4" href="${actionUrl}">
|
||||||
<i class="fas fa-check-circle me-2"></i>${buttonText}
|
<i class="fas fa-check-circle me-2"></i>${buttonText}
|
||||||
@ -24,9 +24,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
modal.show();
|
modal.show();
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{% if not create_po %}
|
{% if not create_po %}
|
||||||
@ -171,7 +171,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
{# Status Action Buttons #}
|
{# Status Action Buttons #}
|
||||||
{% if po_model.can_draft %}
|
{% if po_model.can_draft %}
|
||||||
<button class="btn btn-phoenix-secondary"
|
<button class="btn btn-phoenix-secondary"
|
||||||
onclick="showPOModal('Draft PO', '{% url 'po-action-mark-as-draft' entity_slug po_model.pk %}', 'Mark As Draft')">
|
onclick="showPOModal('Draft PO', '{% url 'po-action-mark-as-draft' entity_slug po_model.pk %}', 'Mark As Draft')">
|
||||||
<i class="fas fa-file me-2"></i>{% trans 'Mark as Draft' %}
|
<i class="fas fa-file me-2"></i>{% trans 'Mark as Draft' %}
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -199,23 +199,23 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{# Danger Action Buttons #}
|
{# Danger Action Buttons #}
|
||||||
{% if po_model.can_delete %}
|
{% if po_model.can_delete %}
|
||||||
<button class="btn btn-outline-danger"
|
<button class="btn btn-outline-danger"
|
||||||
onclick="showPOModal('Cancel PO', '{% url 'po-delete' entity_slug po_model.pk %}', 'Mark As Cancelled')">
|
onclick="showPOModal('Cancel PO', '{% url 'po-delete' entity_slug po_model.pk %}', 'Mark As Cancelled')">
|
||||||
<i class="fas fa-ban me-2"></i>{% trans 'Delete' %}
|
<i class="fas fa-ban me-2"></i>{% trans 'Delete' %}
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if po_model.can_void %}
|
{% if po_model.can_void %}
|
||||||
<button class="btn btn-outline-danger"
|
<button class="btn btn-outline-danger"
|
||||||
onclick="showPOModal('Void PO', '{% url 'po-action-mark-as-void' entity_slug po_model.pk %}', 'Mark As Void')">
|
onclick="showPOModal('Void PO', '{% url 'po-action-mark-as-void' entity_slug po_model.pk %}', 'Mark As Void')">
|
||||||
<i class="fas fa-times-circle me-2"></i>{% trans 'Void' %}
|
<i class="fas fa-times-circle me-2"></i>{% trans 'Void' %}
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if po_model.can_cancel %}
|
{% if po_model.can_cancel %}
|
||||||
<button class="btn btn-outline-danger"
|
<button class="btn btn-outline-danger"
|
||||||
onclick="showPOModal('Cancel PO', '{% url 'po-action-mark-as-canceled' entity_slug po_model.pk %}', 'Mark As Cancelled')">
|
onclick="showPOModal('Cancel PO', '{% url 'po-action-mark-as-canceled' entity_slug po_model.pk %}', 'Mark As Cancelled')">
|
||||||
<i class="fas fa-ban me-2"></i>{% trans 'Cancel' %}
|
<i class="fas fa-ban me-2"></i>{% trans 'Cancel' %}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
|||||||
@ -3,25 +3,25 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<form action="{% url 'inventory_item_create' po_model.pk %}" method="post">
|
<form action="{% url 'inventory_item_create' po_model.pk %}" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% include "purchase_orders/partials/po-select.html" with name="make" target="model" data=make_data pk=po_model.pk %}
|
{% include "purchase_orders/partials/po-select.html" with name="make" target="model" data=make_data pk=po_model.pk %}
|
||||||
{% include "purchase_orders/partials/po-select.html" with name="model" target="serie" data=model_data pk=po_model.pk %}
|
{% include "purchase_orders/partials/po-select.html" with name="model" target="serie" data=model_data pk=po_model.pk %}
|
||||||
{% include "purchase_orders/partials/po-select.html" with name="serie" target="trim" data=serie_data pk=po_model.pk %}
|
{% include "purchase_orders/partials/po-select.html" with name="serie" target="trim" data=serie_data pk=po_model.pk %}
|
||||||
{% include "purchase_orders/partials/po-select.html" with name="trim" target="none" data=trim_data pk=po_model.pk %}
|
{% include "purchase_orders/partials/po-select.html" with name="trim" target="none" data=trim_data pk=po_model.pk %}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="account">Account</label>
|
<label for="account">Account</label>
|
||||||
<select class="form-control" name="account" id="account">
|
<select class="form-control" name="account" id="account">
|
||||||
{% for account in inventory_accounts %}
|
{% for account in inventory_accounts %}
|
||||||
<option value="{{ account.pk }}">{{ account }}"></option>
|
<option value="{{ account.pk }}">{{ account }}"></option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="quantity">Quantity</label>
|
<label for="quantity">Quantity</label>
|
||||||
<input type="number" class="form-control" id="quantity" name="quantity" required>
|
<input type="number" class="form-control" id="quantity" name="quantity" required>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-phoenix-primary">Add New Item To Inventory</button>
|
<button type="submit" class="btn btn-phoenix-primary">Add New Item To Inventory</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
@ -3,21 +3,21 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container-fluid mt-4">
|
<div class="container-fluid mt-4">
|
||||||
<form action="" method="post">
|
<form action="" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="account">Name</label>
|
<label for="account">Name</label>
|
||||||
<input type="text" class="form-control" id="name" name="name" required>
|
<input type="text" class="form-control" id="name" name="name" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="account">Account</label>
|
||||||
|
<select class="form-control" name="account" id="account">
|
||||||
|
{% for account in inventory_accounts %}
|
||||||
|
<option value="{{ account.pk }}">{{ account }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-phoenix-primary mt-2">Add New Item To Inventory</button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
|
||||||
<label for="account">Account</label>
|
|
||||||
<select class="form-control" name="account" id="account">
|
|
||||||
{% for account in inventory_accounts %}
|
|
||||||
<option value="{{ account.pk }}">{{ account }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-phoenix-primary mt-2">Add New Item To Inventory</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
@ -1,16 +1,16 @@
|
|||||||
<div class="form-group" id="form-{{name}}">
|
<div class="form-group" id="form-{{name}}">
|
||||||
<label for="{{name}}">{{name|capfirst}}</label>
|
<label for="{{name}}">{{name|capfirst}}</label>
|
||||||
<select class="form-control"
|
<select class="form-control"
|
||||||
name="{{name}}" id="{{name}}"
|
name="{{name}}" id="{{name}}"
|
||||||
{% if name != "trim" %}
|
{% if name != "trim" %}
|
||||||
hx-get="{% url 'inventory_items_filter' %}"
|
hx-get="{% url 'inventory_items_filter' %}"
|
||||||
hx-target="#form-{{target}}"
|
hx-target="#form-{{target}}"
|
||||||
hx-select="#form-{{target}}"
|
hx-select="#form-{{target}}"
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
{% endif %}
|
{% endif %}
|
||||||
>
|
>
|
||||||
{% for item in data %}
|
{% for item in data %}
|
||||||
<option value="{{ item.pk }}">{{ item.name }}</option>
|
<option value="{{ item.pk }}">{{ item.name }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@ -6,14 +6,14 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container mt-4">
|
<div class="container mt-4">
|
||||||
<h2>Confirm Deletion</h2>
|
<h2>Confirm Deletion</h2>
|
||||||
<p>Are you sure you want to delete the Purchase Order <strong>"{{ po.po_number }}"</strong>?</p>
|
<p>Are you sure you want to delete the Purchase Order <strong>"{{ po.po_number }}"</strong>?</p>
|
||||||
|
|
||||||
<form method="post">
|
<form method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<button type="submit" class="btn btn-phoenix-danger">Yes, Delete</button>
|
<button type="submit" class="btn btn-phoenix-danger">Yes, Delete</button>
|
||||||
<a href="{% url 'purchase_order_detail' po.id %}" class="btn btn-phoenix-secondary">Cancel</a>
|
<a href="{% url 'purchase_order_detail' po.id %}" class="btn btn-phoenix-secondary">Cancel</a>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -5,84 +5,84 @@
|
|||||||
{% load django_ledger %}
|
{% load django_ledger %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container-fluid mt-4">
|
<div class="container-fluid mt-4">
|
||||||
<div class="row g-4">
|
<div class="row g-4">
|
||||||
<!-- Left Sidebar -->
|
<!-- Left Sidebar -->
|
||||||
<div class="col-lg-4">
|
<div class="col-lg-4">
|
||||||
<div class="d-flex flex-column gap-3">
|
<div class="d-flex flex-column gap-3">
|
||||||
<!-- PO Card -->
|
<!-- PO Card -->
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
{% include 'purchase_orders/includes/card_po.html' with po_model=po_model entity_slug=entity_slug style='po-detail' %}
|
{% include 'purchase_orders/includes/card_po.html' with po_model=po_model entity_slug=entity_slug style='po-detail' %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- PO List Button -->
|
<!-- PO List Button -->
|
||||||
<a class="btn btn-phoenix-primary w-100 py-2"
|
<a class="btn btn-phoenix-primary w-100 py-2"
|
||||||
href="{% url 'purchase_order_list' %}">
|
href="{% url 'purchase_order_list' %}">
|
||||||
<i class="fas fa-list me-2"></i>{% trans 'PO List' %}
|
<i class="fas fa-list me-2"></i>{% trans 'PO List' %}
|
||||||
</a>
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Main Content -->
|
<!-- Main Content -->
|
||||||
<div class="col-lg-8">
|
<div class="col-lg-8">
|
||||||
<!-- Stats Cards -->
|
<!-- Stats Cards -->
|
||||||
<div class="card mb-4">
|
<div class="card mb-4">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="row text-center">
|
<div class="row text-center">
|
||||||
<div class="col-md-6 border-end">
|
<div class="col-md-6 border-end">
|
||||||
<div class="p-3">
|
<div class="p-3">
|
||||||
<h6 class="text-muted mb-2">{% trans 'PO Amount' %}</h6>
|
<h6 class="text-muted mb-2">{% trans 'PO Amount' %}</h6>
|
||||||
<h3 class="fw-light mb-0">
|
<h3 class="fw-light mb-0">
|
||||||
{% currency_symbol %}{{ po_model.po_amount | absolute | currency_format }}
|
{% currency_symbol %}{{ po_model.po_amount | absolute | currency_format }}
|
||||||
</h3>
|
</h3>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="col-md-6">
|
||||||
<div class="col-md-6">
|
<div class="p-3">
|
||||||
<div class="p-3">
|
<h6 class="text-muted mb-2">{% trans 'Amount Received' %}</h6>
|
||||||
<h6 class="text-muted mb-2">{% trans 'Amount Received' %}</h6>
|
<h3 class="fw-light mb-0 text-success">
|
||||||
<h3 class="fw-light mb-0 text-success">
|
{% currency_symbol %}{{ po_model.po_amount_received | currency_format }}
|
||||||
{% currency_symbol %}{{ po_model.po_amount_received | currency_format }}
|
</h3>
|
||||||
</h3>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- PO Details -->
|
<!-- PO Details -->
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h3 class="h4 fw-light mb-4">{{ po_model.po_title }}</h3>
|
<h3 class="h4 fw-light mb-4">{{ po_model.po_title }}</h3>
|
||||||
|
|
||||||
<!-- PO Items Table -->
|
<!-- PO Items Table -->
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
{% po_item_table1 po_items %}
|
{% po_item_table1 po_items %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{% include "purchase_orders/includes/mark_as.html" %}
|
{% include "purchase_orders/includes/mark_as.html" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block customJS %}
|
{% block customJS %}
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
window.showPOModal = function(title, actionUrl, buttonText) {
|
window.showPOModal = function(title, actionUrl, buttonText) {
|
||||||
const modalEl = document.getElementById('POModal');
|
const modalEl = document.getElementById('POModal');
|
||||||
if (!modalEl) {
|
if (!modalEl) {
|
||||||
console.error('Modal element not found');
|
console.error('Modal element not found');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const modal = bootstrap.Modal.getOrCreateInstance(modalEl);
|
const modal = bootstrap.Modal.getOrCreateInstance(modalEl);
|
||||||
document.getElementById('POModalTitle').textContent = title;
|
document.getElementById('POModalTitle').textContent = title;
|
||||||
|
|
||||||
document.getElementById('POModalBody').innerHTML = `
|
document.getElementById('POModalBody').innerHTML = `
|
||||||
<div class="d-flex justify-content-center gap-3">
|
<div class="d-flex justify-content-center gap-3">
|
||||||
<a class="btn btn-phoenix-primary px-4" href="${actionUrl}">
|
<a class="btn btn-phoenix-primary px-4" href="${actionUrl}">
|
||||||
<i class="fas fa-check-circle me-2"></i>${buttonText}
|
<i class="fas fa-check-circle me-2"></i>${buttonText}
|
||||||
@ -93,8 +93,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
modal.show();
|
modal.show();
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock customJS %}
|
{% endblock customJS %}
|
||||||
@ -6,90 +6,90 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container mt-4">
|
<div class="container mt-4">
|
||||||
<h2>Purchase Order: {{ po.po_number }}</h2>
|
<h2>Purchase Order: {{ po.po_number }}</h2>
|
||||||
<p><strong>Status:</strong>
|
<p><strong>Status:</strong>
|
||||||
<span class="">
|
<span class="">
|
||||||
{{ po.po_status }}
|
{{ po.po_status }}
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<dl class="row">
|
<dl class="row">
|
||||||
<dt class="col-sm-4">Supplier</dt>
|
<dt class="col-sm-4">Supplier</dt>
|
||||||
<dd class="col-sm-8">{{ po.supplier.name }}</dd>
|
<dd class="col-sm-8">{{ po.supplier.name }}</dd>
|
||||||
|
|
||||||
<dt class="col-sm-4">Created At</dt>
|
<dt class="col-sm-4">Created At</dt>
|
||||||
<dd class="col-sm-8">{{ po.created|date:"M d, Y H:i" }}</dd>
|
<dd class="col-sm-8">{{ po.created|date:"M d, Y H:i" }}</dd>
|
||||||
|
|
||||||
|
<dt class="col-sm-4">Total Amount</dt>
|
||||||
|
<dd class="col-sm-8">${{ po.total_amount|floatformat:2 }}</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4 class="mt-4">Ordered Items</h4>
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Item Number</th>
|
||||||
|
<th>Item</th>
|
||||||
|
<th>Unit of Measure</th>
|
||||||
|
<th>Account</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for item in inventory_items %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ item.item_number }}</td>
|
||||||
|
<td>{{ item.name }}</td>
|
||||||
|
<td>${{ item.uom.name }}</td>
|
||||||
|
<td>${{ item.inventory_account }}</td>
|
||||||
|
</tr>
|
||||||
|
{% empty %}
|
||||||
|
<tr><td colspan="4" class="text-center">No items found.</td></tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="mt-3">
|
||||||
|
|
||||||
|
|
||||||
|
<button class="btn btn-phoenix-primary" data-bs-toggle="modal" data-bs-target="#POModal"><span class="d-none d-sm-inline-block"><i class="fa-solid fa-receipt"></i> {% trans 'View Purchase Order' %}</span></button>
|
||||||
|
<a href="{% url 'purchase_order_list' %}" class="btn btn-phoenix-secondary">Back to List</a>
|
||||||
|
|
||||||
<dt class="col-sm-4">Total Amount</dt>
|
|
||||||
<dd class="col-sm-8">${{ po.total_amount|floatformat:2 }}</dd>
|
|
||||||
</dl>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="modal fade" id="POModal" data-bs-keyboard="true" tabindex="-1" aria-labelledby="POModalLabel" aria-hidden="true">
|
||||||
<h4 class="mt-4">Ordered Items</h4>
|
<div class="modal-dialog modal-lg">
|
||||||
<table class="table table-bordered">
|
<div class="modal-content">
|
||||||
<thead>
|
<div class="modal-header justify-content-between align-items-start gap-5 px-4 pt-4 pb-3 border-0">
|
||||||
<tr>
|
<h4 class="mb-0 me-2 text-primary"><i class="fas text-info ms-2"></i></h4>
|
||||||
<th>Item Number</th>
|
<button class="btn p-0 text-body-quaternary fs-6" data-bs-dismiss="modal" aria-label="Close"><span class="fas fa-times"></span></button>
|
||||||
<th>Item</th>
|
</div>
|
||||||
<th>Unit of Measure</th>
|
<div class="modal-body p-4">
|
||||||
<th>Account</th>
|
<form action="{% url 'inventory_item_create' po.pk %}" method="post">
|
||||||
</tr>
|
{% csrf_token %}
|
||||||
</thead>
|
{% include "purchase_orders/partials/po-select.html" with name="make" target="model" data=make_data pk=po.pk %}
|
||||||
<tbody>
|
{% include "purchase_orders/partials/po-select.html" with name="model" target="serie" data=model_data pk=po.pk %}
|
||||||
{% for item in inventory_items %}
|
{% include "purchase_orders/partials/po-select.html" with name="serie" target="trim" data=serie_data pk=po.pk %}
|
||||||
<tr>
|
{% include "purchase_orders/partials/po-select.html" with name="trim" target="none" data=trim_data pk=po.pk %}
|
||||||
<td>{{ item.item_number }}</td>
|
<div class="form-group">
|
||||||
<td>{{ item.name }}</td>
|
<label for="account">Account</label>
|
||||||
<td>${{ item.uom.name }}</td>
|
<select class="form-control" name="account" id="account">
|
||||||
<td>${{ item.inventory_account }}</td>
|
{% for account in inventory_accounts %}
|
||||||
</tr>
|
<option value="{{ account.pk }}">{{ account }}"></option>
|
||||||
{% empty %}
|
{% endfor %}
|
||||||
<tr><td colspan="4" class="text-center">No items found.</td></tr>
|
</select>
|
||||||
{% endfor %}
|
</div>
|
||||||
</tbody>
|
<div class="form-group">
|
||||||
</table>
|
<label for="quantity">Quantity</label>
|
||||||
|
<input type="number" class="form-control" id="quantity" name="quantity" required>
|
||||||
<div class="mt-3">
|
</div>
|
||||||
|
<button type="submit" class="btn btn-phoenix-primary">Add New Item To Inventory</button>
|
||||||
|
</form>
|
||||||
<button class="btn btn-phoenix-primary" data-bs-toggle="modal" data-bs-target="#POModal"><span class="d-none d-sm-inline-block"><i class="fa-solid fa-receipt"></i> {% trans 'View Purchase Order' %}</span></button>
|
</div>
|
||||||
<a href="{% url 'purchase_order_list' %}" class="btn btn-phoenix-secondary">Back to List</a>
|
</div>
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal fade" id="POModal" data-bs-keyboard="true" tabindex="-1" aria-labelledby="POModalLabel" aria-hidden="true">
|
|
||||||
<div class="modal-dialog modal-lg">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header justify-content-between align-items-start gap-5 px-4 pt-4 pb-3 border-0">
|
|
||||||
<h4 class="mb-0 me-2 text-primary"><i class="fas text-info ms-2"></i></h4>
|
|
||||||
<button class="btn p-0 text-body-quaternary fs-6" data-bs-dismiss="modal" aria-label="Close"><span class="fas fa-times"></span></button>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body p-4">
|
|
||||||
<form action="{% url 'inventory_item_create' po.pk %}" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
{% include "purchase_orders/partials/po-select.html" with name="make" target="model" data=make_data pk=po.pk %}
|
|
||||||
{% include "purchase_orders/partials/po-select.html" with name="model" target="serie" data=model_data pk=po.pk %}
|
|
||||||
{% include "purchase_orders/partials/po-select.html" with name="serie" target="trim" data=serie_data pk=po.pk %}
|
|
||||||
{% include "purchase_orders/partials/po-select.html" with name="trim" target="none" data=trim_data pk=po.pk %}
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="account">Account</label>
|
|
||||||
<select class="form-control" name="account" id="account">
|
|
||||||
{% for account in inventory_accounts %}
|
|
||||||
<option value="{{ account.pk }}">{{ account }}"></option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="quantity">Quantity</label>
|
|
||||||
<input type="number" class="form-control" id="quantity" name="quantity" required>
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-phoenix-primary">Add New Item To Inventory</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -3,11 +3,11 @@
|
|||||||
{% load crispy_forms_filters %}
|
{% load crispy_forms_filters %}
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{# Check if an 'object' exists in the context #}
|
{# Check if an 'object' exists in the context #}
|
||||||
{% if object %}
|
{% if object %}
|
||||||
{% trans 'Update Vendor'%}
|
{% trans 'Update Vendor'%}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% trans 'Add New Vendor'%}
|
{% trans 'Add New Vendor'%}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|||||||
@ -8,10 +8,10 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row mt-4">
|
<div class="row mt-4">
|
||||||
<!-- Success Message -->
|
<!-- Success Message -->
|
||||||
{% if messages %}
|
{% if messages %}
|
||||||
{% for message in messages %}
|
{% for message in messages %}
|
||||||
<div class="alert alert-success">{{ message }}</div>
|
<div class="alert alert-success">{{ message }}</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<!-- Add New PO Button -->
|
<!-- Add New PO Button -->
|
||||||
<div class="d-flex justify-content-between mb-2">
|
<div class="d-flex justify-content-between mb-2">
|
||||||
@ -19,76 +19,76 @@
|
|||||||
{{ _("Purchase Orders") |capfirst }}
|
{{ _("Purchase Orders") |capfirst }}
|
||||||
</h2>
|
</h2>
|
||||||
<div>
|
<div>
|
||||||
<a href="{% url 'purchase_order_create' %}"
|
<a href="{% url 'purchase_order_create' %}"
|
||||||
class="btn btn-md btn-phoenix-primary"><i class="fa fa-plus me-2"></i>{{ _("Create New PO") }}</a>
|
class="btn btn-md btn-phoenix-primary"><i class="fa fa-plus me-2"></i>{{ _("Create New PO") }}</a>
|
||||||
<a href="{% url 'inventory_item_create' %}?for_po=1"
|
<a href="{% url 'inventory_item_create' %}?for_po=1"
|
||||||
class="btn btn-md btn-phoenix-primary"><i class="fa fa-plus me-2"></i>{{ _("Create Inventory Item for PO") }}</a>
|
class="btn btn-md btn-phoenix-primary"><i class="fa fa-plus me-2"></i>{{ _("Create Inventory Item for PO") }}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% include "partials/search_box.html" %}
|
{% include "partials/search_box.html" %}
|
||||||
|
|
||||||
<div class="table-responsive px-1 scrollbar mt-3">
|
<div class="table-responsive px-1 scrollbar mt-3">
|
||||||
<table class= "table align-items-center table-flush table-hover">
|
<table class= "table align-items-center table-flush table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="bg-body-highlight">
|
<tr class="bg-body-highlight">
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col" style="width:15%">PO Number</th>
|
<th class="sort white-space-nowrap align-middle" scope="col" style="width:15%">PO Number</th>
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col" style="width:40%">Description</th>
|
<th class="sort white-space-nowrap align-middle" scope="col" style="width:40%">Description</th>
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col" style="width:15%">Status</th>
|
<th class="sort white-space-nowrap align-middle" scope="col" style="width:15%">Status</th>
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col" style="width:15%">Created At</th>
|
<th class="sort white-space-nowrap align-middle" scope="col" style="width:15%">Created At</th>
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col" style="width:15%">Actions</th>
|
<th class="sort white-space-nowrap align-middle" scope="col" style="width:15%">Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="list">
|
<tbody class="list">
|
||||||
{% if purchase_orders %}
|
{% if purchase_orders %}
|
||||||
{% for po in purchase_orders %}
|
{% for po in purchase_orders %}
|
||||||
<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">{{ po.po_number }}</td>
|
<td class="align-middle product white-space-nowrap">{{ po.po_number }}</td>
|
||||||
<td class="align-middle product white-space-nowrap">{{ po.po_title }}</td>
|
<td class="align-middle product white-space-nowrap">{{ po.po_title }}</td>
|
||||||
<td class="align-middle product white-space-nowrap">
|
<td class="align-middle product white-space-nowrap">
|
||||||
<span class="">
|
<span class="">
|
||||||
{% if po.po_status == 'draft' %}
|
{% if po.po_status == 'draft' %}
|
||||||
<span class="text-warning me-2" data-feather="file"></span>
|
<span class="text-warning me-2" data-feather="file"></span>
|
||||||
{% elif po.po_status == 'in_review' %}
|
{% elif po.po_status == 'in_review' %}
|
||||||
<span class="text-info me-2" data-feather="search"></span>
|
<span class="text-info me-2" data-feather="search"></span>
|
||||||
{% elif po.po_status == 'paid' %}
|
{% elif po.po_status == 'paid' %}
|
||||||
<span class="text-success me-2" data-feather="dollar-sign"></span>
|
<span class="text-success me-2" data-feather="dollar-sign"></span>
|
||||||
{% elif po.po_status == 'canceled' %}
|
{% elif po.po_status == 'canceled' %}
|
||||||
<span class="text-danger me-2" data-feather="x-circle"></span>
|
<span class="text-danger me-2" data-feather="x-circle"></span>
|
||||||
{% elif po.po_status == 'fulfilled' %}
|
{% elif po.po_status == 'fulfilled' %}
|
||||||
<span class="text-success me-2" data-feather="check-circle"></span>
|
<span class="text-success me-2" data-feather="check-circle"></span>
|
||||||
{% elif po.po_status == 'approved' %}
|
{% elif po.po_status == 'approved' %}
|
||||||
<span class="text-success me-2" data-feather="thumbs-up"></span>
|
<span class="text-success me-2" data-feather="thumbs-up"></span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{{ po.po_status|capfirst }}
|
{{ po.po_status|capfirst }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle product white-space-nowrap">{{ po.created|date:"M d, Y" }}</td>
|
<td class="align-middle product white-space-nowrap">{{ po.created|date:"M d, Y" }}</td>
|
||||||
<td class="align-middle white-space-nowrap">
|
<td class="align-middle white-space-nowrap">
|
||||||
<div class="btn-reveal-trigger position-static">
|
<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>
|
<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">
|
<div class="dropdown-menu dropdown-menu-end py-2">
|
||||||
<a href="{% url 'purchase_order_detail' po.pk %}" class="dropdown-item text-success-dark">{% trans 'Detail' %}</a>
|
<a href="{% url 'purchase_order_detail' po.pk %}" class="dropdown-item text-success-dark">{% trans 'Detail' %}</a>
|
||||||
<a href="{% url 'view_items_inventory' entity_slug=entity_slug po_pk=po.pk %}" class="dropdown-item text-success-dark">{% trans 'View Inventory Items' %}</a>
|
<a href="{% url 'view_items_inventory' entity_slug=entity_slug po_pk=po.pk %}" class="dropdown-item text-success-dark">{% trans 'View Inventory Items' %}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{%endfor%}
|
{%endfor%}
|
||||||
{% else%}
|
{% else%}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="6" class="text-center">No purchase orders found.</td>
|
<td colspan="6" class="text-center">No purchase orders found.</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex justify-content-end mt-3">
|
|
||||||
<div class="d-flex">
|
|
||||||
{% if is_paginated %}
|
|
||||||
{% include 'partials/pagination.html' %}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-end mt-3">
|
||||||
|
<div class="d-flex">
|
||||||
|
{% if is_paginated %}
|
||||||
|
{% include 'partials/pagination.html' %}
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% include 'modal/delete_modal.html' %}
|
{% include 'modal/delete_modal.html' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -29,8 +29,8 @@
|
|||||||
<a href="{% url 'purchase_order_detail' po_model.uuid %}"
|
<a href="{% url 'purchase_order_detail' po_model.uuid %}"
|
||||||
class="btn btn-phoenix-secondary w-100 my-2">{% trans 'Back to PO Detail' %}</a>
|
class="btn btn-phoenix-secondary w-100 my-2">{% trans 'Back to PO Detail' %}</a>
|
||||||
<a href="{% url 'purchase_order_list' %}"
|
<a href="{% url 'purchase_order_list' %}"
|
||||||
class="btn btn-phoenix-info
|
class="btn btn-phoenix-info
|
||||||
info w-100 my-2">{% trans 'PO List' %}</a>
|
info w-100 my-2">{% trans 'PO List' %}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@ -49,30 +49,30 @@
|
|||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-striped table-bordered">
|
<table class="table table-striped table-bordered">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans 'Item' %}</th>
|
<th>{% trans 'Item' %}</th>
|
||||||
<th>{% trans 'Quantity' %}</th>
|
<th>{% trans 'Quantity' %}</th>
|
||||||
<th>{% trans 'Avg Unit Price' %}</th>
|
<th>{% trans 'Avg Unit Price' %}</th>
|
||||||
<th>{% trans 'Total Contracted Cost' %}</th>
|
<th>{% trans 'Total Contracted Cost' %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tfoot>
|
<tfoot>
|
||||||
<tr>
|
<tr>
|
||||||
<th></th>
|
<th></th>
|
||||||
<th></th>
|
<th></th>
|
||||||
<th></th>
|
<th></th>
|
||||||
<th>{% currency_symbol %}{{ ce_cost_estimate__sum | currency_format }}</th>
|
<th>{% currency_symbol %}{{ ce_cost_estimate__sum | currency_format }}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</tfoot>
|
</tfoot>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for i in ce_itemtxs_agg %}
|
{% for i in ce_itemtxs_agg %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ i.item_model__name }}</td>
|
<td>{{ i.item_model__name }}</td>
|
||||||
<td>{{ i.ce_quantity__sum }}</td>
|
<td>{{ i.ce_quantity__sum }}</td>
|
||||||
<td>{% currency_symbol %}{{ i.avg_unit_cost | currency_format }}</td>
|
<td>{% currency_symbol %}{{ i.avg_unit_cost | currency_format }}</td>
|
||||||
<td>{% currency_symbol %}{{ i.ce_cost_estimate__sum | currency_format }}</td>
|
<td>{% currency_symbol %}{{ i.ce_cost_estimate__sum | currency_format }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@ -93,18 +93,18 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block customJS %}
|
{% block customJS %}
|
||||||
<script>
|
<script>
|
||||||
function showPOModal(title, actionUrl, buttonText) {
|
function showPOModal(title, actionUrl, buttonText) {
|
||||||
const modal = new bootstrap.Modal(document.getElementById('POModal'));
|
const modal = new bootstrap.Modal(document.getElementById('POModal'));
|
||||||
document.getElementById('POModalTitle').innerText = title;
|
document.getElementById('POModalTitle').innerText = title;
|
||||||
|
|
||||||
// Set the modal body content
|
// Set the modal body content
|
||||||
document.getElementById('POModalBody').innerHTML = `
|
document.getElementById('POModalBody').innerHTML = `
|
||||||
<a class="btn btn-phoenix-primary" href="${actionUrl}">
|
<a class="btn btn-phoenix-primary" href="${actionUrl}">
|
||||||
${buttonText}
|
${buttonText}
|
||||||
</a>
|
</a>
|
||||||
`;
|
`;
|
||||||
modal.show();
|
modal.show();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
{% endblock customJS %}
|
{% endblock customJS %}
|
||||||
@ -1,60 +1,60 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<h1><i class="fa-solid fa-cart-shopping me-1"></i>{{po.po_number}}</h1>
|
<h1><i class="fa-solid fa-cart-shopping me-1"></i>{{po.po_number}}</h1>
|
||||||
|
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<h4>Status:</h4>
|
<h4>Status:</h4>
|
||||||
{% comment %} Apply appropriate text color based on po.po_status {% endcomment %}
|
{% comment %} Apply appropriate text color based on po.po_status {% endcomment %}
|
||||||
{% if po.po_status == 'draft' %}
|
{% if po.po_status == 'draft' %}
|
||||||
<h4 class="ms-2 text-warning mb-0">{{ po.po_status|capfirst }}</h4>
|
<h4 class="ms-2 text-warning mb-0">{{ po.po_status|capfirst }}</h4>
|
||||||
{% elif po.po_status == 'in_review' %}
|
{% elif po.po_status == 'in_review' %}
|
||||||
<h4 class="ms-2 text-primary mb-0">{{ po.po_status|capfirst }}</h4>
|
<h4 class="ms-2 text-primary mb-0">{{ po.po_status|capfirst }}</h4>
|
||||||
{% elif po.po_status == 'approved' %}
|
{% elif po.po_status == 'approved' %}
|
||||||
<h4 class="ms-2 text-info mb-0">{{ po.po_status|capfirst }}</h4>
|
<h4 class="ms-2 text-info mb-0">{{ po.po_status|capfirst }}</h4>
|
||||||
{% elif po.po_status == 'fulfilled' %}
|
{% elif po.po_status == 'fulfilled' %}
|
||||||
<h4 class="ms-2 text-success mb-0">{{ po.po_status|capfirst }}</h4>
|
<h4 class="ms-2 text-success mb-0">{{ po.po_status|capfirst }}</h4>
|
||||||
{% elif po.po_status == 'void' %}
|
{% elif po.po_status == 'void' %}
|
||||||
<h4 class="ms-2 text-danger mb-0">{{ po.po_status|capfirst }}</h4>
|
<h4 class="ms-2 text-danger mb-0">{{ po.po_status|capfirst }}</h4>
|
||||||
{% else %}
|
{% else %}
|
||||||
<h4 class="ms-2 text-muted mb-0">{{ po.po_status|capfirst }}</h4> {# Use muted for unknown/default status #}
|
<h4 class="ms-2 text-muted mb-0">{{ po.po_status|capfirst }}</h4> {# Use muted for unknown/default status #}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="table-responsive mt-3">
|
|
||||||
<table class="table table-striped table-hover align-middle">
|
|
||||||
<thead>
|
<div class="table-responsive mt-3">
|
||||||
<tr class="bg-body-highlight">
|
<table class="table table-striped table-hover align-middle">
|
||||||
<th scope="col">Name</th>
|
<thead>
|
||||||
<th scope="col">Quatnity</th>
|
<tr class="bg-body-highlight">
|
||||||
<th scope="col">Unit Cost</th>
|
<th scope="col">Name</th>
|
||||||
<th scope="col">Is Data Uploaded ?</th>
|
<th scope="col">Quatnity</th>
|
||||||
</tr>
|
<th scope="col">Unit Cost</th>
|
||||||
</thead>
|
<th scope="col">Is Data Uploaded ?</th>
|
||||||
<tbody>
|
</tr>
|
||||||
{% for item in items %}
|
</thead>
|
||||||
<tr>
|
<tbody>
|
||||||
<th scope="row">{{ item.item_model }}</th>
|
{% for item in items %}
|
||||||
<th scope="row">{{ item.po_quantity }}</th>
|
<tr>
|
||||||
<th scope="row">{{ item.po_unit_cost }}</th>
|
<th scope="row">{{ item.item_model }}</th>
|
||||||
<th scope="row">
|
<th scope="row">{{ item.po_quantity }}</th>
|
||||||
{% if item.item_model.additional_info.uploaded %}
|
<th scope="row">{{ item.po_unit_cost }}</th>
|
||||||
<i data-feather="check-circle" class="text-success"></i>
|
<th scope="row">
|
||||||
{% else %}
|
{% if item.item_model.additional_info.uploaded %}
|
||||||
<a href="{% url 'upload_cars' item.pk %}" class="btn btn-sm btn-phoenix-primary">
|
<i data-feather="check-circle" class="text-success"></i>
|
||||||
<i data-feather="upload" class="me-2"></i>Upload Data
|
{% else %}
|
||||||
</a>
|
<a href="{% url 'upload_cars' item.pk %}" class="btn btn-sm btn-phoenix-primary">
|
||||||
{% endif %}
|
<i data-feather="upload" class="me-2"></i>Upload Data
|
||||||
</th>
|
</a>
|
||||||
</tr>
|
{% endif %}
|
||||||
{% endfor %}
|
</th>
|
||||||
</tbody>
|
</tr>
|
||||||
</table>
|
{% endfor %}
|
||||||
</div>
|
</tbody>
|
||||||
<div>
|
</table>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
@ -60,11 +60,11 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex justify-content-end mt-3">
|
<div class="d-flex justify-content-end mt-3">
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
{% if is_paginated %}
|
{% if is_paginated %}
|
||||||
{% include 'partials/pagination.html' %}
|
{% include 'partials/pagination.html' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
{% load i18n static %}
|
{% load i18n static %}
|
||||||
{% load crispy_forms_filters %}
|
{% load crispy_forms_filters %}
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{% trans 'Sale Order' %}
|
{% trans 'Sale Order' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block customCSS %}
|
{% block customCSS %}
|
||||||
@ -68,146 +68,146 @@
|
|||||||
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.1);
|
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock customCSS %}
|
{% endblock customCSS %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col-lg-10">
|
<div class="col-lg-10">
|
||||||
<div class="card shadow">
|
<div class="card shadow">
|
||||||
<div class="card-header bg-primary text-white">
|
<div class="card-header bg-primary text-white">
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
<h2 class="h4 mb-0">
|
<h2 class="h4 mb-0">
|
||||||
<i class="fas fa-file-invoice me-2"></i> New Sale Order
|
<i class="fas fa-file-invoice me-2"></i> New Sale Order
|
||||||
</h2>
|
</h2>
|
||||||
<div>
|
<div>
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form id="saleOrderForm" method="post">
|
<form id="saleOrderForm" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<!-- Basic Information Section -->
|
<!-- Basic Information Section -->
|
||||||
<div class="form-section">
|
<div class="form-section">
|
||||||
<h4 class="form-section-header">
|
<h4 class="form-section-header">
|
||||||
<i class="fas fa-info-circle me-2"></i> Basic Information
|
<i class="fas fa-info-circle me-2"></i> Basic Information
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
<!-- Estimate -->
|
<!-- Estimate -->
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
{{form.estimate|as_crispy_field}}
|
{{form.estimate|as_crispy_field}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Opportunity -->
|
<!-- Opportunity -->
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
{{form.opportunity|as_crispy_field}}
|
{{form.opportunity|as_crispy_field}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Customer -->
|
<!-- Customer -->
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
{% if form.customer %}
|
{% if form.customer %}
|
||||||
{{form.customer}}
|
{{form.customer}}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Vehicle -->
|
<!-- Vehicle -->
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label for="car" class="form-label required-field">Vehicles</label>
|
<label for="car" class="form-label required-field">Vehicles</label>
|
||||||
<ul class="list-group">
|
<ul class="list-group">
|
||||||
{% for car in data.cars %}
|
{% for car in data.cars %}
|
||||||
<li class="list-group-item d-flex justify-content-around align-items-center">
|
<li class="list-group-item d-flex justify-content-around align-items-center">
|
||||||
<span class="badge bg-info rounded-pill">{{ car.make }}</span>
|
<span class="badge bg-info rounded-pill">{{ car.make }}</span>
|
||||||
<span class="badge bg-info rounded-pill">{{ car.model }}</span>
|
<span class="badge bg-info rounded-pill">{{ car.model }}</span>
|
||||||
<span class="badge bg-info rounded-pill">{{ car.year }}</span>
|
<span class="badge bg-info rounded-pill">{{ car.year }}</span>
|
||||||
<span class="badge bg-info rounded-pill">{{ car.vin }}</span>
|
<span class="badge bg-info rounded-pill">{{ car.vin }}</span>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Payment Method -->
|
<!-- Payment Method -->
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
{{form.payment_method|as_crispy_field}}
|
{{form.payment_method|as_crispy_field}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Status -->
|
<!-- Status -->
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
{{form.status|as_crispy_field}}
|
{{form.status|as_crispy_field}}
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Financial Details Section -->
|
<!-- Financial Details Section -->
|
||||||
<div class="form-section">
|
<div class="form-section">
|
||||||
<h4 class="form-section-header">
|
<h4 class="form-section-header">
|
||||||
<i class="fas fa-money-bill-wave me-2"></i> Financial Details
|
<i class="fas fa-money-bill-wave me-2"></i> Financial Details
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
<!-- Agreed Price -->
|
<!-- Agreed Price -->
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
{{form.agreed_price|as_crispy_field}}
|
{{form.agreed_price|as_crispy_field}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Down Payment Amount -->
|
<!-- Down Payment Amount -->
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="currency-input">
|
<div class="currency-input">
|
||||||
{{form.down_payment_amount|as_crispy_field}}
|
{{form.down_payment_amount|as_crispy_field}}
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<!-- Loan Amount -->
|
|
||||||
<div class="col-md-6">
|
|
||||||
{{form.loan_amount|as_crispy_field}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Loan Amount -->
|
||||||
|
<div class="col-md-6">
|
||||||
|
{{form.loan_amount|as_crispy_field}}
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Delivery Information Section -->
|
<!-- Delivery Information Section -->
|
||||||
<div class="form-section">
|
<div class="form-section">
|
||||||
<h4 class="form-section-header">
|
<h4 class="form-section-header">
|
||||||
<i class="fas fa-truck me-2"></i> Delivery Information
|
<i class="fas fa-truck me-2"></i> Delivery Information
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
|
|
||||||
<!-- Expected Delivery Date -->
|
<!-- Expected Delivery Date -->
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
{{form.expected_delivery_date|as_crispy_field}}
|
{{form.expected_delivery_date|as_crispy_field}}
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Comments -->
|
<!-- Comments -->
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<label for="comments" class="form-label">Comments</label>
|
<label for="comments" class="form-label">Comments</label>
|
||||||
<textarea class="form-control" id="comments" rows="3" placeholder="Enter any additional comments..."></textarea>
|
<textarea class="form-control" id="comments" rows="3" placeholder="Enter any additional comments..."></textarea>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Form Actions -->
|
<!-- Form Actions -->
|
||||||
<div class="form-actions mt-4">
|
<div class="form-actions mt-4">
|
||||||
<div class="d-flex justify-content-between">
|
<div class="d-flex justify-content-between">
|
||||||
<a href="{% url 'estimate_detail' estimate.pk %}" type="button" class="btn btn-phoenix-secondary">
|
<a href="{% url 'estimate_detail' estimate.pk %}" type="button" class="btn btn-phoenix-secondary">
|
||||||
<i class="fas fa-times me-2"></i> Cancel
|
<i class="fas fa-times me-2"></i> Cancel
|
||||||
</a>
|
</a>
|
||||||
<div>
|
<div>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-phoenix-primary">
|
<button type="submit" class="btn btn-phoenix-primary">
|
||||||
<i class="fas fa-check-circle me-2"></i> Submit Order
|
<i class="fas fa-check-circle me-2"></i> Submit Order
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block customJS %}
|
{% block customJS %}
|
||||||
|
|||||||
@ -72,11 +72,11 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex justify-content-end mt-3">
|
<div class="d-flex justify-content-end mt-3">
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
{% if is_paginated %}
|
{% if is_paginated %}
|
||||||
{% include 'partials/pagination.html' %}
|
{% include 'partials/pagination.html' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
File diff suppressed because it is too large
Load Diff
@ -27,21 +27,21 @@
|
|||||||
<td class="align-middle product white-space-nowrap py-0">{{ order.formatted_order_id }}</td>
|
<td class="align-middle product white-space-nowrap py-0">{{ order.formatted_order_id }}</td>
|
||||||
<td class="align-middle product white-space-nowrap py-0">{{ order.estimate.customer.customer_name }}</td>
|
<td class="align-middle product white-space-nowrap py-0">{{ order.estimate.customer.customer_name }}</td>
|
||||||
<td class="align-middle product white-space-nowrap">
|
<td class="align-middle product white-space-nowrap">
|
||||||
<a href="{% url 'estimate_detail' order.estimate.pk %}">
|
<a href="{% url 'estimate_detail' order.estimate.pk %}">
|
||||||
{{ order.estimate }}
|
{{ order.estimate }}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle product white-space-nowrap">
|
<td class="align-middle product white-space-nowrap">
|
||||||
{% if order.invoice %}
|
{% if order.invoice %}
|
||||||
<a href="{% url 'invoice_detail' order.invoice.pk %}">
|
<a href="{% url 'invoice_detail' order.invoice.pk %}">
|
||||||
{{ order.invoice }}
|
{{ order.invoice }}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle product white-space-nowrap py-0">{{ order.status }}</td>
|
<td class="align-middle product white-space-nowrap py-0">{{ order.status }}</td>
|
||||||
<td class="align-middle product white-space-nowrap py-0">{{ order.expected_delivery_date|naturalday|capfirst }}</td>
|
<td class="align-middle product white-space-nowrap py-0">{{ order.expected_delivery_date|naturalday|capfirst }}</td>
|
||||||
<td class="align-middle product white-space-nowrap py-0">
|
<td class="align-middle product white-space-nowrap py-0">
|
||||||
<a class="btn btn-sm btn-phoenix-success" href="{% url 'sale_order_details' order.estimate.pk order.pk %}">View</a>
|
<a class="btn btn-sm btn-phoenix-success" href="{% url 'sale_order_details' order.estimate.pk order.pk %}">View</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
@ -53,11 +53,11 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex justify-content-end mt-3">
|
<div class="d-flex justify-content-end mt-3">
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
{% if is_paginated %}
|
{% if is_paginated %}
|
||||||
{% include 'partials/pagination.html' %}
|
{% include 'partials/pagination.html' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -39,11 +39,11 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex justify-content-center">
|
<div class="d-flex justify-content-center">
|
||||||
{% if is_paginated %}
|
{% if is_paginated %}
|
||||||
{% include 'partials/pagination.html' %}
|
{% include 'partials/pagination.html' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -54,11 +54,11 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex justify-content-end mt-3">
|
<div class="d-flex justify-content-end mt-3">
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
{% if is_paginated %}
|
{% if is_paginated %}
|
||||||
{% include 'partials/pagination.html' %}
|
{% include 'partials/pagination.html' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -217,9 +217,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="d-flex justify-content-end mt-3">
|
<div class="d-flex justify-content-end mt-3">
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
{% if is_paginated %}
|
{% if is_paginated %}
|
||||||
{% include 'partials/pagination.html' %}
|
{% include 'partials/pagination.html' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -4,265 +4,265 @@
|
|||||||
{% block title %}{{ _("Terms of use and privacy policy")}}{% endblock title %}
|
{% block title %}{{ _("Terms of use and privacy policy")}}{% endblock title %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<style>
|
<style>
|
||||||
h2 {
|
h2 {
|
||||||
font-size: 1.7em;
|
font-size: 1.7em;
|
||||||
}
|
}
|
||||||
h3 {
|
h3 {
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="content fs-9">
|
<div class="content fs-9">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<!-- English Section -->
|
<!-- English Section -->
|
||||||
<h2>Date: 1/1/2025</h2>
|
<h2>Date: 1/1/2025</h2>
|
||||||
<section id="terms">
|
<section id="terms">
|
||||||
<h2>Terms of Service</h2>
|
<h2>Terms of Service</h2>
|
||||||
<p>Welcome to <strong>Haikal</strong>, an advanced car inventory management platform owned and operated by <strong>Tenhal Information Technology Company</strong> ("we", "our", "us"). By accessing or using the Haikal system ("the Service"), you agree to be legally bound by the terms outlined below.</p>
|
<p>Welcome to <strong>Haikal</strong>, an advanced car inventory management platform owned and operated by <strong>Tenhal Information Technology Company</strong> ("we", "our", "us"). By accessing or using the Haikal system ("the Service"), you agree to be legally bound by the terms outlined below.</p>
|
||||||
<h3>1. Acceptance of Terms</h3>
|
<h3>1. Acceptance of Terms</h3>
|
||||||
<p>By using the Service, you confirm that you are authorized to act on behalf of a business entity, agree to these Terms of Service, and comply with all applicable laws and regulations.</p>
|
<p>By using the Service, you confirm that you are authorized to act on behalf of a business entity, agree to these Terms of Service, and comply with all applicable laws and regulations.</p>
|
||||||
|
|
||||||
<h3>2. Description of Service</h3>
|
<h3>2. Description of Service</h3>
|
||||||
<p>Haikal provides car dealers and authorized users with tools for managing car inventory, sales, branches, financial transactions, and analytics. Additional services may include integration with government systems, API access, and reporting modules.</p>
|
<p>Haikal provides car dealers and authorized users with tools for managing car inventory, sales, branches, financial transactions, and analytics. Additional services may include integration with government systems, API access, and reporting modules.</p>
|
||||||
|
|
||||||
<h3>3. Account Registration & Security</h3>
|
<h3>3. Account Registration & Security</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li>You must register and maintain a secure account with accurate information.</li>
|
<li>You must register and maintain a secure account with accurate information.</li>
|
||||||
<li>You are solely responsible for any activity under your account.</li>
|
<li>You are solely responsible for any activity under your account.</li>
|
||||||
<li>You must notify us immediately if you suspect unauthorized access or breach of your account.</li>
|
<li>You must notify us immediately if you suspect unauthorized access or breach of your account.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h3>4. License and Restrictions</h3>
|
<h3>4. License and Restrictions</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li>We grant you a non-exclusive, non-transferable, revocable license to use the Service in accordance with these terms.</li>
|
<li>We grant you a non-exclusive, non-transferable, revocable license to use the Service in accordance with these terms.</li>
|
||||||
<li>You may not copy, modify, distribute, resell, reverse-engineer, or decompile any part of the Service.</li>
|
<li>You may not copy, modify, distribute, resell, reverse-engineer, or decompile any part of the Service.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h3>5. User Obligations</h3>
|
<h3>5. User Obligations</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li>You agree not to upload illegal, harmful, or offensive data to the system.</li>
|
<li>You agree not to upload illegal, harmful, or offensive data to the system.</li>
|
||||||
<li>You are responsible for maintaining compliance with data privacy regulations when inputting customer data.</li>
|
<li>You are responsible for maintaining compliance with data privacy regulations when inputting customer data.</li>
|
||||||
<li>You must not attempt to access systems or data not explicitly made available to you.</li>
|
<li>You must not attempt to access systems or data not explicitly made available to you.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h3>6. Intellectual Property</h3>
|
<h3>6. Intellectual Property</h3>
|
||||||
<p>All content, software, user interface designs, databases, and trademarks within Haikal are the intellectual property of Tenhal Information Technology Company and are protected under local and international IP laws.</p>
|
<p>All content, software, user interface designs, databases, and trademarks within Haikal are the intellectual property of Tenhal Information Technology Company and are protected under local and international IP laws.</p>
|
||||||
|
|
||||||
<h3>7. Service Availability & Modifications</h3>
|
<h3>7. Service Availability & Modifications</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li>We aim to provide 99.9% uptime but do not guarantee uninterrupted access.</li>
|
<li>We aim to provide 99.9% uptime but do not guarantee uninterrupted access.</li>
|
||||||
<li>We may modify or discontinue parts of the service at any time with or without notice.</li>
|
<li>We may modify or discontinue parts of the service at any time with or without notice.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h3>8. Third-Party Integrations</h3>
|
<h3>8. Third-Party Integrations</h3>
|
||||||
<p>We may integrate with external services such as VIN databases, payment processors, or government systems. Use of those services is subject to their own terms and privacy policies.</p>
|
<p>We may integrate with external services such as VIN databases, payment processors, or government systems. Use of those services is subject to their own terms and privacy policies.</p>
|
||||||
|
|
||||||
<h3>9. Limitation of Liability</h3>
|
<h3>9. Limitation of Liability</h3>
|
||||||
<p>To the fullest extent permitted by law, Tenhal is not liable for indirect, incidental, punitive, or consequential damages resulting from your use of the Service. Our total liability is limited to the amount you paid us in the last 12 months.</p>
|
<p>To the fullest extent permitted by law, Tenhal is not liable for indirect, incidental, punitive, or consequential damages resulting from your use of the Service. Our total liability is limited to the amount you paid us in the last 12 months.</p>
|
||||||
|
|
||||||
<h3>10. Termination</h3>
|
<h3>10. Termination</h3>
|
||||||
<p>We may suspend or terminate your access if you violate these terms. Upon termination, your access to the Service and associated data may be revoked or deleted.</p>
|
<p>We may suspend or terminate your access if you violate these terms. Upon termination, your access to the Service and associated data may be revoked or deleted.</p>
|
||||||
|
|
||||||
<h3>11. Governing Law</h3>
|
<h3>11. Governing Law</h3>
|
||||||
<p>These terms are governed by the laws of the Kingdom of Saudi Arabia. Any disputes will be resolved exclusively in courts located in Riyadh.</p>
|
<p>These terms are governed by the laws of the Kingdom of Saudi Arabia. Any disputes will be resolved exclusively in courts located in Riyadh.</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<section id="privacy">
|
<section id="privacy">
|
||||||
<h2>Privacy Policy</h2>
|
<h2>Privacy Policy</h2>
|
||||||
|
|
||||||
<p>We value your privacy and are committed to protecting your personal and business data. This Privacy Policy explains how we collect, use, and protect your information when you use Haikal.</p>
|
<p>We value your privacy and are committed to protecting your personal and business data. This Privacy Policy explains how we collect, use, and protect your information when you use Haikal.</p>
|
||||||
|
|
||||||
<h3>1. Information We Collect</h3>
|
<h3>1. Information We Collect</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong>Account Information:</strong> Name, email, phone number, user role, and login credentials.</li>
|
<li><strong>Account Information:</strong> Name, email, phone number, user role, and login credentials.</li>
|
||||||
<li><strong>Business Data:</strong> Inventory details, financial transactions, customer and supplier records.</li>
|
<li><strong>Business Data:</strong> Inventory details, financial transactions, customer and supplier records.</li>
|
||||||
<li><strong>Technical Data:</strong> IP addresses, browser types, login timestamps, session logs, device identifiers.</li>
|
<li><strong>Technical Data:</strong> IP addresses, browser types, login timestamps, session logs, device identifiers.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h3>2. How We Use Your Information</h3>
|
<h3>2. How We Use Your Information</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li>To operate and improve the Service.</li>
|
<li>To operate and improve the Service.</li>
|
||||||
<li>To secure accounts and prevent misuse or fraud.</li>
|
<li>To secure accounts and prevent misuse or fraud.</li>
|
||||||
<li>To provide customer support and respond to inquiries.</li>
|
<li>To provide customer support and respond to inquiries.</li>
|
||||||
<li>To comply with legal obligations and cooperate with regulators when required.</li>
|
<li>To comply with legal obligations and cooperate with regulators when required.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h3>3. Data Sharing</h3>
|
<h3>3. Data Sharing</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li>We do not sell your information to third parties.</li>
|
<li>We do not sell your information to third parties.</li>
|
||||||
<li>We may share data with trusted processors (e.g., hosting, support tools) under strict confidentiality terms.</li>
|
<li>We may share data with trusted processors (e.g., hosting, support tools) under strict confidentiality terms.</li>
|
||||||
<li>We may disclose data to authorities when legally required.</li>
|
<li>We may disclose data to authorities when legally required.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h3>4. Data Storage and Security</h3>
|
<h3>4. Data Storage and Security</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Your data is stored securely on encrypted servers with access control policies in place.</li>
|
<li>Your data is stored securely on encrypted servers with access control policies in place.</li>
|
||||||
<li>We apply firewalls, intrusion detection, and regular audits to safeguard information.</li>
|
<li>We apply firewalls, intrusion detection, and regular audits to safeguard information.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h3>5. Your Rights</h3>
|
<h3>5. Your Rights</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li>You have the right to access, correct, or request deletion of your data.</li>
|
<li>You have the right to access, correct, or request deletion of your data.</li>
|
||||||
<li>You may contact us to object to processing or request data portability.</li>
|
<li>You may contact us to object to processing or request data portability.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h3>6. Data Retention</h3>
|
<h3>6. Data Retention</h3>
|
||||||
<p>We retain data as long as necessary to provide the service, comply with legal obligations, or enforce agreements. Upon request, we may anonymize or delete your data.</p>
|
<p>We retain data as long as necessary to provide the service, comply with legal obligations, or enforce agreements. Upon request, we may anonymize or delete your data.</p>
|
||||||
|
|
||||||
<h3>7. Cookies and Tracking</h3>
|
<h3>7. Cookies and Tracking</h3>
|
||||||
<p>We may use cookies to enhance your experience. These may include session cookies, authentication tokens, and analytics tools.</p>
|
<p>We may use cookies to enhance your experience. These may include session cookies, authentication tokens, and analytics tools.</p>
|
||||||
|
|
||||||
<h3>8. International Data Transfers</h3>
|
<h3>8. International Data Transfers</h3>
|
||||||
<p>If data is processed outside of Saudi Arabia, we ensure adequate protection via agreements and security standards aligned with applicable laws.</p>
|
<p>If data is processed outside of Saudi Arabia, we ensure adequate protection via agreements and security standards aligned with applicable laws.</p>
|
||||||
|
|
||||||
<h3>9. Changes to this Policy</h3>
|
<h3>9. Changes to this Policy</h3>
|
||||||
<p>We may revise this Privacy Policy from time to time. Updates will be posted here with a revised effective date.</p>
|
<p>We may revise this Privacy Policy from time to time. Updates will be posted here with a revised effective date.</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6" dir="rtl">
|
<div class="col-6" dir="rtl">
|
||||||
<h2>التاريخ: ١/١/٢٠٢٥</h2>
|
<h2>التاريخ: ١/١/٢٠٢٥</h2>
|
||||||
<!-- Arabic Section -->
|
<!-- Arabic Section -->
|
||||||
<section class="arabic">
|
<section class="arabic">
|
||||||
<h2>شروط الخدمة</h2>
|
<h2>شروط الخدمة</h2>
|
||||||
<p>مرحبًا بك في <strong>هيكل</strong>، منصة متقدمة لإدارة مخزون السيارات، مملوكة وتديرها <strong>شركة تنحل لتقنية المعلومات</strong> ("نحن"، "خاصتنا"). باستخدامك لنظام هيكل، فإنك توافق على الالتزام القانوني بالشروط التالية:</p>
|
<p>مرحبًا بك في <strong>هيكل</strong>، منصة متقدمة لإدارة مخزون السيارات، مملوكة وتديرها <strong>شركة تنحل لتقنية المعلومات</strong> ("نحن"، "خاصتنا"). باستخدامك لنظام هيكل، فإنك توافق على الالتزام القانوني بالشروط التالية:</p>
|
||||||
|
|
||||||
<h3>١. قبول الشروط</h3>
|
<h3>١. قبول الشروط</h3>
|
||||||
<p>باستخدامك للخدمة، فإنك تؤكد أنك مفوض بالتصرف نيابة عن كيان تجاري، وتوافق على شروط الخدمة هذه، وتلتزم بجميع القوانين والأنظمة المعمول بها.</p>
|
<p>باستخدامك للخدمة، فإنك تؤكد أنك مفوض بالتصرف نيابة عن كيان تجاري، وتوافق على شروط الخدمة هذه، وتلتزم بجميع القوانين والأنظمة المعمول بها.</p>
|
||||||
|
|
||||||
<h3>٢. وصف الخدمة</h3>
|
<h3>٢. وصف الخدمة</h3>
|
||||||
<p>يوفر هيكل أدوات لتجار السيارات والمستخدمين المخولين لإدارة المخزون، المبيعات، الفروع، المعاملات المالية، والتحليلات. تشمل الخدمات الإضافية تكاملات مع أنظمة حكومية، وصول API، وتقارير.</p>
|
<p>يوفر هيكل أدوات لتجار السيارات والمستخدمين المخولين لإدارة المخزون، المبيعات، الفروع، المعاملات المالية، والتحليلات. تشمل الخدمات الإضافية تكاملات مع أنظمة حكومية، وصول API، وتقارير.</p>
|
||||||
|
|
||||||
<h3>٣. التسجيل والحماية</h3>
|
<h3>٣. التسجيل والحماية</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li>يجب تسجيل حساب دقيق وآمن.</li>
|
<li>يجب تسجيل حساب دقيق وآمن.</li>
|
||||||
<li>أنت مسؤول عن كل نشاط يتم عبر حسابك.</li>
|
<li>أنت مسؤول عن كل نشاط يتم عبر حسابك.</li>
|
||||||
<li>يجب إبلاغنا فورًا عند الاشتباه في اختراق الحساب.</li>
|
<li>يجب إبلاغنا فورًا عند الاشتباه في اختراق الحساب.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h3>٤. الترخيص والقيود</h3>
|
<h3>٤. الترخيص والقيود</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li>نمنحك ترخيصًا غير حصري وقابل للإلغاء لاستخدام الخدمة.</li>
|
<li>نمنحك ترخيصًا غير حصري وقابل للإلغاء لاستخدام الخدمة.</li>
|
||||||
<li>لا يحق لك نسخ، تعديل، توزيع، أو عكس هندسة أي جزء من الخدمة.</li>
|
<li>لا يحق لك نسخ، تعديل، توزيع، أو عكس هندسة أي جزء من الخدمة.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h3>٥. التزامات المستخدم</h3>
|
<h3>٥. التزامات المستخدم</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li>عدم تحميل بيانات غير قانونية أو ضارة.</li>
|
<li>عدم تحميل بيانات غير قانونية أو ضارة.</li>
|
||||||
<li>أنت مسؤول عن الامتثال لقوانين خصوصية البيانات.</li>
|
<li>أنت مسؤول عن الامتثال لقوانين خصوصية البيانات.</li>
|
||||||
<li>لا تحاول الوصول لبيانات أو أنظمة غير مصرّح بها.</li>
|
<li>لا تحاول الوصول لبيانات أو أنظمة غير مصرّح بها.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h3>٦. الملكية الفكرية</h3>
|
<h3>٦. الملكية الفكرية</h3>
|
||||||
<p>جميع المحتويات، البرمجيات، قواعد البيانات، والتصاميم تخص تنحل وتخضع للقوانين المحلية والدولية.</p>
|
<p>جميع المحتويات، البرمجيات، قواعد البيانات، والتصاميم تخص تنحل وتخضع للقوانين المحلية والدولية.</p>
|
||||||
|
|
||||||
<h3>٧. توفر الخدمة والتعديلات</h3>
|
<h3>٧. توفر الخدمة والتعديلات</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li>نهدف لتوفير الخدمة بنسبة تشغيل 99.9٪ ولكن لا نضمن عدم الانقطاع.</li>
|
<li>نهدف لتوفير الخدمة بنسبة تشغيل 99.9٪ ولكن لا نضمن عدم الانقطاع.</li>
|
||||||
<li>قد نقوم بتحديث أو تعديل أو إيقاف الخدمة في أي وقت.</li>
|
<li>قد نقوم بتحديث أو تعديل أو إيقاف الخدمة في أي وقت.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h3>٨. تكامل الأطراف الخارجية</h3>
|
<h3>٨. تكامل الأطراف الخارجية</h3>
|
||||||
<p>قد نتكامل مع خدمات خارجية مثل قواعد بيانات VIN، ومعالجات الدفع، والأنظمة الحكومية. يخضع استخدام هذه الخدمات لشروطها الخاصة.</p>
|
<p>قد نتكامل مع خدمات خارجية مثل قواعد بيانات VIN، ومعالجات الدفع، والأنظمة الحكومية. يخضع استخدام هذه الخدمات لشروطها الخاصة.</p>
|
||||||
|
|
||||||
<h3>٩. حدود المسؤولية</h3>
|
<h3>٩. حدود المسؤولية</h3>
|
||||||
<p>أقصى مسؤولية لنا عن أي ضرر غير مباشر أو عرضي تقتصر على ما دفعته خلال الـ 12 شهرًا الماضية.</p>
|
<p>أقصى مسؤولية لنا عن أي ضرر غير مباشر أو عرضي تقتصر على ما دفعته خلال الـ 12 شهرًا الماضية.</p>
|
||||||
|
|
||||||
<h3>١٠. الإنهاء</h3>
|
<h3>١٠. الإنهاء</h3>
|
||||||
<p>يجوز لنا إنهاء أو تعليق حسابك إذا انتهكت هذه الشروط. وقد يتم حذف بياناتك بعد الإنهاء.</p>
|
<p>يجوز لنا إنهاء أو تعليق حسابك إذا انتهكت هذه الشروط. وقد يتم حذف بياناتك بعد الإنهاء.</p>
|
||||||
|
|
||||||
<h3>١١. القانون الحاكم</h3>
|
<h3>١١. القانون الحاكم</h3>
|
||||||
<p>تخضع هذه الشروط لقوانين المملكة العربية السعودية، ويكون الاختصاص القضائي لمحاكم الرياض فقط.</p>
|
<p>تخضع هذه الشروط لقوانين المملكة العربية السعودية، ويكون الاختصاص القضائي لمحاكم الرياض فقط.</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<section class="arabic">
|
<section class="arabic">
|
||||||
<h2>سياسة الخصوصية</h2>
|
<h2>سياسة الخصوصية</h2>
|
||||||
|
|
||||||
<p>نحن نهتم بخصوصيتك وملتزمون بحماية بياناتك الشخصية والتجارية. توضح هذه السياسة كيفية جمع واستخدام وحماية بياناتك عند استخدام نظام هيكل.</p>
|
<p>نحن نهتم بخصوصيتك وملتزمون بحماية بياناتك الشخصية والتجارية. توضح هذه السياسة كيفية جمع واستخدام وحماية بياناتك عند استخدام نظام هيكل.</p>
|
||||||
|
|
||||||
<h3>١. المعلومات التي نجمعها</h3>
|
<h3>١. المعلومات التي نجمعها</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong>بيانات الحساب:</strong> الاسم، البريد الإلكتروني، الهاتف، الدور، بيانات تسجيل الدخول.</li>
|
<li><strong>بيانات الحساب:</strong> الاسم، البريد الإلكتروني، الهاتف، الدور، بيانات تسجيل الدخول.</li>
|
||||||
<li><strong>بيانات الأعمال:</strong> تفاصيل السيارات، المعاملات المالية، سجلات العملاء والموردين.</li>
|
<li><strong>بيانات الأعمال:</strong> تفاصيل السيارات، المعاملات المالية، سجلات العملاء والموردين.</li>
|
||||||
<li><strong>بيانات تقنية:</strong> عناوين IP، أنواع المتصفحات، أوقات الدخول، سجلات الجلسات، معرفات الأجهزة.</li>
|
<li><strong>بيانات تقنية:</strong> عناوين IP، أنواع المتصفحات، أوقات الدخول، سجلات الجلسات، معرفات الأجهزة.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h3>٢. استخدام البيانات</h3>
|
<h3>٢. استخدام البيانات</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li>لتشغيل الخدمة وتحسينها.</li>
|
<li>لتشغيل الخدمة وتحسينها.</li>
|
||||||
<li>لحماية الحسابات ومنع الاحتيال.</li>
|
<li>لحماية الحسابات ومنع الاحتيال.</li>
|
||||||
<li>لدعم العملاء والاستجابة للاستفسارات.</li>
|
<li>لدعم العملاء والاستجابة للاستفسارات.</li>
|
||||||
<li>للالتزام بالقوانين والتعاون مع الجهات التنظيمية.</li>
|
<li>للالتزام بالقوانين والتعاون مع الجهات التنظيمية.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h3>٣. مشاركة البيانات</h3>
|
<h3>٣. مشاركة البيانات</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li>لا نبيع بياناتك لأي طرف ثالث.</li>
|
<li>لا نبيع بياناتك لأي طرف ثالث.</li>
|
||||||
<li>قد نشارك البيانات مع مزودين موثوقين بموجب اتفاقيات سرية.</li>
|
<li>قد نشارك البيانات مع مزودين موثوقين بموجب اتفاقيات سرية.</li>
|
||||||
<li>قد نكشف عن البيانات للجهات المختصة عند الطلب القانوني.</li>
|
<li>قد نكشف عن البيانات للجهات المختصة عند الطلب القانوني.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h3>٤. التخزين والحماية</h3>
|
<h3>٤. التخزين والحماية</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li>تُخزن البيانات على خوادم مشفرة مع سياسات وصول صارمة.</li>
|
<li>تُخزن البيانات على خوادم مشفرة مع سياسات وصول صارمة.</li>
|
||||||
<li>نطبق جدران حماية، واكتشاف التسلل، ومراجعات دورية.</li>
|
<li>نطبق جدران حماية، واكتشاف التسلل، ومراجعات دورية.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h3>٥. حقوقك</h3>
|
<h3>٥. حقوقك</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li>لك الحق في الوصول إلى بياناتك أو تعديلها أو طلب حذفها.</li>
|
<li>لك الحق في الوصول إلى بياناتك أو تعديلها أو طلب حذفها.</li>
|
||||||
<li>يمكنك الاعتراض على المعالجة أو طلب نقل البيانات.</li>
|
<li>يمكنك الاعتراض على المعالجة أو طلب نقل البيانات.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h3>٦. الاحتفاظ بالبيانات</h3>
|
<h3>٦. الاحتفاظ بالبيانات</h3>
|
||||||
<p>نحتفظ بالبيانات طالما كانت ضرورية لتقديم الخدمة أو للامتثال للأنظمة. يمكننا إزالتها أو إخفاؤها حسب الطلب.</p>
|
<p>نحتفظ بالبيانات طالما كانت ضرورية لتقديم الخدمة أو للامتثال للأنظمة. يمكننا إزالتها أو إخفاؤها حسب الطلب.</p>
|
||||||
|
|
||||||
<h3>٧. الكوكيز والتتبع</h3>
|
<h3>٧. الكوكيز والتتبع</h3>
|
||||||
<p>قد نستخدم الكوكيز لتحسين تجربتك، بما في ذلك جلسات التوثيق والتحليلات.</p>
|
<p>قد نستخدم الكوكيز لتحسين تجربتك، بما في ذلك جلسات التوثيق والتحليلات.</p>
|
||||||
|
|
||||||
<h3>٨. نقل البيانات خارجياً</h3>
|
<h3>٨. نقل البيانات خارجياً</h3>
|
||||||
<p>إذا تم نقل البيانات خارج السعودية، نضمن حمايتها وفق اتفاقيات ومعايير قانونية مناسبة.</p>
|
<p>إذا تم نقل البيانات خارج السعودية، نضمن حمايتها وفق اتفاقيات ومعايير قانونية مناسبة.</p>
|
||||||
|
|
||||||
<h3>٩. التحديثات</h3>
|
<h3>٩. التحديثات</h3>
|
||||||
<p>قد نُجري تغييرات على هذه السياسة، وسيتم نشر التعديلات مع تاريخ سريان جديد.</p>
|
<p>قد نُجري تغييرات على هذه السياسة، وسيتم نشر التعديلات مع تاريخ سريان جديد.</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="row border-top">
|
|
||||||
<div class="col-6">
|
|
||||||
<section id="contact">
|
|
||||||
<h2>Contact Information</h2>
|
|
||||||
<p>If you have any questions or concerns about these Terms or Privacy practices, please contact us:</p>
|
|
||||||
<p>
|
|
||||||
<strong>Tenhal Information Technology Company</strong><br>
|
|
||||||
Riyadh, Saudi Arabia<br>
|
|
||||||
📧 <a href="mailto:info@tenhal.sa">info@tenhal.sa</a><br>
|
|
||||||
🌐 <a href="https://www.tenhal.sa" target="_blank">www.tenhal.sa</a>
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6" dir="rtl">
|
<div class="row border-top">
|
||||||
<section class="arabic">
|
<div class="col-6">
|
||||||
<h2>معلومات التواصل</h2>
|
<section id="contact">
|
||||||
<p>لأي استفسار حول هذه الشروط أو سياسة الخصوصية، يرجى التواصل معنا:</p>
|
<h2>Contact Information</h2>
|
||||||
<p>
|
<p>If you have any questions or concerns about these Terms or Privacy practices, please contact us:</p>
|
||||||
<strong>شركة تنحل لتقنية المعلومات</strong><br>
|
<p>
|
||||||
الرياض، المملكة العربية السعودية<br>
|
<strong>Tenhal Information Technology Company</strong><br>
|
||||||
📧 <a href="mailto:info@tenhal.sa">info@tenhal.sa</a><br>
|
Riyadh, Saudi Arabia<br>
|
||||||
🌐 <a href="https://www.tenhal.sa" target="_blank">tenhal.sa</a>
|
📧 <a href="mailto:info@tenhal.sa">info@tenhal.sa</a><br>
|
||||||
</p>
|
🌐 <a href="https://www.tenhal.sa" target="_blank">www.tenhal.sa</a>
|
||||||
</section>
|
</p>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<div class="col-6" dir="rtl">
|
||||||
|
<section class="arabic">
|
||||||
|
<h2>معلومات التواصل</h2>
|
||||||
|
<p>لأي استفسار حول هذه الشروط أو سياسة الخصوصية، يرجى التواصل معنا:</p>
|
||||||
|
<p>
|
||||||
|
<strong>شركة تنحل لتقنية المعلومات</strong><br>
|
||||||
|
الرياض، المملكة العربية السعودية<br>
|
||||||
|
📧 <a href="mailto:info@tenhal.sa">info@tenhal.sa</a><br>
|
||||||
|
🌐 <a href="https://www.tenhal.sa" target="_blank">tenhal.sa</a>
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -3,29 +3,29 @@
|
|||||||
{% block title %}{{ tour.name }} - Interactive Guide{% endblock %}
|
{% block title %}{{ tour.name }} - Interactive Guide{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container my-4">
|
<div class="container my-4">
|
||||||
<h1>{{ tour.name }}</h1>
|
<h1>{{ tour.name }}</h1>
|
||||||
<p class="lead">{{ tour.description }}</p>
|
<p class="lead">{{ tour.description }}</p>
|
||||||
|
|
||||||
<div class="card mb-4">
|
<div class="card mb-4">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h5 class="card-title">Ready to Start</h5>
|
<h5 class="card-title">Ready to Start</h5>
|
||||||
<p>This interactive guide will walk you through each step of the process.</p>
|
<p>This interactive guide will walk you through each step of the process.</p>
|
||||||
<button class="btn btn-primary" onclick="window.tourManager.loadTour('{{ tour.slug }}')">
|
<button class="btn btn-primary" onclick="window.tourManager.loadTour('{{ tour.slug }}')">
|
||||||
Start Guide Now
|
Start Guide Now
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block extra_js %}
|
{% block extra_js %}
|
||||||
<script>
|
<script>
|
||||||
// Auto-start the tour after a short delay
|
// Auto-start the tour after a short delay
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
window.tourManager.loadTour('{{ tour.slug }}');
|
window.tourManager.loadTour('{{ tour.slug }}');
|
||||||
}, 1000);
|
}, 1000);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -3,30 +3,30 @@
|
|||||||
{% block title %}Interactive Guides{% endblock %}
|
{% block title %}Interactive Guides{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container my-4">
|
<div class="container my-4">
|
||||||
<h1>Interactive Guides</h1>
|
<h1>Interactive Guides</h1>
|
||||||
<p class="lead">Learn how to use the car inventory system with these interactive step-by-step guides.</p>
|
<p class="lead">Learn how to use the car inventory system with these interactive step-by-step guides.</p>
|
||||||
|
|
||||||
<div class="row mt-4">
|
<div class="row mt-4">
|
||||||
{% for tour in tours %}
|
{% for tour in tours %}
|
||||||
<div class="col-md-4 mb-4">
|
<div class="col-md-4 mb-4">
|
||||||
<div class="card h-100">
|
<div class="card h-100">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h5 class="card-title">{{ tour.name }}</h5>
|
<h5 class="card-title">{{ tour.name }}</h5>
|
||||||
<p class="card-text">{{ tour.description }}</p>
|
<p class="card-text">{{ tour.description }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer">
|
||||||
|
<a href="{% url 'start_tour' tour.slug %}" class="btn btn-primary">Start Guide</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer">
|
{% empty %}
|
||||||
<a href="{% url 'start_tour' tour.slug %}" class="btn btn-primary">Start Guide</a>
|
<div class="col-12">
|
||||||
|
<div class="alert alert-info">
|
||||||
|
No interactive guides available at this time.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% empty %}
|
|
||||||
<div class="col-12">
|
|
||||||
<div class="alert alert-info">
|
|
||||||
No interactive guides available at this time.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -3,11 +3,11 @@
|
|||||||
{% load crispy_forms_filters %}
|
{% load crispy_forms_filters %}
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{# Check if an 'object' exists in the context #}
|
{# Check if an 'object' exists in the context #}
|
||||||
{% if object %}
|
{% if object %}
|
||||||
{% trans 'Update Staff'%}
|
{% trans 'Update Staff'%}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% trans 'Add New Staff'%}
|
{% trans 'Add New Staff'%}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|||||||
@ -42,7 +42,7 @@
|
|||||||
<td class="align-middle white-space-nowrap ps-1">
|
<td class="align-middle white-space-nowrap ps-1">
|
||||||
<div>
|
<div>
|
||||||
<a class="fs-8 fw-bold" href="{% url 'user_detail' user.slug%}">{{ user.arabic_name }}</a>
|
<a class="fs-8 fw-bold" href="{% url 'user_detail' user.slug%}">{{ user.arabic_name }}</a>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle white-space-nowrap align-items-center">{{ user.email }}</td>
|
<td class="align-middle white-space-nowrap align-items-center">{{ user.email }}</td>
|
||||||
|
|||||||
10
templates/vendors/vendor_form.html
vendored
10
templates/vendors/vendor_form.html
vendored
@ -3,11 +3,11 @@
|
|||||||
{% load crispy_forms_filters %}
|
{% load crispy_forms_filters %}
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{# Check if an 'object' exists in the context #}
|
{# Check if an 'object' exists in the context #}
|
||||||
{% if object %}
|
{% if object %}
|
||||||
{% trans 'Update Vendor'%}
|
{% trans 'Update Vendor'%}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% trans 'Add New Vendor'%}
|
{% trans 'Add New Vendor'%}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user