Compare commits

...

6 Commits

24 changed files with 304 additions and 211 deletions

View File

@ -432,25 +432,20 @@ class CarFinanceForm(forms.ModelForm):
additional services associated with a car finance application.
"""
# additional_finances = forms.ModelMultipleChoiceField(
# queryset=AdditionalServices.objects.all(),
# widget=forms.CheckboxSelectMultiple(attrs={"class": "form-check-input"}),
# required=False,
# )
def clean(self):
cleaned_data = super().clean()
cost_price = cleaned_data.get("cost_price")
marked_price = cleaned_data.get("marked_price")
if cost_price > marked_price:
raise forms.ValidationError({"cost_price": "Cost price should not be greater than marked price"})
return cleaned_data
class Meta:
model = CarFinance
fields = ["cost_price","marked_price"]
# def save(self, commit=True):
# instance = super().save()
# try:
# instance.additional_services.set(self.cleaned_data["additional_finances"])
# except KeyError:
# pass
# instance.save()
# return instance
class CarLocationForm(forms.ModelForm):
"""
@ -1172,6 +1167,7 @@ class ScheduleForm(forms.ModelForm):
scheduled_at = forms.DateTimeField(
widget=DateTimeInput(attrs={"type": "datetime-local"})
)
reminder = forms.BooleanField(help_text=_("Send a reminder?"),required=False)
class Meta:
model = Schedule

View File

@ -742,7 +742,9 @@ class Car(Base):
)
except Exception:
return False
@property
def invoice(self):
return self.item_model.invoicemodel_set.first if self.item_model.invoicemodel_set.first() else None
def get_transfer(self):
return self.transfer_logs.filter(active=True).first()

View File

@ -1282,14 +1282,18 @@ urlpatterns = [
),
path('car-sale-report/<slug:dealer_slug>/csv/', views.car_sale_report_csv_export, name='car-sale-report-csv-export'),
path('feature/recalls/', views.RecallListView.as_view(), name='recall_list'),
path('feature/recall/', views.RecallFilterView, name='recall_filter'),
path('feature/recall/', views.RecallListView.as_view(), name='recall_list'),
path('feature/recall/filter/', views.RecallFilterView, name='recall_filter'),
path('feature/recall/<int:pk>/view/', views.RecallDetailView.as_view(), name='recall_detail'),
path('feature/recall/create/', views.RecallCreateView.as_view(), name='recall_create'),
path('feature/recall/success/', views.RecallSuccessView.as_view(), name='recall_success'),
path('<slug:dealer_slug>/schedules/calendar/', views.schedule_calendar, name='schedule_calendar'),
# staff profile
path('<slug:dealer_slug>/staff/<slug:slug>detail/', views.StaffDetailView.as_view(), name='staff_detail'),
]
handler404 = "inventory.views.custom_page_not_found_view"

View File

@ -2197,6 +2197,35 @@ class DealerUpdateView(
def get_success_url(self):
return reverse("dealer_detail", kwargs={"slug": self.object.slug})
class StaffDetailView(LoginRequiredMixin, DetailView):
"""
Represents a detailed view for a Dealer model.
This class extends Django's `DetailView` to provide a detailed view of a dealer.
It includes additional context data such as the count of staff members, cars
associated with the dealer, available car makes, and dynamically fetched quotas.
The class also ensures that users must be logged in to access the detailed view.
:ivar model: The model associated with this view (Dealer model).
:type model: django.db.models.Model
:ivar template_name: Path to the template used to render the view.
:type template_name: str
:ivar context_object_name: The name used to refer to the object in the template context.
:type context_object_name: str
"""
model = models.Staff
template_name = "staff/staff_detail.html"
context_object_name = "staff"
def dealer_vat_rate_update(request, slug):
dealer = get_object_or_404(models.Dealer, slug=slug)
models.VatRate.objects.filter(dealer=dealer).update(rate=request.POST.get("rate"))
messages.success(request, _("VAT rate updated successfully"))
return redirect("dealer_detail", slug=slug)
class CustomerListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
@ -4912,10 +4941,19 @@ def update_estimate_discount(request, dealer_slug, pk):
object_id=estimate.pk,
)
discount_amount = request.POST.get("discount_amount", 0)
calculator = CarFinanceCalculator(estimate)
finance_data = calculator.get_finance_data()
if Decimal(discount_amount) >= finance_data.get('cars')[0]['marked_price']:
messages.error(request, _("Discount amount cannot be greater than marked price"))
return redirect("estimate_detail", dealer_slug=dealer_slug, pk=pk)
print(finance_data.get('cars')[0]['marked_price'] * Decimal('0.5'))
if Decimal(discount_amount) > finance_data.get('cars')[0]['marked_price'] * Decimal('0.5'):
messages.warning(request, _("Discount amount is greater than 50% of the marked price, proceed with caution."))
else:
messages.success(request, _("Discount updated successfully"))
extra_info.data.update({"discount": Decimal(discount_amount)})
extra_info.save()
messages.success(request, "Discount updated successfully")
return redirect("estimate_detail", dealer_slug=dealer_slug, pk=pk)
@ -6535,6 +6573,7 @@ def schedule_event(request, dealer_slug, content_type, slug):
form = forms.ScheduleForm(request.POST)
if form.is_valid():
reminder = form.cleaned_data['reminder']
instance = form.save(commit=False)
instance.dealer = dealer
instance.content_object = obj
@ -6581,20 +6620,21 @@ def schedule_event(request, dealer_slug, content_type, slug):
created_by=request.user,
activity_type=instance.scheduled_type,
)
scheduled_at_aware = timezone.make_aware(instance.scheduled_at, timezone.get_current_timezone()) if timezone.is_naive(instance.scheduled_at) else instance.scheduled_at
if reminder:
scheduled_at_aware = timezone.make_aware(instance.scheduled_at, timezone.get_current_timezone()) if timezone.is_naive(instance.scheduled_at) else instance.scheduled_at
reminder_time = scheduled_at_aware - timezone.timedelta(minutes=15)
# Only schedule if the reminder time is in the future
# Reminder emails are scheduled to be sent 15 minutes before the scheduled time
if reminder_time > timezone.now():
DjangoQSchedule.objects.create(
name=f"send_schedule_reminder_email_to_{instance.scheduled_by.email}_for_{content_type}_with_PK_{instance.pk}",
func='inventory.tasks.send_schedule_reminder_email',
args=f'"{instance.pk}"',
schedule_type=DjangoQSchedule.ONCE,
next_run=reminder_time,
hook='inventory.tasks.log_email_status',
)
reminder_time = scheduled_at_aware - timezone.timedelta(minutes=15)
# Only schedule if the reminder time is in the future
# Reminder emails are scheduled to be sent 15 minutes before the scheduled time
if reminder_time > timezone.now():
DjangoQSchedule.objects.create(
name=f"send_schedule_reminder_email_to_{instance.scheduled_by.email}_for_{content_type}_with_PK_{instance.pk}",
func='inventory.tasks.send_schedule_reminder_email',
args=f'"{instance.pk}"',
schedule_type=DjangoQSchedule.ONCE,
next_run=reminder_time,
hook='inventory.tasks.log_email_status',
)
messages.success(request, _("Appointment Created Successfully"))
return redirect(f'{content_type}_detail',dealer_slug=dealer_slug, slug=slug)
@ -9456,10 +9496,12 @@ def submit_plan(request, dealer_slug):
tax=15,
status=1,
)
logger.info(f"order created {order}")
except Exception as e:
print(e)
logger.error(e)
if not order:
messages.error(request, _("Error creating order"))
logger.error("unable to create order")
return redirect("pricing_page", dealer_slug=dealer_slug)
transaction_url = handle_payment(request, order)
return redirect(transaction_url)
@ -9473,9 +9515,13 @@ def payment_callback(request, dealer_slug):
payment_id = request.GET.get("id")
history = models.PaymentHistory.objects.filter(transaction_id=payment_id).first()
payment_status = request.GET.get("status")
logger.info(f"Received payment callback for dealer_slug: {dealer_slug}, payment_id: {payment_id}, status: {payment_status}")
order = Order.objects.filter(user=dealer.user, status=1).first() # Status 1 = NEW
print(order)
if payment_status == "paid":
logger.info(f"Payment successful for transaction ID {payment_id}. Processing order completion.")
billing_info, created = BillingInfo.objects.get_or_create(
user=dealer.user,
defaults={
@ -9487,12 +9533,20 @@ def payment_callback(request, dealer_slug):
'country': dealer.entity.country or " ",
}
)
if created:
logger.info(f"Created new billing info for user {dealer.user}.")
else:
logger.debug(f"Billing info already exists for user {dealer.user}.")
if not hasattr(order.user, 'userplan'):
UserPlan.objects.create(
user=order.user,
plan=order.plan,
expire=datetime.now().date() + timedelta(days=order.get_plan_pricing().pricing.period)
)
logger.info(f"Created new UserPlan for user {order.user} with plan {order.plan}.")
else:
logger.info(f"UserPlan already exists for user {order.user}.")
try:
@ -9510,7 +9564,7 @@ def payment_callback(request, dealer_slug):
order.complete_order()
history.status = "paid"
history.save()
logger.info(f"Order {order.id} for user {order.user} completed successfully. Payment history updated.")
invoice = order.get_invoices().first()
return render(
request,
@ -9519,12 +9573,14 @@ def payment_callback(request, dealer_slug):
)
except Exception as e:
logger.exception(f"Error completing order {order.id} for user {order.user}: {e}")
logger.error(f"Plan activation failed: {str(e)}")
history.status = "failed"
history.save()
return render(request, "payment_failed.html", {"message": "Plan activation error"})
elif payment_status == "failed":
logger.warning(f"Payment failed for transaction ID {payment_id}. Message: {message}")
history.status = "failed"
history.save()
return render(request, "payment_failed.html", {"message": message})
@ -9769,10 +9825,10 @@ def update_task(request, dealer_slug, pk):
@permission_required("inventory.change_schedule", raise_exception=True)
def update_schedule(request, dealer_slug, pk):
task = get_object_or_404(models.Schedule, pk=pk)
if request.method == "POST":
task.completed = False if task.completed else True
task.save()
print("task")
return render(request, "partials/task.html", {"task": task})

View File

@ -138,7 +138,8 @@ html[dir="rtl"] .form-icon-container .form-control {
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 10;
z-index: 9999;
pointer-events: none;
}
#spinner-bg {
@ -150,11 +151,14 @@ html[dir="rtl"] .form-icon-container .form-control {
background-color: rgba(255, 255, 255, 0.7);
opacity: 0;
transition: opacity 500ms ease-in;
z-index: 5;
visibility: hidden;
z-index: 10000;
pointer-events: none;
}
#spinner-bg.htmx-request {
opacity: .8;
visibility: visible;
}

View File

@ -84,10 +84,10 @@
{% include "plans/expiration_messages.html" %}
{% block period_navigation %}
{% endblock period_navigation %}
<div id="main_content" class="fade-me-in" hx-boost="false" hx-target="#main_content" hx-select="#main_content" hx-swap="outerHTML transition:true" hx-select-oob="#toast-container" hx-history-elt>
<div id="spinner" class="htmx-indicator spinner-bg">
<div id="spinner" class="htmx-indicator spinner-bg">
<img src="{% static 'spinner.svg' %}" width="100" height="100" alt="">
</div>
<div id="main_content" class="fade-me-in" hx-boost="false" hx-target="#main_content" hx-select="#main_content" hx-swap="outerHTML transition:true" hx-select-oob="#toast-container" hx-history-elt>
{% block customCSS %}{% endblock %}
{% block content %}{% endblock content %}
{% block customJS %}{% endblock %}

View File

@ -47,13 +47,13 @@
{% endif %}
{% if perms.inventory.add_car %}
<li class="nav-item">
{% comment %} <li class="nav-item">
<a class="nav-link" href="{% url 'upload_cars' request.dealer.slug %}">
<div class="d-flex align-items-center">
<span class="nav-link-icon"><span class="fas fa-file-import"></span></span><span class="nav-link-text">{% trans "Bulk Upload"|capfirst %}</span>
</div>
</a>
</li>
</li> {% endcomment %}
{% endif %}
{% if perms.django_ledger.view_purchaseordermodel %}
<li class="nav-item">
@ -504,7 +504,7 @@
</li>
{% else %}
<li class="nav-item">
<a hx-boost="false" class="nav-link px-3 d-block" href="{% url 'appointment:user_profile' request.user.id %}"> <span class="me-2 text-body align-bottom" data-feather="user"></span><span>{% translate 'profile'|capfirst %}</span></a>
<a hx-boost="false" class="nav-link px-3 d-block" href="{% url 'staff_detail' request.dealer.slug request.staff.slug %}"> <span class="me-2 text-body align-bottom" data-feather="user"></span><span>{% translate 'profile'|capfirst %}</span></a>
</li>
{% endif %}
{% if request.is_dealer %}
@ -530,7 +530,7 @@
</li>
{% if request.is_staff %}
<li class="nav-item">
<a hx-boost="false" class="nav-link px-3 d-block" href="{% url 'appointment:get_user_appointments' %}"> <span class="me-2 text-body align-bottom" data-feather="calendar"></span>{{ _("My Calendar") }}</a>
<a hx-boost="false" class="nav-link px-3 d-block" href="{% url 'schedule_calendar' request.dealer.slug%}"> <span class="me-2 text-body align-bottom" data-feather="calendar"></span>{{ _("My Calendar") }}</a>
</li>
{% endif %}
<!--<li class="nav-item"><a class="nav-link px-3 d-block" href=""> Language</a></li>-->

View File

@ -127,7 +127,11 @@
</div>
</div>
</div>
</div>
</section>
<section id="sale-details" class="mb-3">
@ -137,47 +141,48 @@
<i class="bi bi-download me-2"></i>{% trans 'Download as CSV' %}
</a>
</div>
<div class="table-responsive ">
<table class="table table-sm table-striped table-hover ">
<thead class="bg-body-highlight">
<div class="table-responsive">
<table class="table table-striped table-hover table-bordered table-sm">
<thead>
<tr>
<th scope="col">{% trans 'VIN' %}</th>
<th scope="col">{% trans 'Make' %}</th>
<th scope="col">{% trans 'Model' %}</th>
<th scope="col">{% trans 'Year' %}</th>
<th scope="col">{% trans 'Serie' %}</th>
<th scope="col">{% trans 'Trim' %}</th>
<th scope="col">{% trans 'Mileage' %}</th>
<th scope="col">{% trans 'Stock Type' %}</th>
<th scope="col">{% trans 'Created Date' %}</th>
<th scope="col">{% trans 'Sold Date' %}</th>
<th scope="col">{% trans 'Cost Price' %}</th>
<th scope="col">{% trans 'Marked Price' %}</th>
<th scope="col">{% trans 'Discount Amount' %}</th>
<th scope="col">{% trans 'Selling Price' %}</th>
<th scope="col">{% trans 'Tax Amount' %}</th>
<th scope="col">{% trans 'Invoice Number' %}</th>
<th class="fs-9">{% trans 'VIN' %}</th>
<th class="fs-9">{% trans 'Make' %}</th>
<th class="fs-9">{% trans 'Model' %}</th>
<th class="fs-9">{% trans 'Year' %}</th>
<th class="fs-9">{% trans 'Serie' %}</th>
<th class="fs-9">{% trans 'Trim' %}</th>
<th class="fs-9">{% trans 'Mileage' %}</th>
<th class="fs-9">{% trans 'Stock Type' %}</th>
<th class="fs-9">{% trans 'Created Date' %}</th>
<th class="fs-9">{% trans 'Sold Date' %}</th>
<th class="fs-9">{% trans 'Cost Price' %}</th>
<th class="fs-9">{% trans 'Marked Price' %}</th>
<th class="fs-9">{% trans 'Discount Amount' %}</th>
<th class="fs-9">{% trans 'Selling Price' %}</th>
<th class="fs-9">{% trans 'Tax Amount' %}</th>
<th class="fs-9">{% trans 'Invoice Number' %}</th>
</tr>
</thead>
<tbody>
{% for car in cars_sold %}
<tr>
<td>{{ car.vin }}</td>
<td>{{ car.id_car_make.name }}</td>
<td>{{ car.id_car_model.name }}</td>
<td>{{ car.year }}</td>
<td>{{ car.id_car_serie.name }}</td>
<td>{{ car.id_car_trim.name }}</td>
<td>{{ car.mileage }}</td>
<td>{{ car.stock_type }}</td>
<td>{{ car.created_at }}</td>
<td>{{ car.sold_date }}</td>
<td>{{ car.finances.cost_price }}</td>
<td>{{ car.finances.marked_price }}</td>
<td>{{ car.finances.discount_amount }}</td>
<td>{{ car.finances.selling_price }}</td>
<td>{{ car.finances.vat_amount }}</td>
<td>{{ car.item_model.invoicemodel_set.first.invoice_number }}</td>
<td class="ps-1 fs-9">{{ car.vin }}</td>
<td class="fs-9">{{ car.id_car_make.name }}</td>
<td class="fs-9">{{ car.id_car_model.name }}</td>
<td class="fs-9">{{ car.year }}</td>
<td class="fs-9">{{ car.id_car_serie.name }}</td>
<td class="fs-9">{{ car.id_car_trim.name }}</td>
<td class="fs-9">{{ car.mileage }}</td>
<td class="fs-9">{{ car.stock_type }}</td>
<td class="fs-9">{{ car.created_at|date }}</td>
<td class="fs-9">{{ car.invoice.date_paid|date|default_if_none:"-" }}</td>
<td class="fs-9">{{ car.finances.cost_price }}</td>
<td class="fs-9">{{ car.finances.marked_price }}</td>
<td class="fs-9">{{ car.finances.discount_amount }}</td>
<td class="fs-9">{{ car.finances.selling_price }}</td>
<td class="fs-9">{{ car.finances.vat_amount }}</td>
<td class="fs-9">{{ car.invoice.invoice_number }}</td>
</tr>
{% endfor %}
</tbody>

View File

@ -1,25 +1,13 @@
{% load i18n %}{% autoescape off %}
مرحباً {% firstof user.get_full_name user.username %}،
{% trans "Hi" %} {% firstof user.get_full_name user.username %},
{% if userplan.expire != None %}
خطتك الحالية هي {{ plan.name }} وستنتهي صلاحيتها في {{ userplan.expire }}.
{% blocktrans with plan_name=plan.name expire=userplan.expire %}Your current plan is {{ plan_name }} and it will expire on {{ expire }}. {% endblocktrans %}
{% else %}
خطتك الحالية هي {{ plan.name }}.
{% blocktrans with plan_name=plan.name %}Your current plan is {{ plan_name }}. {% endblocktrans %}
{% endif %}
شكراً لك،
فريق تنحل
---------------------
Hi {% firstof user.get_full_name user.username %},
{% if userplan.expire != None %}
Your current plan is {{ plan_name }} and it will expire on {{ expire }}. {% endblocktrans %}
{% else %}
Your current plan is {{ plan_name }}. {% endblocktrans %}
{% endif %}
Thank you
The Team at Tenhal
{% endautoescape %}
{% trans "Thank you" %}
--
{% blocktrans %}The Team at {{ site_name }}{% endblocktrans %}
{% endautoescape %}

View File

@ -1,3 +1 @@
{% load i18n %}
Your account {{ user }} has new plan {{ plan }}
حسابك {{ user }} لديه خطة جديدة {{ plan }}
{% load i18n %}{% blocktrans with user=user plan=plan.name %}Your account {{ user }} has new plan {{ plan }}{% endblocktrans %}

View File

@ -1,27 +1,14 @@
{% load i18n %}{% autoescape off %}
مرحباً {% firstof user.get_full_name user.username %}،
{% trans "Hi" %} {% firstof user.get_full_name user.username %},
لقد انتهت صلاحية حسابك للتو.
{% blocktrans %}Your account has just expired.{% endblocktrans %}
يمكنك استعادة خطتك الحالية {{ userplan.plan.name }} من هنا:
{% blocktrans with plan_name=userplan.plan.name %}You can restore your current plan {{ plan_name }} here:{% endblocktrans %}
http://{{ site_domain }}{% url 'current_plan' %}
أو يمكنك ترقية خطتك من هنا:
{% blocktrans %}or you can upgrade your plan here:{% endblocktrans %}
http://{{ site_domain }}{% url 'upgrade_plan' %}
شكراً لك،
{% trans "Thank you" %}
--
فريق تنحل
---------------------
Hi {% firstof user.get_full_name user.username %},
Your account has just expired.
You can restore your current plan {{ userplan.plan.name }} here:
http://{{ site_domain }}{% url 'current_plan' %}
or you can upgrade your plan here:
http://{{ site_domain }}{% url 'upgrade_plan' %}
Thank you,
--
The Team at TENHAL
{% endautoescape %}
{% blocktrans %}The Team at {{ site_name }}{% endblocktrans %}
{% endautoescape %}

View File

@ -1,5 +1 @@
{% load i18n %}{% autoescape off %}
لقد انتهت صلاحية حسابك {{ user }} للتو.
---------------------
Your account {{ user }} has just expired.
{% endautoescape %}
{% load i18n %}{% blocktrans %}Your account {{ user }} has just expired{% endblocktrans %}

View File

@ -1,20 +1,11 @@
{% load i18n %}{% autoescape off %}
مرحباً {% firstof user.get_full_name user.username %}،
{% trans "Hi" %} {% firstof user.get_full_name user.username %},
تم تمديد صلاحية حسابك للتو لمدة {{ pricing.period }} يوم. خطتك الحالية هي {{plan.name}} وستنتهي في {{userplan.expire}}.
{% blocktrans with days=pricing.period plan_name=plan.name expire=userplan.expire %}Your account has just been extended by {{ days }} days. Your current plan is {{ plan_name }} and it will expire on {{ expire }}. {% endblocktrans %}
سيتم إرسال فاتورة في رسالة بريد إلكتروني أخرى، في حال تم تقديم بيانات الفوترة.
شكراً لك
{% trans "An invoice will be sent with another e-mail, if billing data was provided." %}
{% trans "Thank you" %}
--
فريق تنحل
---------------------
Hi {% firstof user.get_full_name user.username %},
Your account has just been extended by {{ pricing.period }} days. Your current plan is {{plan.name}} and it will expire on {{userplan.expire}}.
An invoice will be sent with another e-mail, if billing data was provided.
Thank you
--
The Team at Tenhal
{% endautoescape %}
{% blocktrans %}The Team at {{ site_name }}{% endblocktrans %}
{% endautoescape %}

View File

@ -1,14 +1 @@
{% load i18n %}
{% autoescape off %}
مرحباً {% firstof user.get_full_name user.username %}،
تم تمديد حسابك {{ user }} لمدة {{ pricing.period }} يوم
شكراً لك
--
فريق تنحل
--------
Hi {% firstof user.get_full_name user.username %},
Your account {{ user }} has been extended by {{ pricing.period }} days
Thank you
--
The Team at Tenhal
{% endautoescape %}
{% load i18n %}{% blocktrans with user=user days=pricing.period %}Your account {{ user }} has been extended by {{ days }} days{% endblocktrans %}

View File

@ -1,21 +1,14 @@
{% load i18n %}{% autoescape off %}
مرحباً {% firstof user.get_full_name user.username %}،
نكتب إليك لإعلامك، أنه قد تم إصدار {{ invoice_type }} رقم {{ invoice_number }}. يمكنك الاطلاع عليها وطباعتها عبر الرابط:
{% trans "Hi" %} {% firstof user.get_full_name user.username %},
{% blocktrans %}We are writing to inform you, that {{ invoice_type }} {{ invoice_number }} has been issued. You can view it and print it at:
http://{{ site_domain }}{{ url }}
يمكنك الاطلاع على تفاصيل الطلب عبر الرابط:
{% endblocktrans %}
{% trans "Details of the order can be see on:" %}:
http://{{ site_domain }}{% url 'order' pk=order %}
شكراً لك
{% trans "Thank you" %}
--
فريق تنحل
---------------------
Hi {% firstof user.get_full_name user.username %},
We are writing to inform you, that {{ invoice_type }} {{ invoice_number }} has been issued. You can view it and print it at:
http://{{ site_domain }}{{ url }}
Details of the order can be see on:
http://{{ site_domain }}{% url 'order' pk=order %}
Thank you
--
The Team at Tenhal
{% endautoescape %}
{% blocktrans %}The Team at {{ site_name }}{% endblocktrans %}
{% endautoescape %}

View File

@ -1,3 +1 @@
{% load i18n %}
Order {{ order }} - {{ invoice_type }} {{ invoice_number }} has been issued for {{ user }}
تم إصدار {{ invoice_type }} {{ invoice_number }} للأمر {{ order }} باسم {{ user }}
{% load i18n %}{% trans 'Order' %} {{ order }} - {% blocktrans with invoice_type=invoice_type invoice_number=invoice_number user=user %}{{ invoice_type }} {{ invoice_number }} has been issued for {{ user }}{% endblocktrans %}

View File

@ -1,29 +1,10 @@
{% load i18n %}{% autoescape off %}
Hi {% firstof user.get_full_name user.username %},
{% trans "Hi" %} {% firstof user.get_full_name user.username %},
Your account will expire in {{ days }} days.
{% blocktrans %}Your account will expire in {{ days }} days.{% endblocktrans %}
You can extend your current plan {{ userplan.plan.name }} on page:
{% blocktrans with plan_name=userplan.plan.name %}You can extend your current plan {{ plan_name }} on page:{% endblocktrans %}
http://{{ site_domain }}{% url 'current_plan' %}
or you can upgrade your plan here:
http://{{ site_domain }}{% url 'upgrade_plan' %}
Thank you
--
The Team at Tenhal
----------------
مرحباً {% firstof full_name username %}،
سينتهي حسابك في غضون {{ days }} يوم.
يمكنك تمديد خطتك الحالية {{ userplan.plan.name }} على الصفحة التالية:
http://{{ site_domain }}{% url 'current_plan' %}
أو يمكنك ترقية خطتك هنا:
http://{{ site_domain }}{% url 'upgrade_plan' %}
شكراً لك
--
فريق تنحل
{% endautoescape %}
{% blocktrans %}or you can upgrade your plan here:{% endblocktrans %}
http://{{ site_domain }}{% url 'upgrade_plan' %}

View File

@ -1,3 +1 @@
{% load i18n %}
Your account {{ user }} will expire in {{ days }} day.
سينتهي حسابك {{ user }} في غضون {{ days }} يوم.
{% load i18n %}{% blocktrans count days as days %}Your account {{ user }} will expire in {{ days }} day{% plural %}Your account {{ user }} will expire in {{ days }} days{% endblocktrans %}

View File

@ -7,9 +7,10 @@
<input class="form-check-input"
{% if task.completed %}checked{% endif %}
type="checkbox"
hx-post="{% url 'update_schedule' request.dealer.slug task.pk %}"
hx-post="{% url 'update_schedule' request.dealer.slug task.pk %}"
hx-trigger="change"
hx-swap="outerHTML"
hx-swap="none"
hx-on:click="$(this).closest('tr').toggleClass('completed-task')"
hx-target="#task-{{ task.pk }}" />
</div>
</td>
@ -18,7 +19,7 @@
<div class="fs-10 d-block">{{ task.scheduled_type|capfirst }}</div>
</td>
<td class="sent align-middle white-space-nowrap text-start fw-thin text-body-tertiary py-2">{{ task.notes }}</td>
<td class="date align-middle white-space-nowrap text-body py-2">{{ task.created_at|naturalday|capfirst }}</td>
<td class="date align-middle white-space-nowrap text-body py-2">{{ task.scheduled_at|naturaltime|capfirst }}</td>
<td class="date align-middle white-space-nowrap text-body py-2">
{% if task.completed %}
<span class="badge badge-phoenix fs-10 badge-phoenix-success"><i class="fa-solid fa-check"></i></span>

View File

@ -4,8 +4,10 @@
{% block content %}
<div class="container mt-4">
<h2>{% trans "Recall History" %}</h2>
<div class="d-flex justify-content-between mb-3">
<h2>{% trans "Recall History" %}</h2>
<a href="{% url 'recall_filter' %}" class="btn btn-primary">{% trans "Create Recall" %}</a>
</div>
<div class="card mb-4">
<div class="card-body">
<div class="table-responsive">
@ -13,7 +15,7 @@
<thead class="thead-dark">
<tr>
<th>{% trans "Title" %}</th>
<th>{% trans "Sent" %}</th>
<th>{% trans "Sent At" %}</th>
<th>{% trans "Make" %}</th>
<th>{% trans "Model" %}</th>
<th>{% trans "Series" %}</th>
@ -28,7 +30,7 @@
<td>{{ recall.title }}</td>
<td>
<span title="{{ recall.created_at }}">
{{ recall.created_at|naturaltime }}
{{ recall.created_at|date }}
</span>
</td>
<td><img src="{{ recall.make.logo.url }}" width="50" height="50"> &nbsp;{{ recall.make|default:"-" }}</td>

View File

@ -8,7 +8,7 @@
<h4 class="alert-heading">{% trans "Recall Initiated Successfully!" %}</h4>
<p>{% trans "The recall has been created and notifications have been sent to all affected dealers." %}</p>
<hr>
<a href="{% url 'recall_filter' %}" class="btn btn-primary">
<a href="{% url 'recall_list' %}" class="btn btn-primary">
{% trans "Back to Recall Management" %}
</a>
</div>

View File

@ -137,7 +137,7 @@
{% endif %}
</div>
<div>
<div class="col-lg-8 col-md-10 needs-validation {% if not items or not customer_count %}d-none{% endif %}">
<div class="col-lg-12 col-md-10 needs-validation {% if not items or not customer_count %}d-none{% endif %}">
<div class="card shadow-sm border-0 rounded-3">
<div class="card-header bg-gray-200 py-3 border-0 rounded-top-3">
<h3 class="mb-0 fs-4 text-center">
@ -148,7 +148,6 @@
<form id="mainForm" method="post" class="needs-validation {% if not items and not customer_count %}d-none{% endif %}">
{% csrf_token %}
<div class="row g-3 col-12">
{{ form|crispy }}

View File

@ -0,0 +1,107 @@
{% extends 'base.html' %}
{% load i18n static custom_filters crispy_forms_filters %}
{% block title %}
{% trans 'Profile' %} {% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row align-items-center justify-content-between g-3 mb-4">
<div class="col-auto">
<h2 class="mb-0">{% trans 'Profile' %}</h2>
</div>
<div class="col-auto">
<div class="row g-2 g-sm-3">
<div class="col-auto">
<a class="btn btn-phoenix-primary"
href="{% url 'user_update' request.dealer.slug request.staff.slug %}"><span class="fas fa-edit me-2 text-primary"></span>{{ _("Edit") }} </a>
</div>
<div class="col-auto">
<a href="{% url 'staff_password_reset' request.dealer.slug staff.user.pk %}" 'staff_password_reset' request.dealer.slug user_.pk
class="btn btn-phoenix-danger"><span class="fas fa-key me-2"></span>{{ _("Change Password") }}</a>
</div>
</div>
</div>
</div>
<div class="row g-3">
<div class="col-12 col-lg-8">
<div class="card h-100">
<div class="card-body">
<div class="border-bottom border-dashed pb-4">
<div class="row align-items-center g-3 g-sm-5 text-center text-sm-start">
<div class="col-12 col-sm-auto">
<input class="d-none" id="avatarFile" type="file" />
<label class="cursor-pointer avatar avatar-5xl" for="avatarFile">
{% if staff.logo %}
<img src="{{ staff.logo.url }}"
alt="{{ staff.get_local_name }}"
class="rounded-circle"
style="max-width: 150px" />
{% else %}
<span class="rounded-circle feather feather-user text-body-tertiary"
style="max-width: 150px"></span>
<img src="{% static 'images/logos/logo.png' %}"
alt="{{ staff.get_local_name }}"
class=""
style="max-width: 150px" />
{% endif %}
</label>
</div>
<div class="col-12 col-sm-auto flex-1">
<h3>{{ staff.get_local_name }}</h3>
<p class="text-body-secondary">{{staff.user.groups.name}}</p>
<p class="text-body-secondary">{% trans 'Joined' %} {{ staff.created|timesince }} {% trans 'ago' %}</p>
<div></div>
</div>
</div>
</div>
<div class="d-flex flex-between-center pt-4">
<div>
<h6 class="mb-2 text-body-secondary">{% trans 'last login'|capfirst %}</h6>
<h4 class="fs-7 text-body-highlight mb-0">{{ staff.user.last_login|date:"D M d, Y H:i" }}</h4>
</div>
</div>
</div>
</div>
</div>
<div class="col-12 col-lg-4">
<div class="card h-100">
<div class="card-body">
<div class="border-bottom border-dashed">
<h4 class="mb-3">{% trans 'Default Address' %}</h4>
</div>
<div class="pt-4 mb-7 mb-lg-4 mb-xl-7">
<div class="row justify-content-between">
<div class="col-auto">
<h5 class="text-body-highlight">{% trans 'Address' %}</h5>
</div>
<div class="col-auto">
<p class="text-body-secondary">{{ staff.address }}</p>
</div>
</div>
</div>
<div class="border-top border-dashed pt-4">
<div class="row flex-between-center mb-2">
<div class="col-auto">
<h5 class="text-body-highlight mb-0">{% trans 'Email' %}</h5>
</div>
<div class="col-auto">{{ staff.user.email }}</div>
</div>
<div class="row flex-between-center">
<div class="col-auto">
<h5 class="text-body-highlight mb-0">{% trans 'Phone' %}</h5>
</div>
<div class="col-auto" dir="ltr">{{ staff.phone_number }}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -98,7 +98,7 @@
<i class="fa-regular fa-circle-left"></i>
</a>
<a class="btn btn-sm btn-phoenix-secondary"
href="{% url 'staff_password_reset' request.dealer.slug user_.pk %}">
href="{% url 'staff_password_reset' request.dealer.slug %}">
{{ _("Reset Password") }}
<i class="fa-solid fa-key"></i>
</a>