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 services associated with a car finance application.
""" """
# additional_finances = forms.ModelMultipleChoiceField( def clean(self):
# queryset=AdditionalServices.objects.all(), cleaned_data = super().clean()
# widget=forms.CheckboxSelectMultiple(attrs={"class": "form-check-input"}), cost_price = cleaned_data.get("cost_price")
# required=False, 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: class Meta:
model = CarFinance model = CarFinance
fields = ["cost_price","marked_price"] 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): class CarLocationForm(forms.ModelForm):
""" """
@ -1172,6 +1167,7 @@ class ScheduleForm(forms.ModelForm):
scheduled_at = forms.DateTimeField( scheduled_at = forms.DateTimeField(
widget=DateTimeInput(attrs={"type": "datetime-local"}) widget=DateTimeInput(attrs={"type": "datetime-local"})
) )
reminder = forms.BooleanField(help_text=_("Send a reminder?"),required=False)
class Meta: class Meta:
model = Schedule model = Schedule

View File

@ -742,7 +742,9 @@ class Car(Base):
) )
except Exception: except Exception:
return False 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): def get_transfer(self):
return self.transfer_logs.filter(active=True).first() 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('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.RecallListView.as_view(), name='recall_list'),
path('feature/recall/', views.RecallFilterView, name='recall_filter'), 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/<int:pk>/view/', views.RecallDetailView.as_view(), name='recall_detail'),
path('feature/recall/create/', views.RecallCreateView.as_view(), name='recall_create'), path('feature/recall/create/', views.RecallCreateView.as_view(), name='recall_create'),
path('feature/recall/success/', views.RecallSuccessView.as_view(), name='recall_success'), path('feature/recall/success/', views.RecallSuccessView.as_view(), name='recall_success'),
path('<slug:dealer_slug>/schedules/calendar/', views.schedule_calendar, name='schedule_calendar'), 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" handler404 = "inventory.views.custom_page_not_found_view"

View File

@ -2197,6 +2197,35 @@ class DealerUpdateView(
def get_success_url(self): def get_success_url(self):
return reverse("dealer_detail", kwargs={"slug": self.object.slug}) 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): class CustomerListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
@ -4912,10 +4941,19 @@ def update_estimate_discount(request, dealer_slug, pk):
object_id=estimate.pk, object_id=estimate.pk,
) )
discount_amount = request.POST.get("discount_amount", 0) 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.data.update({"discount": Decimal(discount_amount)})
extra_info.save() extra_info.save()
messages.success(request, "Discount updated successfully")
return redirect("estimate_detail", dealer_slug=dealer_slug, pk=pk) 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) form = forms.ScheduleForm(request.POST)
if form.is_valid(): if form.is_valid():
reminder = form.cleaned_data['reminder']
instance = form.save(commit=False) instance = form.save(commit=False)
instance.dealer = dealer instance.dealer = dealer
instance.content_object = obj instance.content_object = obj
@ -6581,20 +6620,21 @@ def schedule_event(request, dealer_slug, content_type, slug):
created_by=request.user, created_by=request.user,
activity_type=instance.scheduled_type, 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) reminder_time = scheduled_at_aware - timezone.timedelta(minutes=15)
# Only schedule if the reminder time is in the future # Only schedule if the reminder time is in the future
# Reminder emails are scheduled to be sent 15 minutes before the scheduled time # Reminder emails are scheduled to be sent 15 minutes before the scheduled time
if reminder_time > timezone.now(): if reminder_time > timezone.now():
DjangoQSchedule.objects.create( DjangoQSchedule.objects.create(
name=f"send_schedule_reminder_email_to_{instance.scheduled_by.email}_for_{content_type}_with_PK_{instance.pk}", 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', func='inventory.tasks.send_schedule_reminder_email',
args=f'"{instance.pk}"', args=f'"{instance.pk}"',
schedule_type=DjangoQSchedule.ONCE, schedule_type=DjangoQSchedule.ONCE,
next_run=reminder_time, next_run=reminder_time,
hook='inventory.tasks.log_email_status', hook='inventory.tasks.log_email_status',
) )
messages.success(request, _("Appointment Created Successfully")) messages.success(request, _("Appointment Created Successfully"))
return redirect(f'{content_type}_detail',dealer_slug=dealer_slug, slug=slug) return redirect(f'{content_type}_detail',dealer_slug=dealer_slug, slug=slug)
@ -9456,10 +9496,12 @@ def submit_plan(request, dealer_slug):
tax=15, tax=15,
status=1, status=1,
) )
logger.info(f"order created {order}")
except Exception as e: except Exception as e:
print(e) logger.error(e)
if not order: if not order:
messages.error(request, _("Error creating order")) messages.error(request, _("Error creating order"))
logger.error("unable to create order")
return redirect("pricing_page", dealer_slug=dealer_slug) return redirect("pricing_page", dealer_slug=dealer_slug)
transaction_url = handle_payment(request, order) transaction_url = handle_payment(request, order)
return redirect(transaction_url) return redirect(transaction_url)
@ -9473,9 +9515,13 @@ def payment_callback(request, dealer_slug):
payment_id = request.GET.get("id") payment_id = request.GET.get("id")
history = models.PaymentHistory.objects.filter(transaction_id=payment_id).first() history = models.PaymentHistory.objects.filter(transaction_id=payment_id).first()
payment_status = request.GET.get("status") 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 order = Order.objects.filter(user=dealer.user, status=1).first() # Status 1 = NEW
print(order) print(order)
if payment_status == "paid": 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( billing_info, created = BillingInfo.objects.get_or_create(
user=dealer.user, user=dealer.user,
defaults={ defaults={
@ -9487,12 +9533,20 @@ def payment_callback(request, dealer_slug):
'country': dealer.entity.country or " ", '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'): if not hasattr(order.user, 'userplan'):
UserPlan.objects.create( UserPlan.objects.create(
user=order.user, user=order.user,
plan=order.plan, plan=order.plan,
expire=datetime.now().date() + timedelta(days=order.get_plan_pricing().pricing.period) 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: try:
@ -9510,7 +9564,7 @@ def payment_callback(request, dealer_slug):
order.complete_order() order.complete_order()
history.status = "paid" history.status = "paid"
history.save() history.save()
logger.info(f"Order {order.id} for user {order.user} completed successfully. Payment history updated.")
invoice = order.get_invoices().first() invoice = order.get_invoices().first()
return render( return render(
request, request,
@ -9519,12 +9573,14 @@ def payment_callback(request, dealer_slug):
) )
except Exception as e: 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)}") logger.error(f"Plan activation failed: {str(e)}")
history.status = "failed" history.status = "failed"
history.save() history.save()
return render(request, "payment_failed.html", {"message": "Plan activation error"}) return render(request, "payment_failed.html", {"message": "Plan activation error"})
elif payment_status == "failed": elif payment_status == "failed":
logger.warning(f"Payment failed for transaction ID {payment_id}. Message: {message}")
history.status = "failed" history.status = "failed"
history.save() history.save()
return render(request, "payment_failed.html", {"message": message}) 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) @permission_required("inventory.change_schedule", raise_exception=True)
def update_schedule(request, dealer_slug, pk): def update_schedule(request, dealer_slug, pk):
task = get_object_or_404(models.Schedule, pk=pk) task = get_object_or_404(models.Schedule, pk=pk)
if request.method == "POST": if request.method == "POST":
task.completed = False if task.completed else True task.completed = False if task.completed else True
task.save() task.save()
print("task")
return render(request, "partials/task.html", {"task": 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%; top: 50%;
left: 50%; left: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
z-index: 10; z-index: 9999;
pointer-events: none;
} }
#spinner-bg { #spinner-bg {
@ -150,11 +151,14 @@ html[dir="rtl"] .form-icon-container .form-control {
background-color: rgba(255, 255, 255, 0.7); background-color: rgba(255, 255, 255, 0.7);
opacity: 0; opacity: 0;
transition: opacity 500ms ease-in; transition: opacity 500ms ease-in;
z-index: 5; visibility: hidden;
z-index: 10000;
pointer-events: none;
} }
#spinner-bg.htmx-request { #spinner-bg.htmx-request {
opacity: .8; opacity: .8;
visibility: visible;
} }

View File

@ -84,10 +84,10 @@
{% include "plans/expiration_messages.html" %} {% include "plans/expiration_messages.html" %}
{% block period_navigation %} {% block period_navigation %}
{% endblock 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=""> <img src="{% static 'spinner.svg' %}" width="100" height="100" alt="">
</div> </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 customCSS %}{% endblock %}
{% block content %}{% endblock content %} {% block content %}{% endblock content %}
{% block customJS %}{% endblock %} {% block customJS %}{% endblock %}

View File

@ -47,13 +47,13 @@
{% endif %} {% endif %}
{% if perms.inventory.add_car %} {% 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 %}"> <a class="nav-link" href="{% url 'upload_cars' request.dealer.slug %}">
<div class="d-flex align-items-center"> <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> <span class="nav-link-icon"><span class="fas fa-file-import"></span></span><span class="nav-link-text">{% trans "Bulk Upload"|capfirst %}</span>
</div> </div>
</a> </a>
</li> </li> {% endcomment %}
{% endif %} {% endif %}
{% if perms.django_ledger.view_purchaseordermodel %} {% if perms.django_ledger.view_purchaseordermodel %}
<li class="nav-item"> <li class="nav-item">
@ -504,7 +504,7 @@
</li> </li>
{% else %} {% else %}
<li class="nav-item"> <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> </li>
{% endif %} {% endif %}
{% if request.is_dealer %} {% if request.is_dealer %}
@ -530,7 +530,7 @@
</li> </li>
{% if request.is_staff %} {% if request.is_staff %}
<li class="nav-item"> <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> </li>
{% endif %} {% endif %}
<!--<li class="nav-item"><a class="nav-link px-3 d-block" href=""> Language</a></li>--> <!--<li class="nav-item"><a class="nav-link px-3 d-block" href=""> Language</a></li>-->

View File

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

View File

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

View File

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

View File

@ -1,27 +1,14 @@
{% load i18n %}{% autoescape off %} {% 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' %} http://{{ site_domain }}{% url 'current_plan' %}
أو يمكنك ترقية خطتك من هنا: {% blocktrans %}or you can upgrade your plan here:{% endblocktrans %}
http://{{ site_domain }}{% url 'upgrade_plan' %} http://{{ site_domain }}{% url 'upgrade_plan' %}
شكراً لك، {% trans "Thank you" %}
-- --
فريق تنحل {% blocktrans %}The Team at {{ site_name }}{% endblocktrans %}
--------------------- {% endautoescape %}
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 %}

View File

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

View File

@ -1,20 +1,11 @@
{% load i18n %}{% autoescape off %} {% 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" %}
-- --
فريق تنحل {% blocktrans %}The Team at {{ site_name }}{% endblocktrans %}
--------------------- {% endautoescape %}
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 %}

View File

@ -1,14 +1 @@
{% load i18n %} {% load i18n %}{% blocktrans with user=user days=pricing.period %}Your account {{ user }} has been extended by {{ days }} days{% endblocktrans %}
{% 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 %}

View File

@ -1,21 +1,14 @@
{% load i18n %}{% autoescape off %} {% load i18n %}{% autoescape off %}
مرحباً {% firstof user.get_full_name user.username %}، {% trans "Hi" %} {% firstof user.get_full_name user.username %},
نكتب إليك لإعلامك، أنه قد تم إصدار {{ invoice_type }} رقم {{ invoice_number }}. يمكنك الاطلاع عليها وطباعتها عبر الرابط:
{% 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 }} http://{{ site_domain }}{{ url }}
يمكنك الاطلاع على تفاصيل الطلب عبر الرابط: {% endblocktrans %}
{% trans "Details of the order can be see on:" %}:
http://{{ site_domain }}{% url 'order' pk=order %} http://{{ site_domain }}{% url 'order' pk=order %}
شكراً لك {% trans "Thank you" %}
-- --
فريق تنحل {% blocktrans %}The Team at {{ site_name }}{% endblocktrans %}
--------------------- {% endautoescape %}
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 %}

View File

@ -1,3 +1 @@
{% load i18n %} {% 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 %}
Order {{ order }} - {{ invoice_type }} {{ invoice_number }} has been issued for {{ user }}
تم إصدار {{ invoice_type }} {{ invoice_number }} للأمر {{ order }} باسم {{ user }}

View File

@ -1,29 +1,10 @@
{% load i18n %}{% autoescape off %} {% 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' %} http://{{ site_domain }}{% url 'current_plan' %}
or you can upgrade your plan here: {% blocktrans %}or you can upgrade your plan here:{% endblocktrans %}
http://{{ site_domain }}{% url 'upgrade_plan' %} 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 %}

View File

@ -1,3 +1 @@
{% load i18n %} {% 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 %}
Your account {{ user }} will expire in {{ days }} day.
سينتهي حسابك {{ user }} في غضون {{ days }} يوم.

View File

@ -7,9 +7,10 @@
<input class="form-check-input" <input class="form-check-input"
{% if task.completed %}checked{% endif %} {% if task.completed %}checked{% endif %}
type="checkbox" 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-trigger="change"
hx-swap="outerHTML" hx-swap="none"
hx-on:click="$(this).closest('tr').toggleClass('completed-task')"
hx-target="#task-{{ task.pk }}" /> hx-target="#task-{{ task.pk }}" />
</div> </div>
</td> </td>
@ -18,7 +19,7 @@
<div class="fs-10 d-block">{{ task.scheduled_type|capfirst }}</div> <div class="fs-10 d-block">{{ task.scheduled_type|capfirst }}</div>
</td> </td>
<td class="sent align-middle white-space-nowrap text-start fw-thin text-body-tertiary py-2">{{ task.notes }}</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"> <td class="date align-middle white-space-nowrap text-body py-2">
{% if task.completed %} {% if task.completed %}
<span class="badge badge-phoenix fs-10 badge-phoenix-success"><i class="fa-solid fa-check"></i></span> <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 %} {% block content %}
<div class="container mt-4"> <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 mb-4">
<div class="card-body"> <div class="card-body">
<div class="table-responsive"> <div class="table-responsive">
@ -13,7 +15,7 @@
<thead class="thead-dark"> <thead class="thead-dark">
<tr> <tr>
<th>{% trans "Title" %}</th> <th>{% trans "Title" %}</th>
<th>{% trans "Sent" %}</th> <th>{% trans "Sent At" %}</th>
<th>{% trans "Make" %}</th> <th>{% trans "Make" %}</th>
<th>{% trans "Model" %}</th> <th>{% trans "Model" %}</th>
<th>{% trans "Series" %}</th> <th>{% trans "Series" %}</th>
@ -28,7 +30,7 @@
<td>{{ recall.title }}</td> <td>{{ recall.title }}</td>
<td> <td>
<span title="{{ recall.created_at }}"> <span title="{{ recall.created_at }}">
{{ recall.created_at|naturaltime }} {{ recall.created_at|date }}
</span> </span>
</td> </td>
<td><img src="{{ recall.make.logo.url }}" width="50" height="50"> &nbsp;{{ recall.make|default:"-" }}</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> <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> <p>{% trans "The recall has been created and notifications have been sent to all affected dealers." %}</p>
<hr> <hr>
<a href="{% url 'recall_filter' %}" class="btn btn-primary"> <a href="{% url 'recall_list' %}" class="btn btn-primary">
{% trans "Back to Recall Management" %} {% trans "Back to Recall Management" %}
</a> </a>
</div> </div>

View File

@ -137,7 +137,7 @@
{% endif %} {% endif %}
</div> </div>
<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 shadow-sm border-0 rounded-3">
<div class="card-header bg-gray-200 py-3 border-0 rounded-top-3"> <div class="card-header bg-gray-200 py-3 border-0 rounded-top-3">
<h3 class="mb-0 fs-4 text-center"> <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 %}"> <form id="mainForm" method="post" class="needs-validation {% if not items and not customer_count %}d-none{% endif %}">
{% csrf_token %} {% csrf_token %}
<div class="row g-3 col-12"> <div class="row g-3 col-12">
{{ form|crispy }} {{ 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> <i class="fa-regular fa-circle-left"></i>
</a> </a>
<a class="btn btn-sm btn-phoenix-secondary" <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") }} {{ _("Reset Password") }}
<i class="fa-solid fa-key"></i> <i class="fa-solid fa-key"></i>
</a> </a>