Merge branch 'main' of http://10.10.1.136:3000/ismail/haikal into frontend
This commit is contained in:
commit
ac9727dde4
@ -1,15 +1,15 @@
|
|||||||
|
from inventory import views
|
||||||
|
from django.conf import settings
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import path, include
|
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 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 two_factor.urls import urlpatterns as tf_urls
|
||||||
|
# from debug_toolbar.toolbar import debug_toolbar_urls
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# path('__debug__/', include(debug_toolbar.urls)),
|
# path('__debug__/', include(debug_toolbar.urls)),
|
||||||
|
|||||||
@ -1603,7 +1603,7 @@ class PermissionForm(forms.ModelForm):
|
|||||||
"django_ledger.invoicemodel",
|
"django_ledger.invoicemodel",
|
||||||
"django_ledger.vendormodel",
|
"django_ledger.vendormodel",
|
||||||
"django_ledger.journalentrymodel"
|
"django_ledger.journalentrymodel"
|
||||||
"django_ledger.purchaseordermodel", # TODO add purchase order
|
"django_ledger.purchaseordermodel",
|
||||||
]
|
]
|
||||||
|
|
||||||
permissions = cache.get(
|
permissions = cache.get(
|
||||||
|
|||||||
28
inventory/management/commands/p.py
Normal file
28
inventory/management/commands/p.py
Normal file
@ -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)
|
||||||
72
inventory/management/commands/plans_maintenance.py
Normal file
72
inventory/management/commands/plans_maintenance.py
Normal file
@ -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")
|
||||||
@ -321,7 +321,6 @@ class BasePurchaseOrderActionActionView(
|
|||||||
f"User {user_username} attempting to call action '{self.action_name}' "
|
f"User {user_username} attempting to call action '{self.action_name}' "
|
||||||
f"on Purchase Order ID: {po_model.pk} (Entity: {entity_slug})."
|
f"on Purchase Order ID: {po_model.pk} (Entity: {entity_slug})."
|
||||||
)
|
)
|
||||||
print(self.action_name)
|
|
||||||
if self.action_name == "mark_as_fulfilled":
|
if self.action_name == "mark_as_fulfilled":
|
||||||
try:
|
try:
|
||||||
if po_model.can_fulfill():
|
if po_model.can_fulfill():
|
||||||
|
|||||||
@ -24,6 +24,7 @@ from . import models
|
|||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django_q.tasks import async_task
|
from django_q.tasks import async_task
|
||||||
|
from plans.signals import order_completed, activate_user_plan
|
||||||
|
|
||||||
# logging
|
# logging
|
||||||
import logging
|
import logging
|
||||||
@ -1153,3 +1154,10 @@ def bill_model_after_approve_notification(sender, instance, created, **kwargs):
|
|||||||
please complete the bill payment.
|
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)
|
||||||
@ -1,11 +1,18 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
from plans.models import Plan
|
||||||
|
from django.conf import settings
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django_ledger.io import roles
|
from django_ledger.io import roles
|
||||||
from django_q.tasks import async_task
|
from django_q.tasks import async_task
|
||||||
from django.core.mail import send_mail
|
from django.core.mail import send_mail
|
||||||
from appointment.models import StaffMember
|
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 allauth.account.models import EmailAddress
|
||||||
|
from django.core.mail import EmailMultiAlternatives
|
||||||
from inventory.models import DealerSettings, Dealer
|
from inventory.models import DealerSettings, Dealer
|
||||||
|
from django.template.loader import render_to_string
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.contrib.auth.models import User, Group, Permission
|
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.set_password(password)
|
||||||
user.save()
|
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")
|
group = Group.objects.create(name=f"{user.pk}-Admin")
|
||||||
user.groups.add(group)
|
user.groups.add(group)
|
||||||
for perm in Permission.objects.filter(
|
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)
|
# instance.user.groups.add(group)
|
||||||
|
|
||||||
# transaction.on_commit(run)
|
# 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}")
|
||||||
@ -701,7 +701,7 @@ urlpatterns = [
|
|||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"<slug:dealer_slug>/ledgers/<slug:entity_slug>/delete/<uuid:ledger_pk>/",
|
"<slug:dealer_slug>/ledgers/<slug:entity_slug>/delete/<uuid:ledger_pk>/",
|
||||||
views.LedgerModelDeleteView.as_view(),
|
views.LedgerModelDeleteView,
|
||||||
name="ledger-delete",
|
name="ledger-delete",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
|
|||||||
@ -4024,7 +4024,7 @@ class BankAccountDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailV
|
|||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"])
|
dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"])
|
||||||
query = self.request.GET.get("q")
|
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:
|
if query:
|
||||||
qs = apply_search_filters(qs, query)
|
qs = apply_search_filters(qs, query)
|
||||||
return qs
|
return qs
|
||||||
@ -4067,8 +4067,8 @@ class BankAccountUpdateView(
|
|||||||
dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"])
|
dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"])
|
||||||
entity = dealer.entity
|
entity = dealer.entity
|
||||||
kwargs = super().get_form_kwargs()
|
kwargs = super().get_form_kwargs()
|
||||||
kwargs["entity_slug"] = entity.slug # Get entity_slug from URL
|
kwargs["entity_slug"] = entity.slug
|
||||||
kwargs["user_model"] = entity.admin # Get user_model from the request
|
kwargs["user_model"] = entity.admin
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
def get_form(self, form_class=None):
|
def get_form(self, form_class=None):
|
||||||
@ -4082,7 +4082,6 @@ class BankAccountUpdateView(
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
form.fields["account_model"].queryset = account_qs
|
form.fields["account_model"].queryset = account_qs
|
||||||
|
|
||||||
return form
|
return form
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
@ -8938,7 +8937,7 @@ class LedgerModelDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailV
|
|||||||
permission_required = "django_ledger.view_ledgermodel"
|
permission_required = "django_ledger.view_ledgermodel"
|
||||||
|
|
||||||
|
|
||||||
class LedgerModelCreateView(LedgerModelCreateViewBase):
|
class LedgerModelCreateView(LedgerModelCreateViewBase,SuccessMessageMixin):
|
||||||
"""
|
"""
|
||||||
Handles the creation of LedgerModel entities.
|
Handles the creation of LedgerModel entities.
|
||||||
|
|
||||||
@ -8954,6 +8953,7 @@ class LedgerModelCreateView(LedgerModelCreateViewBase):
|
|||||||
|
|
||||||
template_name = "ledger/ledger/ledger_form.html"
|
template_name = "ledger/ledger/ledger_form.html"
|
||||||
permission_required = ["django_ledger.add_ledgermodel"]
|
permission_required = ["django_ledger.add_ledgermodel"]
|
||||||
|
success_message = _("Ledger created successfully")
|
||||||
|
|
||||||
def get_form(self, form_class=None):
|
def get_form(self, form_class=None):
|
||||||
return LedgerModelCreateForm(
|
return LedgerModelCreateForm(
|
||||||
@ -8963,10 +8963,11 @@ class LedgerModelCreateView(LedgerModelCreateViewBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
form.field["entity"] = self.request.dealer.entity
|
form.fields["entity"] = self.request.dealer.entity
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
|
messages.success(self.request, self.success_message)
|
||||||
return reverse(
|
return reverse(
|
||||||
"ledger_list",
|
"ledger_list",
|
||||||
kwargs={
|
kwargs={
|
||||||
@ -9000,34 +9001,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
|
@login_required
|
||||||
ledger instance from the system. Extends functionality for managing success
|
@permission_required("django_ledger.delete_ledgermodel", raise_exception=True)
|
||||||
messages and redirections upon successful deletion.
|
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
|
# Provides functionality for rendering a confirmation template and deleting a
|
||||||
confirmation view.
|
# ledger instance from the system. Extends functionality for managing success
|
||||||
:type template_name: str
|
# messages and redirections upon successful deletion.
|
||||||
:ivar success_message: Success message displayed upon successful deletion
|
|
||||||
of the ledger instance.
|
|
||||||
:type success_message: str
|
|
||||||
"""
|
|
||||||
|
|
||||||
template_name = "ledger/ledger/ledger_delete.html"
|
# :ivar template_name: Path to the template used for rendering the delete
|
||||||
success_message = _("Ledger deleted successfully")
|
# confirmation view.
|
||||||
permission_required = ["django_ledger.delete_ledgermodel"]
|
# :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):
|
# template_name = "ledger/ledger/ledger_delete.html"
|
||||||
return reverse(
|
# pk_url_kwarg = 'ledger_pk'
|
||||||
"ledger_list",
|
# context_object_name = 'ledger_model'
|
||||||
kwargs={
|
|
||||||
"dealer_slug": self.kwargs["dealer_slug"],
|
# success_message = _("Ledger deleted successfully")
|
||||||
"entity_slug": self.kwargs["entity_slug"],
|
# 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):
|
class JournalEntryListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
|
||||||
@ -9121,7 +9135,7 @@ class JournalEntryCreateView(
|
|||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required("django_ledger.delete_journalentrymodel", raise_exception=True)
|
@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
|
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
|
the deletion of a journal entry identified by its primary key (pk). If the
|
||||||
@ -9142,10 +9156,10 @@ def JournalEntryDeleteView(request, pk):
|
|||||||
ledger = journal_entry.ledger
|
ledger = journal_entry.ledger
|
||||||
if not journal_entry.can_delete():
|
if not journal_entry.can_delete():
|
||||||
messages.error(request, _("Journal Entry cannot be deleted"))
|
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()
|
journal_entry.delete()
|
||||||
messages.success(request, "Journal Entry deleted")
|
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(
|
return render(
|
||||||
request,
|
request,
|
||||||
"ledger/journal_entry/journal_entry_delete.html",
|
"ledger/journal_entry/journal_entry_delete.html",
|
||||||
@ -9375,39 +9389,91 @@ 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")
|
||||||
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":
|
if payment_status == "paid":
|
||||||
|
# Get or create billing info (optional step)
|
||||||
billing_info, created = BillingInfo.objects.get_or_create(
|
billing_info, created = BillingInfo.objects.get_or_create(
|
||||||
user=dealer.user,
|
user=dealer.user,
|
||||||
tax_number=dealer.vrn,
|
defaults={
|
||||||
name=dealer.arabic_name,
|
'tax_number': dealer.vrn,
|
||||||
street=dealer.address,
|
'name': dealer.arabic_name,
|
||||||
zipcode=dealer.entity.zip_code if dealer.entity.zip_code else " ",
|
'street': dealer.address,
|
||||||
city=dealer.entity.city if dealer.entity.city else " ",
|
'zipcode': dealer.entity.zip_code or " ",
|
||||||
country=dealer.entity.country if dealer.entity.country else " ",
|
'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()
|
try:
|
||||||
history.status = "paid"
|
# COMPLETE THE ORDER - This handles plan activation/upgrade
|
||||||
history.save()
|
order.complete_order() # Critical step: activates the plan
|
||||||
invoice = order.get_invoices().first()
|
|
||||||
return render(
|
# Update payment history
|
||||||
request, "payment_success.html", {"order": order, "invoice": invoice}
|
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":
|
elif payment_status == "failed":
|
||||||
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})
|
# 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
|
@login_required
|
||||||
|
|||||||
@ -1,9 +1,17 @@
|
|||||||
{% extends "account/email/base_message.txt" %}
|
{% extends "account/email/base_message.txt" %}
|
||||||
{% load account %}
|
{% load account i18n %}
|
||||||
{% load 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 %}
|
||||||
@ -27,6 +27,7 @@
|
|||||||
<h3 class="mb-4">{% trans "Change Password" %}</h3>
|
<h3 class="mb-4">{% trans "Change Password" %}</h3>
|
||||||
</div>
|
</div>
|
||||||
<form method="post"
|
<form method="post"
|
||||||
|
hx-boost="false"
|
||||||
action="{% url 'account_change_password' %}"
|
action="{% url 'account_change_password' %}"
|
||||||
class="form needs-validation"
|
class="form needs-validation"
|
||||||
novalidate>
|
novalidate>
|
||||||
|
|||||||
@ -182,9 +182,9 @@ document.addEventListener('htmx:afterRequest', function(evt) {
|
|||||||
return alert("Error: Could Not Find Resource");
|
return alert("Error: Could Not Find Resource");
|
||||||
}
|
}
|
||||||
if (evt.detail.successful != true) {
|
if (evt.detail.successful != true) {
|
||||||
console.log(evt)
|
console.log(evt.detail.xhr.statusText)
|
||||||
/* Notify of an unexpected error, & print error to console */
|
/* 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) {
|
document.body.addEventListener('htmx:beforeSwap', function(evt) {
|
||||||
|
|||||||
18
templates/emails/expiration_reminder_ar.html
Normal file
18
templates/emails/expiration_reminder_ar.html
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<body style="font-family: 'Segoe UI', Tahoma, sans-serif; direction: rtl;">
|
||||||
|
<p>مرحباً {{ user.get_full_name }}،</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
اشتراكك في <strong>{{ plan.name }}</strong> سينتهي خلال
|
||||||
|
{{ days_until_expire }} يوم في {{ expiration_date|date:"j F Y" }}.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a href="{{ RENEWAL_URL }}">جدد اشتراكك الآن</a> لمواصلة الخدمة.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>مع أطيب التحيات،<br>
|
||||||
|
فريق {{ SITE_NAME }}</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
8
templates/emails/expiration_reminder_ar.txt
Normal file
8
templates/emails/expiration_reminder_ar.txt
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
مرحباً {{ user.get_full_name }}،
|
||||||
|
|
||||||
|
اشتراكك في {{ plan.name }} سينتهي خلال {{ days_until_expire }} يوم في {{ expiration_date|date:"j F Y" }}.
|
||||||
|
|
||||||
|
جدد اشتراكك الآن: {{ RENEWAL_URL }}
|
||||||
|
|
||||||
|
مع أطيب التحيات،
|
||||||
|
فريق {{ SITE_NAME }}
|
||||||
14
templates/emails/expiration_reminder_en.html
Normal file
14
templates/emails/expiration_reminder_en.html
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<body style="font-family: Arial, sans-serif; direction: {{ direction }};">
|
||||||
|
<p>Hello {{ user.get_full_name }},</p>
|
||||||
|
|
||||||
|
<p>Your <strong>{{ plan.name }}</strong> subscription will expire
|
||||||
|
in {{ days_until_expire }} days on {{ expiration_date|date:"F j, Y" }}.</p>
|
||||||
|
|
||||||
|
<p><a href="{{ RENEWAL_URL }}">Renew now</a> to continue service.</p>
|
||||||
|
|
||||||
|
<p>Best regards,<br>
|
||||||
|
The {{ SITE_NAME }} Team</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
8
templates/emails/expiration_reminder_en.txt
Normal file
8
templates/emails/expiration_reminder_en.txt
Normal file
@ -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
|
||||||
@ -6,7 +6,7 @@
|
|||||||
{{ _("Add New Expense") }}
|
{{ _("Add New Expense") }}
|
||||||
{% endblock title %}
|
{% endblock title %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<!---->
|
<!---->
|
||||||
<div class="row justify-content-center mt-5 mb-3">
|
<div class="row justify-content-center mt-5 mb-3">
|
||||||
|
|
||||||
@ -19,7 +19,6 @@
|
|||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body bg-light-subtle">
|
<div class="card-body bg-light-subtle">
|
||||||
|
|
||||||
<form method="post" action="">
|
<form method="post" action="">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form|crispy }}
|
{{ form|crispy }}
|
||||||
@ -29,11 +28,7 @@
|
|||||||
<button class="btn btn-lg btn-phoenix-primary md-me-2" type="submit"><i class="fa-solid fa-floppy-disk me-1"></i>{{ _("Save") }}</button>
|
<button class="btn btn-lg btn-phoenix-primary md-me-2" type="submit"><i class="fa-solid fa-floppy-disk me-1"></i>{{ _("Save") }}</button>
|
||||||
<a href="{% url 'item_expense_list' request.dealer.slug %}" class="btn btn-lg btn-phoenix-secondary"><i class="fa-solid fa-ban me-1"></i>{% trans "Cancel" %}</a>
|
<a href="{% url 'item_expense_list' request.dealer.slug %}" class="btn btn-lg btn-phoenix-secondary"><i class="fa-solid fa-ban me-1"></i>{% trans "Cancel" %}</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
{{ _("Create Bill") }}
|
{{ _("Create Bill") }}
|
||||||
{% endblock title %}
|
{% endblock title %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row mt-4">
|
<div class="row mt-4" hx-boost="true">
|
||||||
<h3 class="text-center">{% trans "Create Bill" %}<span class="fas fa-money-bills ms-2 text-primary"></span></h3>
|
<h3 class="text-center">{% trans "Create Bill" %}<span class="fas fa-money-bills ms-2 text-primary"></span></h3>
|
||||||
<form id="mainForm" method="post" class="needs-validation">
|
<form id="mainForm" method="post" class="needs-validation">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
@ -15,7 +15,7 @@
|
|||||||
<button class="btn btn-sm btn-phoenix-success me-2" type="submit">
|
<button class="btn btn-sm btn-phoenix-success me-2" type="submit">
|
||||||
<i class="fa-solid fa-floppy-disk me-1"></i>{{ _("Save") }}
|
<i class="fa-solid fa-floppy-disk me-1"></i>{{ _("Save") }}
|
||||||
</button>
|
</button>
|
||||||
<a href="{{ request.META.HTTP_REFERER }}"
|
<a href="{% url 'bill_list' request.dealer.slug %}"
|
||||||
class="btn btn-sm btn-phoenix-danger"><i class="fa-solid fa-ban me-1"></i>{% trans "Cancel" %}</a>
|
class="btn btn-sm btn-phoenix-danger"><i class="fa-solid fa-ban me-1"></i>{% trans "Cancel" %}</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<form action="{% url 'journalentry_delete' journal_entry.pk %}"
|
<form action="{% url 'journalentry_delete' request.dealer.slug journal_entry.pk %}"
|
||||||
method="post">
|
method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="card">
|
<div class="card">
|
||||||
@ -13,7 +13,7 @@
|
|||||||
<h5 class="card-title fw-light">Are you sure you want to delete?</h5>
|
<h5 class="card-title fw-light">Are you sure you want to delete?</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body text-center">
|
<div class="card-body text-center">
|
||||||
<a href="{% url 'journalentry_list' journal_entry.ledger.pk %}"
|
<a href="{% url 'journalentry_list' request.dealer.slug journal_entry.ledger.pk %}"
|
||||||
class="btn btn-phoenix-primary me-2">{% trans 'Go Back' %}</a>
|
class="btn btn-phoenix-primary me-2">{% trans 'Go Back' %}</a>
|
||||||
<button type="submit" class="btn btn-phoenix-danger">{% trans 'Delete' %}</button>
|
<button type="submit" class="btn btn-phoenix-danger">{% trans 'Delete' %}</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -27,7 +27,7 @@
|
|||||||
{% for transaction in transactions %}
|
{% for transaction in transactions %}
|
||||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
||||||
<td>{{ forloop.counter }}</td>
|
<td>{{ forloop.counter }}</td>
|
||||||
<td class="align-middle product white-space-nowrap py-0">{{ transaction.created|date }}</td>
|
<td class="align-middle product white-space-nowrap py-0">{{ transaction.created|date }}</td>
|
||||||
<td class="align-middle product white-space-nowrap">{{ transaction.account.name }}</td>
|
<td class="align-middle product white-space-nowrap">{{ transaction.account.name }}</td>
|
||||||
<td class="align-middle product white-space-nowrap">{{ transaction.account.code }}</td>
|
<td class="align-middle product white-space-nowrap">{{ transaction.account.code }}</td>
|
||||||
<td class="align-middle product white-space-nowrap text-success">
|
<td class="align-middle product white-space-nowrap text-success">
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<form action="{% url 'ledger-delete' entity_slug=view.kwargs.entity_slug ledger_pk=ledger_model.uuid %}"
|
<form action="{% url 'ledger-delete' dealer_slug=request.dealer.slug entity_slug=request.dealer.entity.slug ledger_pk=ledger_model.uuid %}"
|
||||||
method="post">
|
method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="card">
|
<div class="card">
|
||||||
@ -13,7 +13,7 @@
|
|||||||
<h5 class="card-title fw-light">{{ ledger_model.get_delete_message }}</h5>
|
<h5 class="card-title fw-light">{{ ledger_model.get_delete_message }}</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body text-center">
|
<div class="card-body text-center">
|
||||||
<a href="{% url 'ledger_list' %}" class="btn btn-phoenix-primary me-2">{% trans 'Go Back' %}</a>
|
<a href="{% url 'ledger_list' request.dealer.slug request.dealer.entity.slug %}" class="btn btn-phoenix-primary me-2">{% trans 'Go Back' %}</a>
|
||||||
<button type="submit" class="btn btn-phoenix-danger">{% trans 'Delete' %}</button>
|
<button type="submit" class="btn btn-phoenix-danger">{% trans 'Delete' %}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user