From 85515bd27239511c01f7b5d8764f9e5f22d193e3 Mon Sep 17 00:00:00 2001 From: ismail Date: Sun, 27 Jul 2025 16:42:02 +0300 Subject: [PATCH] update on the plans and more --- car_inventory/urls.py | 16 +- inventory/forms.py | 2 +- inventory/management/commands/p.py | 28 +++ .../management/commands/plans_maintenance.py | 72 +++++++ inventory/override.py | 1 - inventory/signals.py | 8 + inventory/tasks.py | 76 +++++++- inventory/urls.py | 2 +- inventory/views.py | 176 ++++++++++++------ .../email/email_confirmation_message.txt | 18 +- templates/account/password_change.html | 1 + templates/base.html | 4 +- templates/emails/expiration_reminder_ar.html | 18 ++ templates/emails/expiration_reminder_ar.txt | 8 + templates/emails/expiration_reminder_en.html | 14 ++ templates/emails/expiration_reminder_en.txt | 8 + templates/items/expenses/expense_create.html | 7 +- templates/ledger/bills/bill_form.html | 4 +- .../journal_entry/journal_entry_delete.html | 4 +- .../journal_entry_transactions.html | 2 +- templates/ledger/ledger/ledger_delete.html | 4 +- 21 files changed, 379 insertions(+), 94 deletions(-) create mode 100644 inventory/management/commands/p.py create mode 100644 inventory/management/commands/plans_maintenance.py create mode 100644 templates/emails/expiration_reminder_ar.html create mode 100644 templates/emails/expiration_reminder_ar.txt create mode 100644 templates/emails/expiration_reminder_en.html create mode 100644 templates/emails/expiration_reminder_en.txt diff --git a/car_inventory/urls.py b/car_inventory/urls.py index 1917a67b..b2a078d7 100644 --- a/car_inventory/urls.py +++ b/car_inventory/urls.py @@ -1,15 +1,15 @@ +from inventory import views +from django.conf import settings from django.contrib import admin from django.urls import path, include -from django.conf.urls.static import static -from django.conf import settings -from django.conf.urls.i18n import i18n_patterns -from inventory import views - # from debug_toolbar.toolbar import debug_toolbar_urls - -from inventory.notifications.sse import NotificationSSEApp -# import debug_toolbar from schema_graph.views import Schema +from django.conf.urls.static import static +from django.conf.urls.i18n import i18n_patterns +from inventory.notifications.sse import NotificationSSEApp + +# import debug_toolbar # from two_factor.urls import urlpatterns as tf_urls +# from debug_toolbar.toolbar import debug_toolbar_urls urlpatterns = [ # path('__debug__/', include(debug_toolbar.urls)), diff --git a/inventory/forms.py b/inventory/forms.py index 3cb7b982..57a396d1 100644 --- a/inventory/forms.py +++ b/inventory/forms.py @@ -1603,7 +1603,7 @@ class PermissionForm(forms.ModelForm): "django_ledger.invoicemodel", "django_ledger.vendormodel", "django_ledger.journalentrymodel" - "django_ledger.purchaseordermodel", # TODO add purchase order + "django_ledger.purchaseordermodel", ] permissions = cache.get( diff --git a/inventory/management/commands/p.py b/inventory/management/commands/p.py new file mode 100644 index 00000000..468a4223 --- /dev/null +++ b/inventory/management/commands/p.py @@ -0,0 +1,28 @@ +from django.core.management.base import BaseCommand +from django.contrib.auth import get_user_model +import datetime +from inventory.models import Dealer +from plans.models import Plan, Order,PlanPricing +User = get_user_model() +class Command(BaseCommand): + help = "" + + def handle(self, *args, **options): + dealer = Dealer.objects.get(user__email="dealer6@example.com") + user = dealer.user + + user.userplan.expire = datetime.datetime.now().date() + user.userplan.save() + pp = PlanPricing.objects.get(plan__name="Basic") + order = Order.objects.create( + user=user, + plan=pp.plan, + pricing=pp.pricing, + amount=pp.price, + currency="SA", + tax=15, + status=1, + ) + + order.complete_order() + print(user.userplan) \ No newline at end of file diff --git a/inventory/management/commands/plans_maintenance.py b/inventory/management/commands/plans_maintenance.py new file mode 100644 index 00000000..9e201e64 --- /dev/null +++ b/inventory/management/commands/plans_maintenance.py @@ -0,0 +1,72 @@ +from django.core.management.base import BaseCommand +from django.utils import timezone +from django.conf import settings +from django.template.loader import render_to_string +from plans.models import UserPlan, Order +from datetime import timedelta +from django.utils.translation import activate, get_language +from django_q.tasks import async_task +import logging +from inventory.tasks import send_bilingual_reminder, handle_email_result + +logger = logging.getLogger(__name__) + +class Command(BaseCommand): + help = "Handles subscription plan maintenance tasks" + + def handle(self, *args, **options): + self.stdout.write("Starting plans maintenance...") + + # 1. Send expiration reminders + self.send_expiration_reminders() + + # 2. Deactivate expired plans + self.deactivate_expired_plans() + + # 3. Clean up old incomplete orders + self.cleanup_old_orders() + + self.stdout.write("Maintenance completed!") + + def send_expiration_reminders(self): + """Queue email reminders for expiring plans""" + reminder_days = getattr(settings, 'PLANS_EXPIRATION_REMIND', [3, 7, 14]) + today = timezone.now().date() + + for days in reminder_days: + target_date = today + timedelta(days=days) + expiring_plans = UserPlan.objects.filter( + active=True, + expire=target_date + ).select_related('user', 'plan') + + self.stdout.write(f"Queuing {days}-day reminders for {expiring_plans.count()} plans") + + for user_plan in expiring_plans: + # Queue email task + async_task( + send_bilingual_reminder, + user_plan.user_id, + user_plan.plan_id, + user_plan.expire, + days, + hook=handle_email_result + ) + + def deactivate_expired_plans(self): + """Deactivate plans that have expired (synchronous)""" + expired_plans = UserPlan.objects.filter( + active=True, + expire__lt=timezone.now().date() + ) + count = expired_plans.update(active=False) + self.stdout.write(f"Deactivated {count} expired plans") + + def cleanup_old_orders(self): + """Delete incomplete orders older than 30 days""" + cutoff = timezone.now() - timedelta(days=30) + count, _ = Order.objects.filter( + created__lt=cutoff, + status=Order.STATUS.NEW + ).delete() + self.stdout.write(f"Cleaned up {count} old incomplete orders") \ No newline at end of file diff --git a/inventory/override.py b/inventory/override.py index a5fb6036..a929869b 100644 --- a/inventory/override.py +++ b/inventory/override.py @@ -321,7 +321,6 @@ class BasePurchaseOrderActionActionView( f"User {user_username} attempting to call action '{self.action_name}' " f"on Purchase Order ID: {po_model.pk} (Entity: {entity_slug})." ) - print(self.action_name) if self.action_name == "mark_as_fulfilled": try: if po_model.can_fulfill(): diff --git a/inventory/signals.py b/inventory/signals.py index 06188b8e..98b124f1 100644 --- a/inventory/signals.py +++ b/inventory/signals.py @@ -24,6 +24,7 @@ from . import models from django.utils.timezone import now from django.db import transaction from django_q.tasks import async_task +from plans.signals import order_completed, activate_user_plan # logging import logging @@ -1153,3 +1154,10 @@ def bill_model_after_approve_notification(sender, instance, created, **kwargs): please complete the bill payment. """, ) + + + +def handle_upgrade(sender, order, **kwargs): + logger.info(f"User {order.user} upgraded to {order.plan}") + +order_completed.connect(handle_upgrade) \ No newline at end of file diff --git a/inventory/tasks.py b/inventory/tasks.py index 36bcee2d..9f7e4e1c 100644 --- a/inventory/tasks.py +++ b/inventory/tasks.py @@ -1,11 +1,18 @@ + import logging +from plans.models import Plan +from django.conf import settings from django.db import transaction from django_ledger.io import roles from django_q.tasks import async_task from django.core.mail import send_mail from appointment.models import StaffMember +from django.utils.translation import activate +from django.contrib.auth import get_user_model from allauth.account.models import EmailAddress +from django.core.mail import EmailMultiAlternatives from inventory.models import DealerSettings, Dealer +from django.template.loader import render_to_string from django.utils.translation import gettext_lazy as _ from django.contrib.auth.models import User, Group, Permission @@ -1151,14 +1158,6 @@ def create_user_dealer(email, password, name, arabic_name, phone, crn, vrn, addr user.set_password(password) user.save() - #TODO remove this later - EmailAddress.objects.create( - user=user, - email=user.email, - verified=True, - primary=True - ) - group = Group.objects.create(name=f"{user.pk}-Admin") user.groups.add(group) for perm in Permission.objects.filter( @@ -1195,3 +1194,64 @@ def create_user_dealer(email, password, name, arabic_name, phone, crn, vrn, addr # instance.user.groups.add(group) # transaction.on_commit(run) + + + + +def send_bilingual_reminder(user_id, plan_id, expiration_date, days_until_expire): + """Send bilingual email reminder using Django-Q""" + try: + user = User.objects.get(id=user_id) + plan = Plan.objects.get(id=plan_id) + + # Determine user language preference + user_language = getattr(user, 'language', settings.LANGUAGE_CODE) + activate(user_language) + + # Context data + context = { + 'user': user, + 'plan': plan, + 'expiration_date': expiration_date, + 'days_until_expire': days_until_expire, + 'SITE_NAME': settings.SITE_NAME, + 'RENEWAL_URL': "url" ,#settings.RENEWAL_URL, + 'direction': 'rtl' if user_language.startswith('ar') else 'ltr' + } + + # Subject with translation + subject_en = f"Your {plan.name} subscription expires in {days_until_expire} days" + subject_ar = f"اشتراكك في {plan.name} ينتهي خلال {days_until_expire} أيام" + + # Render templates + text_content = render_to_string([ + f'emails/expiration_reminder_{user_language}.txt', + 'emails/expiration_reminder.txt' + ], context) + + html_content = render_to_string([ + f'emails/expiration_reminder_{user_language}.html', + 'emails/expiration_reminder.html' + ], context) + + # Create email + email = EmailMultiAlternatives( + subject=subject_ar if user_language.startswith('ar') else subject_en, + body=text_content, + from_email=settings.DEFAULT_FROM_EMAIL, + to=[user.email] + ) + email.attach_alternative(html_content, "text/html") + email.send() + + return f"Sent to {user.email} in {user_language}" + except Exception as e: + logger.error(f"Email failed: {str(e)}") + raise + +def handle_email_result(task): + """Callback for email results""" + if task.success: + logger.info(f"Email task succeeded: {task.result}") + else: + logger.error(f"Email task failed: {task.result}") \ No newline at end of file diff --git a/inventory/urls.py b/inventory/urls.py index 5e50f177..093ffcd9 100644 --- a/inventory/urls.py +++ b/inventory/urls.py @@ -701,7 +701,7 @@ urlpatterns = [ ), path( "/ledgers//delete//", - views.LedgerModelDeleteView.as_view(), + views.LedgerModelDeleteView, name="ledger-delete", ), path( diff --git a/inventory/views.py b/inventory/views.py index 6f420dd6..65a5b8ca 100644 --- a/inventory/views.py +++ b/inventory/views.py @@ -3979,7 +3979,7 @@ class BankAccountDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailV def get_queryset(self): dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"]) query = self.request.GET.get("q") - qs = self.model.objects.filter(entity=dealer.entity) + qs = self.model.objects.filter(entity_model=dealer.entity) if query: qs = apply_search_filters(qs, query) return qs @@ -4022,8 +4022,8 @@ class BankAccountUpdateView( dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"]) entity = dealer.entity kwargs = super().get_form_kwargs() - kwargs["entity_slug"] = entity.slug # Get entity_slug from URL - kwargs["user_model"] = entity.admin # Get user_model from the request + kwargs["entity_slug"] = entity.slug + kwargs["user_model"] = entity.admin return kwargs def get_form(self, form_class=None): @@ -4037,7 +4037,6 @@ class BankAccountUpdateView( ] ) form.fields["account_model"].queryset = account_qs - return form def get_success_url(self): @@ -8893,7 +8892,7 @@ class LedgerModelDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailV permission_required = "django_ledger.view_ledgermodel" -class LedgerModelCreateView(LedgerModelCreateViewBase): +class LedgerModelCreateView(LedgerModelCreateViewBase,SuccessMessageMixin): """ Handles the creation of LedgerModel entities. @@ -8909,6 +8908,7 @@ class LedgerModelCreateView(LedgerModelCreateViewBase): template_name = "ledger/ledger/ledger_form.html" permission_required = ["django_ledger.add_ledgermodel"] + success_message = _("Ledger created successfully") def get_form(self, form_class=None): return LedgerModelCreateForm( @@ -8918,10 +8918,11 @@ class LedgerModelCreateView(LedgerModelCreateViewBase): ) def form_valid(self, form): - form.field["entity"] = self.request.dealer.entity + form.fields["entity"] = self.request.dealer.entity return super().form_valid(form) def get_success_url(self): + messages.success(self.request, self.success_message) return reverse( "ledger_list", kwargs={ @@ -8955,34 +8956,47 @@ class LedgerModelModelActionView(LedgerModelModelActionViewBase): ) -class LedgerModelDeleteView(LedgerModelDeleteViewBase, SuccessMessageMixin): - """ - Handles the deletion of a Ledger model instance. - Provides functionality for rendering a confirmation template and deleting a - ledger instance from the system. Extends functionality for managing success - messages and redirections upon successful deletion. +@login_required +@permission_required("django_ledger.delete_ledgermodel", raise_exception=True) +def LedgerModelDeleteView(request, dealer_slug,entity_slug,ledger_pk): + ledger = LedgerModel.objects.filter(pk=ledger_pk).first() + if request.method == "POST": + ledger.delete() + messages.success(request, _("Ledger deleted successfully")) + return redirect("ledger_list", dealer_slug=dealer_slug, entity_slug=entity_slug) + return render(request,"ledger/ledger/ledger_delete.html",{"ledger_model":ledger}) +# class LedgerModelDeleteView(DeleteView, SuccessMessageMixin): +# """ +# Handles the deletion of a Ledger model instance. - :ivar template_name: Path to the template used for rendering the delete - confirmation view. - :type template_name: str - :ivar success_message: Success message displayed upon successful deletion - of the ledger instance. - :type success_message: str - """ +# Provides functionality for rendering a confirmation template and deleting a +# ledger instance from the system. Extends functionality for managing success +# messages and redirections upon successful deletion. - template_name = "ledger/ledger/ledger_delete.html" - success_message = _("Ledger deleted successfully") - permission_required = ["django_ledger.delete_ledgermodel"] +# :ivar template_name: Path to the template used for rendering the delete +# confirmation view. +# :type template_name: str +# :ivar success_message: Success message displayed upon successful deletion +# of the ledger instance. +# :type success_message: str +# """ - def get_success_url(self): - return reverse( - "ledger_list", - kwargs={ - "dealer_slug": self.kwargs["dealer_slug"], - "entity_slug": self.kwargs["entity_slug"], - }, - ) +# template_name = "ledger/ledger/ledger_delete.html" +# pk_url_kwarg = 'ledger_pk' +# context_object_name = 'ledger_model' + +# success_message = _("Ledger deleted successfully") +# permission_required = ["django_ledger.delete_ledgermodel"] + +# def get_success_url(self): +# return reverse( +# "ledger_list", +# kwargs={ +# "dealer_slug": self.kwargs["dealer_slug"], +# "entity_slug": self.kwargs["entity_slug"], +# }, +# ) class JournalEntryListView(LoginRequiredMixin, PermissionRequiredMixin, ListView): @@ -9076,7 +9090,7 @@ class JournalEntryCreateView( @login_required @permission_required("django_ledger.delete_journalentrymodel", raise_exception=True) -def JournalEntryDeleteView(request, pk): +def JournalEntryDeleteView(request,dealer_slug, pk): """ Handles the deletion of a specific journal entry. This view facilitates the deletion of a journal entry identified by its primary key (pk). If the @@ -9097,10 +9111,10 @@ def JournalEntryDeleteView(request, pk): ledger = journal_entry.ledger if not journal_entry.can_delete(): messages.error(request, _("Journal Entry cannot be deleted")) - return redirect("journalentry_list", pk=ledger.pk) + return redirect("journalentry_list",dealer_slug=dealer_slug, pk=ledger.pk) journal_entry.delete() messages.success(request, "Journal Entry deleted") - return redirect("journalentry_list", pk=ledger.pk) + return redirect("journalentry_list",dealer_slug=dealer_slug, pk=ledger.pk) return render( request, "ledger/journal_entry/journal_entry_delete.html", @@ -9330,39 +9344,91 @@ 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") - order = Order.objects.filter(user=dealer.user, status=1).first() + order = Order.objects.filter(user=dealer.user, status=1).first() # Status 1 = NEW if payment_status == "paid": + # Get or create billing info (optional step) billing_info, created = BillingInfo.objects.get_or_create( user=dealer.user, - tax_number=dealer.vrn, - name=dealer.arabic_name, - street=dealer.address, - zipcode=dealer.entity.zip_code if dealer.entity.zip_code else " ", - city=dealer.entity.city if dealer.entity.city else " ", - country=dealer.entity.country if dealer.entity.country else " ", + defaults={ + 'tax_number': dealer.vrn, + 'name': dealer.arabic_name, + 'street': dealer.address, + 'zipcode': dealer.entity.zip_code or " ", + 'city': dealer.entity.city or " ", + 'country': dealer.entity.country or " ", + } ) - if created: - userplan = UserPlan.objects.create( - user=request.user, - plan=order.plan, - active=True, - ) - userplan.initialize() - order.complete_order() - history.status = "paid" - history.save() - invoice = order.get_invoices().first() - return render( - request, "payment_success.html", {"order": order, "invoice": invoice} - ) + try: + # COMPLETE THE ORDER - This handles plan activation/upgrade + order.complete_order() # Critical step: activates the plan + + # Update payment history + history.status = "paid" + history.save() + + # Retrieve invoice + invoice = order.get_invoices().first() + return render( + request, + "payment_success.html", + {"order": order, "invoice": invoice} + ) + + except Exception as e: + # Handle activation errors (log, notify admin, etc.) + 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": history.status = "failed" history.save() + return render(request, "payment_failed.html", {"message": message}) - return render(request, "payment_failed.html", {"message": message}) + # Handle unexpected status + return render(request, "payment_failed.html", {"message": "Unknown payment status"}) +# def payment_callback(request, dealer_slug): +# message = request.GET.get("message") +# dealer = get_object_or_404(models.Dealer, slug=dealer_slug) +# payment_id = request.GET.get("id") +# history = models.PaymentHistory.objects.filter(transaction_id=payment_id).first() +# payment_status = request.GET.get("status") +# order = Order.objects.filter(user=dealer.user, status=1).first() + +# if payment_status == "paid": +# billing_info, created = BillingInfo.objects.get_or_create( +# user=dealer.user, +# tax_number=dealer.vrn, +# name=dealer.arabic_name, +# street=dealer.address, +# zipcode=dealer.entity.zip_code if dealer.entity.zip_code else " ", +# city=dealer.entity.city if dealer.entity.city else " ", +# country=dealer.entity.country if dealer.entity.country else " ", +# ) +# if created: +# userplan = UserPlan.objects.create( +# user=request.user, +# plan=order.plan, +# active=True, +# ) +# userplan.initialize() + +# order.complete_order() +# history.status = "paid" +# history.save() +# invoice = order.get_invoices().first() +# return render( +# request, "payment_success.html", {"order": order, "invoice": invoice} +# ) + +# elif payment_status == "failed": +# history.status = "failed" +# history.save() + +# return render(request, "payment_failed.html", {"message": message}) @login_required diff --git a/templates/account/email/email_confirmation_message.txt b/templates/account/email/email_confirmation_message.txt index ed9d0093..d19e3758 100644 --- a/templates/account/email/email_confirmation_message.txt +++ b/templates/account/email/email_confirmation_message.txt @@ -1,9 +1,17 @@ {% extends "account/email/base_message.txt" %} -{% load account %} -{% load i18n %} +{% load account i18n %} -{% block content %}{% autoescape off %}{% user_display user as user_display %}{% blocktranslate with site_name=current_site.name site_domain=current_site.domain %}You're receiving this email because user {{ user_display }} has given your email address to register an account on {{ site_domain }}.{% endblocktranslate %} +{% block content %}{% autoescape off %}{% user_display user as user_display %} +{% blocktranslate with site_domain=current_site.domain %}تتلقى هذا البريد لأن {{ user_display }} استخدم بريدك للتسجيل في {{ site_domain }}.{% endblocktranslate %} -{% if code %}{% blocktranslate %}Your email verification code is listed below. Please enter it in your open browser window.{% endblocktranslate %} +{% if code %} +{% blocktranslate %}أدخل رمز التحقق في المتصفح:{% endblocktranslate %} -{{ code }}{% else %}{% blocktranslate %}To confirm this is correct, go to {{ activate_url }}{% endblocktranslate %}{% endif %}{% endautoescape %}{% endblock content %} +{{ code }} + +{% blocktranslate %}ينتهي صلاحية هذا الرمز خلال {{ code_expiration }} دقيقة.{% endblocktranslate %} +{% else %} +{% blocktranslate %}أكد هذا التسجيل بالزيارة:{% endblocktranslate %} + +{{ activate_url }} +{% endif %}{% endautoescape %}{% endblock content %} \ No newline at end of file diff --git a/templates/account/password_change.html b/templates/account/password_change.html index 8d7af6dc..e5d93de8 100644 --- a/templates/account/password_change.html +++ b/templates/account/password_change.html @@ -27,6 +27,7 @@

{% trans "Change Password" %}

diff --git a/templates/base.html b/templates/base.html index 6da18490..03e3a73c 100644 --- a/templates/base.html +++ b/templates/base.html @@ -181,9 +181,9 @@ document.addEventListener('htmx:afterRequest', function(evt) { return alert("Error: Could Not Find Resource"); } if (evt.detail.successful != true) { - console.log(evt) + console.log(evt.detail.xhr.statusText) /* Notify of an unexpected error, & print error to console */ - notify("error", "Unexpected Error"); + notify("error", `Unexpected Error ,${evt.detail.xhr.statusText}`); } }); document.body.addEventListener('htmx:beforeSwap', function(evt) { diff --git a/templates/emails/expiration_reminder_ar.html b/templates/emails/expiration_reminder_ar.html new file mode 100644 index 00000000..421aea38 --- /dev/null +++ b/templates/emails/expiration_reminder_ar.html @@ -0,0 +1,18 @@ + + + +

مرحباً {{ user.get_full_name }}،

+ +

+ اشتراكك في {{ plan.name }} سينتهي خلال + {{ days_until_expire }} يوم في {{ expiration_date|date:"j F Y" }}. +

+ +

+ جدد اشتراكك الآن لمواصلة الخدمة. +

+ +

مع أطيب التحيات،
+ فريق {{ SITE_NAME }}

+ + \ No newline at end of file diff --git a/templates/emails/expiration_reminder_ar.txt b/templates/emails/expiration_reminder_ar.txt new file mode 100644 index 00000000..28d5e3bf --- /dev/null +++ b/templates/emails/expiration_reminder_ar.txt @@ -0,0 +1,8 @@ +مرحباً {{ user.get_full_name }}، + +اشتراكك في {{ plan.name }} سينتهي خلال {{ days_until_expire }} يوم في {{ expiration_date|date:"j F Y" }}. + +جدد اشتراكك الآن: {{ RENEWAL_URL }} + +مع أطيب التحيات، +فريق {{ SITE_NAME }} \ No newline at end of file diff --git a/templates/emails/expiration_reminder_en.html b/templates/emails/expiration_reminder_en.html new file mode 100644 index 00000000..515fd007 --- /dev/null +++ b/templates/emails/expiration_reminder_en.html @@ -0,0 +1,14 @@ + + + +

Hello {{ user.get_full_name }},

+ +

Your {{ plan.name }} subscription will expire + in {{ days_until_expire }} days on {{ expiration_date|date:"F j, Y" }}.

+ +

Renew now to continue service.

+ +

Best regards,
+ The {{ SITE_NAME }} Team

+ + \ No newline at end of file diff --git a/templates/emails/expiration_reminder_en.txt b/templates/emails/expiration_reminder_en.txt new file mode 100644 index 00000000..65d5dc2a --- /dev/null +++ b/templates/emails/expiration_reminder_en.txt @@ -0,0 +1,8 @@ +Hello {{ user.get_full_name }}, + +Your {{ plan.name }} subscription will expire in {{ days_until_expire }} days on {{ expiration_date|date:"F j, Y" }}. + +Renew now: {{ RENEWAL_URL }} + +Best regards, +{{ SITE_NAME }} Team \ No newline at end of file diff --git a/templates/items/expenses/expense_create.html b/templates/items/expenses/expense_create.html index abf6fd0f..f9f97b36 100644 --- a/templates/items/expenses/expense_create.html +++ b/templates/items/expenses/expense_create.html @@ -6,7 +6,7 @@ {{ _("Add New Expense") }} {% endblock title %} {% block content %} - +
@@ -19,7 +19,6 @@
- {% csrf_token %} {{ form|crispy }} @@ -29,11 +28,7 @@ {% trans "Cancel" %}
-
- - - diff --git a/templates/ledger/bills/bill_form.html b/templates/ledger/bills/bill_form.html index 22d62107..2ab0e8bc 100644 --- a/templates/ledger/bills/bill_form.html +++ b/templates/ledger/bills/bill_form.html @@ -5,7 +5,7 @@ {{ _("Create Bill") }} {% endblock title %} {% block content %} -
+

{% trans "Create Bill" %}

{% csrf_token %} @@ -15,7 +15,7 @@ - {% trans "Cancel" %}
diff --git a/templates/ledger/journal_entry/journal_entry_delete.html b/templates/ledger/journal_entry/journal_entry_delete.html index cbb0fd4d..2416d384 100644 --- a/templates/ledger/journal_entry/journal_entry_delete.html +++ b/templates/ledger/journal_entry/journal_entry_delete.html @@ -5,7 +5,7 @@ {% block content %}
-
{% csrf_token %}
@@ -13,7 +13,7 @@
Are you sure you want to delete?
- {% trans 'Go Back' %}
diff --git a/templates/ledger/journal_entry/journal_entry_transactions.html b/templates/ledger/journal_entry/journal_entry_transactions.html index 44a38703..fc5f4148 100644 --- a/templates/ledger/journal_entry/journal_entry_transactions.html +++ b/templates/ledger/journal_entry/journal_entry_transactions.html @@ -27,7 +27,7 @@ {% for transaction in transactions %} {{ forloop.counter }} - {{ transaction.created|date }} + {{ transaction.created|date }} {{ transaction.account.name }} {{ transaction.account.code }} diff --git a/templates/ledger/ledger/ledger_delete.html b/templates/ledger/ledger/ledger_delete.html index 93492607..5f06aaa4 100644 --- a/templates/ledger/ledger/ledger_delete.html +++ b/templates/ledger/ledger/ledger_delete.html @@ -5,7 +5,7 @@ {% block content %}
- {% csrf_token %}
@@ -13,7 +13,7 @@
{{ ledger_model.get_delete_message }}
- {% trans 'Go Back' %} + {% trans 'Go Back' %}