This commit is contained in:
Faheed 2025-09-15 17:39:24 +03:00
commit c90f8edc5a
4 changed files with 509 additions and 248 deletions

View File

@ -25,6 +25,7 @@ from api import routing
from inventory.notifications.sse import NotificationSSEApp from inventory.notifications.sse import NotificationSSEApp
from django.urls import re_path from django.urls import re_path
from django.core.asgi import get_asgi_application from django.core.asgi import get_asgi_application
from django.contrib.staticfiles.handlers import ASGIStaticFilesHandler
# application = ProtocolTypeRouter( # application = ProtocolTypeRouter(
# { # {
@ -32,6 +33,9 @@ from django.core.asgi import get_asgi_application
# # "websocket": AuthMiddlewareStack(URLRouter(routing.websocket_urlpatterns)), # # "websocket": AuthMiddlewareStack(URLRouter(routing.websocket_urlpatterns)),
# } # }
# ) # )
app = get_asgi_application()
application = ProtocolTypeRouter( application = ProtocolTypeRouter(
{ {
"http": AuthMiddlewareStack( "http": AuthMiddlewareStack(
@ -39,10 +43,13 @@ application = ProtocolTypeRouter(
[ [
path("sse/notifications/", NotificationSSEApp()), path("sse/notifications/", NotificationSSEApp()),
re_path( re_path(
r"", get_asgi_application() r"", app
), # All other routes go to Django ), # All other routes go to Django
] ]
) )
), ),
} }
) )
if django.conf.settings.DEBUG:
application = ASGIStaticFilesHandler(app)

View File

@ -1220,11 +1220,11 @@ urlpatterns = [
views.permenant_delete_account, views.permenant_delete_account,
name="permenant_delete_account", name="permenant_delete_account",
), ),
path( # path(
"<slug:dealer_slug>/management/audit_log_dashboard/", # "<slug:dealer_slug>/management/audit_log_dashboard/",
views.AuditLogDashboardView, # views.AuditLogDashboardView,
name="audit_log_dashboard", # name="audit_log_dashboard",
), # ),
######### #########
# Purchase Order # Purchase Order
path( path(

View File

@ -1609,11 +1609,11 @@ def _post_sale_and_cogs(invoice, dealer):
# .first() # .first()
# ) # )
cash_acc = invoice.cash_account or dealer.settings.invoice_cash_account cash_acc = invoice.cash_account or dealer.settings.invoice_cash_account
vat_acc = dealer.settings.invoice_tax_payable_account or entity.get_default_coa_accounts().filter(role_default=True, role=roles.LIABILITY_CL_TAXES_PAYABLE).first() vat_acc = dealer.settings.invoice_tax_payable_account or entity.get_default_coa_accounts().filter(role_default=True, role=roles.LIABILITY_CL_TAXES_PAYABLE).first()
car_rev = dealer.settings.invoice_vehicle_sale_account or entity.get_default_coa_accounts().filter(role_default=True, role=roles.INCOME_OPERATIONAL).first() car_rev = dealer.settings.invoice_vehicle_sale_account or entity.get_default_coa_accounts().filter(role_default=True, role=roles.INCOME_OPERATIONAL).first()
add_rev = dealer.settings.invoice_additional_services_account add_rev = dealer.settings.invoice_additional_services_account
if not add_rev: if not add_rev:
@ -1636,9 +1636,9 @@ def _post_sale_and_cogs(invoice, dealer):
if car.get_additional_services_amount > 0 and not add_rev: if car.get_additional_services_amount > 0 and not add_rev:
raise Exception("additional services exist but not account found,please create account for the additional services and set as default in the settings") raise Exception("additional services exist but not account found,please create account for the additional services and set as default in the settings")
cogs_acc = dealer.settings.invoice_cost_of_good_sold_account or entity.get_default_coa_accounts().filter(role_default=True, role=roles.COGS).first() cogs_acc = dealer.settings.invoice_cost_of_good_sold_account or entity.get_default_coa_accounts().filter(role_default=True, role=roles.COGS).first()
inv_acc = dealer.settings.invoice_inventory_account or entity.get_default_coa_accounts().filter(role_default=True, role=roles.ASSET_CA_INVENTORY).first() inv_acc = dealer.settings.invoice_inventory_account or entity.get_default_coa_accounts().filter(role_default=True, role=roles.ASSET_CA_INVENTORY).first()
net_car_price = Decimal(data["discounted_price"]) net_car_price = Decimal(data["discounted_price"])
net_additionals_price = Decimal(data["additional_services"]["total"]) net_additionals_price = Decimal(data["additional_services"]["total"])
vat_amount = Decimal(data["vat_amount"]) vat_amount = Decimal(data["vat_amount"])
@ -1692,7 +1692,7 @@ def _post_sale_and_cogs(invoice, dealer):
# tx_type='credit' # tx_type='credit'
# ) # )
if car.get_additional_services_amount > 0: if car.get_additional_services_amount > 0:
# Cr Sales Additional Services # Cr Sales Additional Services
if not add_rev: if not add_rev:
@ -1909,23 +1909,22 @@ def create_make_accounts(dealer):
) )
def handle_payment(request, order): def handle_payment(request, dealer):
url = "https://api.moyasar.com/v1/payments" url = "https://api.moyasar.com/v1/payments"
callback_url = request.build_absolute_uri( callback_url = request.build_absolute_uri(
reverse("payment_callback", kwargs={"dealer_slug": request.dealer.slug}) reverse("payment_callback", kwargs={"dealer_slug": dealer.slug})
) )
if request.user.is_authenticated: email = request.POST.get("email")
email = request.POST["email"] first_name = request.POST.get("first_name")
first_name = request.POST["first_name"] last_name = request.POST.get("last_name")
last_name = request.POST["last_name"] phone = request.POST.get("phone")
phone = request.POST["phone"] card_name = request.POST.get("card_name")
card_number = str(request.POST.get("card_number", "")).replace(" ", "").strip()
card_name = request.POST["card_name"] expiry = request.POST.get("card_expiry", "").split("/")
card_number = str(request.POST["card_number"]).replace(" ", "").strip() month = int(expiry[0].strip()) if len(expiry) > 0 else 0
month = int(request.POST["card_expiry"].split("/")[0].strip()) year = int(expiry[1].strip()) if len(expiry) > 1 else 0
year = int(request.POST["card_expiry"].split("/")[1].strip()) cvv = request.POST.get("card_cvv")
cvv = request.POST["card_cvv"]
user_data = { user_data = {
"email": email, "email": email,
@ -1933,65 +1932,166 @@ def handle_payment(request, order):
"last_name": last_name, "last_name": last_name,
"phone": phone, "phone": phone,
} }
try:
total = int((order.total() + order.tax * order.total() / 100) * 100) # Get selected plan from session
except (AttributeError, TypeError): selected_plan_id = request.session.get('pending_plan_id')
raise ValueError("Order total or tax is invalid") if not selected_plan_id:
payload = json.dumps( raise ValueError("No pending plan found in session")
{ from plans.models import PlanPricing
"amount": total,
"currency": "SAR", pp = PlanPricing.objects.get(pk=selected_plan_id)
"description": f"payment issued for {email}", # Calculate amount without creating order
"callback_url": callback_url, amount_sar = pp.price # assuming price is in SAR
"source": { tax_amount = amount_sar * 15 / 100
"type": "creditcard", total = int((amount_sar + tax_amount) * 100) # convert to halalas
"name": card_name,
"number": card_number, # Pass plan & dealer info via metadata
"month": month, metadata = {
"year": year, **user_data,
"cvc": cvv, "plan_pricing_id": selected_plan_id,
"statement_descriptor": "Century Store", "dealer_slug": dealer.slug,
"3ds": True, }
"manual": False,
"save_card": False, payload = json.dumps({
}, "amount": total,
"metadata": user_data, "currency": "SAR",
} "description": f"Payment for plan {pp.plan.name}",
) "callback_url": callback_url,
"source": {
"type": "creditcard",
"name": card_name,
"number": card_number,
"month": month,
"year": year,
"cvc": cvv,
"statement_descriptor": "Century Store",
"3ds": True,
"manual": False,
"save_card": False,
},
"metadata": metadata,
})
headers = {"Content-Type": "application/json", "Accept": "application/json"} headers = {"Content-Type": "application/json", "Accept": "application/json"}
auth = (settings.MOYASAR_SECRET_KEY, "") auth = (settings.MOYASAR_SECRET_KEY, "")
response = requests.request("POST", url, auth=auth, headers=headers, data=payload) response = requests.post(url, auth=auth, headers=headers, data=payload)
if response.status_code == 400: if response.status_code != 201:
data = response.json() logger.error(f"Payment initiation failed: {response.text}")
if data["type"] == "validation_error": return None, response.get("message")
errors = data.get("errors", {})
if "source.year" in errors:
raise Exception("Invalid expiry year")
else:
raise Exception("Validation Error: ", errors)
else:
print("Failed to process payment:", data)
#
data = response.json() data = response.json()
# order.status = AbstractOrder.STATUS.NEW # Create PaymentHistory WITHOUT linking to order (since order doesn't exist yet)
order.save() amount_decimal = Decimal("{0:.2f}".format(Decimal(total) / Decimal(100)))
#
amount = Decimal("{0:.2f}".format(Decimal(total) / Decimal(100)))
models.PaymentHistory.objects.create( models.PaymentHistory.objects.create(
user=request.user, user=request.user,
user_data=user_data, user_data=json.dumps(metadata),
amount=amount, amount=amount_decimal,
currency=data["currency"], currency=data["currency"],
status=data["status"], status=data["status"],
transaction_id=data["id"], transaction_id=data["id"],
payment_date=data["created_at"], payment_date=data["created_at"],
gateway_response=data, gateway_response=data,
) )
transaction_url = data["source"]["transaction_url"] logger.info(f"Payment initiated: {data}")
return transaction_url return data["source"]["transaction_url"],None
# def handle_payment(request, order):
# logger.info(f"Handling payment for order {order}")
# url = "https://api.moyasar.com/v1/payments"
# callback_url = request.build_absolute_uri(
# reverse("payment_callback", kwargs={"dealer_slug": request.dealer.slug})
# )
# logger.info(f"Got callback_url: {callback_url}")
# if request.user.is_authenticated:
# email = request.POST["email"]
# first_name = request.POST["first_name"]
# last_name = request.POST["last_name"]
# phone = request.POST["phone"]
# logger.info(f"Got user data: {email}, {first_name}, {last_name}, {phone}")
# card_name = request.POST["card_name"]
# card_number = str(request.POST["card_number"]).replace(" ", "").strip()
# month = int(request.POST["card_expiry"].split("/")[0].strip())
# year = int(request.POST["card_expiry"].split("/")[1].strip())
# cvv = request.POST["card_cvv"]
# logger.info(f"Got card data: {card_name}, {card_number[:4]}****, {month}, {year}, {cvv}")
# user_data = {
# "email": email,
# "first_name": first_name,
# "last_name": last_name,
# "phone": phone,
# }
# try:
# total = int((order.total() + order.tax * order.total() / 100) * 100)
# except (AttributeError, TypeError):
# raise ValueError("Order total or tax is invalid")
# logger.info(f"Calculated total: {total}")
# payload = json.dumps(
# {
# "amount": total,
# "currency": "SAR",
# "description": f"payment issued for {email}",
# "callback_url": callback_url,
# "source": {
# "type": "creditcard",
# "name": card_name,
# "number": card_number,
# "month": month,
# "year": year,
# "cvc": cvv,
# "statement_descriptor": "Century Store",
# "3ds": False,
# "manual": False,
# "save_card": False,
# },
# "metadata": user_data,
# }
# )
# logger.info(f"Generated payload: {payload}")
# headers = {"Content-Type": "application/json", "Accept": "application/json"}
# auth = (settings.MOYASAR_SECRET_KEY, "")
# response = requests.request("POST", url, auth=auth, headers=headers, data=payload)
# logger.info(f"Sent request to Moyasar API: {response.status_code}")
# if response.status_code == 400:
# data = response.json()
# if data["type"] == "validation_error":
# errors = data.get("errors", {})
# if "source.year" in errors:
# raise Exception("Invalid expiry year")
# else:
# raise Exception("Validation Error: ", errors)
# else:
# logger.error(f"Failed to process payment: {data}")
# #
# data = response.json()
# # order.status = AbstractOrder.STATUS.NEW
# order.save()
# #
# amount = Decimal("{0:.2f}".format(Decimal(total) / Decimal(100)))
# models.PaymentHistory.objects.create(
# user=request.user,
# user_data=user_data,
# amount=amount,
# currency=data["currency"],
# status=data["status"],
# transaction_id=data["id"],
# payment_date=data["created_at"],
# gateway_response=data,
# )
# transaction_url = data["source"]["transaction_url"]
# logger.info(f"Created payment history and got transaction_url: {transaction_url}")
# return transaction_url
# def get_user_quota(user): # def get_user_quota(user):

View File

@ -218,7 +218,7 @@ from .utils import (
from .tasks import create_accounts_for_make, create_user_dealer, send_email from .tasks import create_accounts_for_make, create_user_dealer, send_email
# djago easy audit log # djago easy audit log
from easyaudit.models import RequestEvent, CRUDEvent, LoginEvent # from easyaudit.models import RequestEvent, CRUDEvent, LoginEvent
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
@ -504,7 +504,8 @@ def general_dashboard(request,dealer_slug):
total_vat_collected = total_vat_collected_from_cars + total_vat_collected_from_services total_vat_collected = total_vat_collected_from_cars + total_vat_collected_from_services
total_revenue_generated = total_revenue_from_cars + total_revenue_from_services total_revenue_generated = total_revenue_from_cars + total_revenue_from_services
# total_expenses=sum([x.amount_paid for x in dealer.entity.get_bills().filter(bill_items__item_role="expense")]) # total_expenses=sum([x.amount_paid for x in dealer.entity.get_bills().filter(bill_items__item_role="expense")])
total_expenses=dealer.entity.get_bills().filter(bill_items__item_role="expense").aggregate(total=Sum('amount_paid'))['total'] or 0
total_expenses=dealer.entity.get_bills().filter(bill_items__item_role="expense").aggregate(total=Sum('amount_paid'))['total'] or 0
gross_profit = net_profit_from_cars+total_revenue_from_services - total_expenses gross_profit = net_profit_from_cars+total_revenue_from_services - total_expenses
# ---------------------------------------------------- # ----------------------------------------------------
@ -9889,50 +9890,123 @@ def pricing_page(request, dealer_slug):
@login_required # @login_required
@permission_required("inventory.change_dealer", raise_exception=True) # @permission_required("inventory.change_dealer", raise_exception=True)
@require_POST # @require_POST
# def submit_plan(request, dealer_slug):
# logger.info(f"dealer slug : {dealer_slug}")
# if request.method == "GET":
# logger.info("method is GET, redirecting to pricing page")
# return redirect("pricing_page", dealer_slug=dealer_slug)
# dealer = get_object_or_404(models.Dealer, slug=dealer_slug)
# logger.info(f"Selected dealer : {dealer}")
# selected_plan_id = request.POST.get("selected_plan")
# logger.info(f"Selected plan id : {selected_plan_id}")
# pp = PlanPricing.objects.get(pk=selected_plan_id)
# logger.info(f"Selected plan pricing : {pp}")
# order = None
# try:
# order = Order.objects.create(
# user=dealer.user,
# plan=pp.plan,
# pricing=pp.pricing,
# amount=pp.price,
# currency="SA",
# tax=15,
# status=1,
# )
# logger.info(f"order created {order}")
# except Exception as e:
# logger.error(e)
# if not order:
# messages.error(request, _("Error creating order"))
# logger.error("unable to create order")
# return redirect("pricing_page", dealer_slug=dealer_slug)
# transaction_url = handle_payment(request, order)
# logger.info(f"Redirecting to : {transaction_url}")
# return redirect(transaction_url)
def submit_plan(request, dealer_slug): def submit_plan(request, dealer_slug):
if request.method == "GET":
return redirect("pricing_page", dealer_slug=dealer_slug)
dealer = get_object_or_404(models.Dealer, slug=dealer_slug) dealer = get_object_or_404(models.Dealer, slug=dealer_slug)
selected_plan_id = request.POST.get("selected_plan") selected_plan_id = request.POST.get("selected_plan")
pp = PlanPricing.objects.get(pk=selected_plan_id) if not selected_plan_id:
order = None messages.error(request, _("No plan selected."))
try:
order = Order.objects.create(
user=dealer.user,
plan=pp.plan,
pricing=pp.pricing,
amount=pp.price,
currency="SA",
tax=15,
status=1,
)
logger.info(f"order created {order}")
except Exception as e:
logger.error(e)
if not order:
messages.error(request, _("Error creating order"))
logger.error("unable to create order")
return redirect("pricing_page", dealer_slug=dealer_slug) return redirect("pricing_page", dealer_slug=dealer_slug)
transaction_url = handle_payment(request, order)
# Store plan & dealer info in session for use in callback
request.session['pending_plan_id'] = selected_plan_id
request.session['pending_dealer_slug'] = dealer_slug
# Initiate payment WITHOUT creating order
transaction_url,error = handle_payment(request, dealer)
if not transaction_url:
messages.error(request, _(f"Payment initiation failed. {error}"))
return redirect("pricing_page", dealer_slug=dealer_slug)
return redirect(transaction_url) return redirect(transaction_url)
@login_required # @login_required
@permission_required("inventory.change_dealer", raise_exception=True)
def payment_callback(request, dealer_slug): 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") payment_id = request.GET.get("id")
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}") message = request.GET.get("message", "")
order = Order.objects.filter(user=dealer.user, status=1).first() # Status 1 = NEW
if history.status == "paid":
return redirect('home')
if payment_status == "paid":
logger.info(f"Payment successful for transaction ID {payment_id}. Processing order completion.")
logger.info(f"Received payment callback for dealer_slug: {dealer_slug}, payment_id: {payment_id}, status: {payment_status}")
history = models.PaymentHistory.objects.filter(transaction_id=payment_id).first()
if not history:
logger.error(f"No PaymentHistory found for transaction_id: {payment_id}")
return render(request, "payment_failed.html", {"message": "Invalid transaction"})
if history.status == "paid":
logger.info("Payment already processed. Redirecting to home.")
return redirect('home')
if payment_status == "paid":
logger.info(f"Payment successful for transaction ID {payment_id}. Creating order...")
# Get metadata from PaymentHistory (passed during handle_payment)
metadata = history.user_data
if isinstance(metadata, str):
try:
metadata = json.loads(metadata)
except json.JSONDecodeError:
logger.error(f"Failed to decode metadata JSON: {metadata}")
metadata = {}
plan_pricing_id = metadata.get("plan_pricing_id")
dealer_slug_from_meta = metadata.get("dealer_slug")
if not plan_pricing_id or dealer_slug_from_meta != dealer_slug:
logger.error("Invalid metadata in payment callback")
history.status = "failed"
history.save()
return render(request, "payment_failed.html", {"message": "Invalid payment data"})
dealer = get_object_or_404(models.Dealer, slug=dealer_slug)
pp = get_object_or_404(PlanPricing, pk=plan_pricing_id)
# ✅ CREATE ORDER HERE
try:
order = Order.objects.create(
user=dealer.user,
plan=pp.plan,
pricing=pp.pricing,
amount=pp.price,
currency="SAR", # Fixed typo: was "SA"
tax=15,
status=Order.STATUS.NEW, # Use constant if available
)
logger.info(f"Order {order.id} created for user {dealer.user}")
except Exception as e:
logger.exception(f"Failed to create order: {e}")
history.status = "failed"
history.save()
return render(request, "payment_failed.html", {"message": "Order creation failed"})
# Create or get BillingInfo
billing_info, created = BillingInfo.objects.get_or_create( billing_info, created = BillingInfo.objects.get_or_create(
user=dealer.user, user=dealer.user,
defaults={ defaults={
@ -9949,8 +10023,8 @@ def payment_callback(request, dealer_slug):
else: else:
logger.debug(f"Billing info already exists for user {dealer.user}.") logger.debug(f"Billing info already exists for user {dealer.user}.")
# Create or update UserPlan
if not hasattr(order.user, 'userplan'): if not hasattr(order.user, 'userplan'):
print(order.get_plan_pricing().pricing.period)
UserPlan.objects.create( UserPlan.objects.create(
user=order.user, user=order.user,
plan=order.plan, plan=order.plan,
@ -9958,26 +10032,22 @@ def payment_callback(request, dealer_slug):
) )
logger.info(f"Created new UserPlan for user {order.user} with plan {order.plan}.") logger.info(f"Created new UserPlan for user {order.user} with plan {order.plan}.")
else: else:
# Optional: upgrade existing plan
# user_plan = order.user.userplan
# user_plan.plan = order.plan
# user_plan.save()
logger.info(f"UserPlan already exists for user {order.user}.") logger.info(f"UserPlan already exists for user {order.user}.")
try: try:
# Complete the order (this may generate invoice, etc.)
# if order.user.userplan:
# user = order.user
# pricing = order.get_plan_pricing().pricing
# logger.info(f"Processing order completion for {user} - upgrading to {order.plan}")
# user.userplan.plan = order.plan
# user.userplan.expire = datetime.now() + timedelta(days=pricing.period)
# user.userplan.save()
# user.save()
# logger.info(f"User {user} upgraded to {order.plan} plan successfully")
order.complete_order() order.complete_order()
history.status = "paid" history.status = "paid"
history.order = order # Link payment to order
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()
logger.info(f"Order {order.id} completed. Rendering success page.")
return render( return render(
request, request,
"payment_success.html", "payment_success.html",
@ -9985,13 +10055,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.exception(f"Error completing order {order.id}: {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"})
finally: finally:
if dealer := getattr(order.user,"dealer", None): # Activate dealer & staff if needed
if dealer := getattr(order.user, "dealer", None):
if not dealer.user.is_active: if not dealer.user.is_active:
dealer.user.is_active = True dealer.user.is_active = True
dealer.user.save() dealer.user.save()
@ -9999,7 +10070,6 @@ def payment_callback(request, dealer_slug):
if not staff.user.is_active: if not staff.user.is_active:
staff.activate_account() staff.activate_account()
logger.info(f"Order {order.id} for user {order.user} completed successfully. Payment history updated.")
elif payment_status == "failed": elif payment_status == "failed":
logger.warning(f"Payment failed for transaction ID {payment_id}. Message: {message}") logger.warning(f"Payment failed for transaction ID {payment_id}. Message: {message}")
history.status = "failed" history.status = "failed"
@ -10007,6 +10077,90 @@ def payment_callback(request, dealer_slug):
return render(request, "payment_failed.html", {"message": message}) return render(request, "payment_failed.html", {"message": message})
return render(request, "payment_failed.html", {"message": "Unknown payment status"}) return render(request, "payment_failed.html", {"message": "Unknown payment status"})
# @login_required
# @permission_required("inventory.change_dealer", raise_exception=True)
# 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")
# 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
# logger.info(f"Retrieved order {order.id} for user {order.user}.")
# if history.status == "paid":
# logger.info(f"Payment history already marked as paid for transaction ID {payment_id}. Redirecting to home.")
# return redirect('home')
# if payment_status == "paid":
# logger.info(f"Payment successful for transaction ID {payment_id}. Processing order completion.")
# billing_info, created = BillingInfo.objects.get_or_create(
# user=dealer.user,
# defaults={
# '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:
# 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'):
# logger.info(f"Creating new UserPlan for user {order.user} with plan {order.plan}.")
# UserPlan.objects.create(
# user=order.user,
# plan=order.plan,
# # expire=datetime.now().date() + timedelta(days=order.get_plan_pricing().pricing.period)
# )
# else:
# logger.info(f"UserPlan already exists for user {order.user}.")
# try:
# logger.info(f"Processing order completion for {order.user} - upgrading to {order.plan}")
# order.complete_order()
# history.status = "paid"
# history.save()
# logger.info(f"Order {order.id} for user {order.user} completed successfully. Payment history updated.")
# invoice = order.get_invoices().first()
# logger.info(f"Redirecting to payment success page with invoice {invoice.id}.")
# return render(
# request,
# "payment_success.html",
# {"order": order, "invoice": invoice}
# )
# except Exception as e:
# logger.exception(f"Error completing order {order.id} for user {order.user}: {e}")
# logger.error(f"Plan activation failed: {str(e)}")
# history.status = "failed"
# history.save()
# logger.info(f"Redirecting to payment failed page with message {message}.")
# return render(request, "payment_failed.html", {"message": "Plan activation error"})
# finally:
# if dealer := getattr(order.user,"dealer", None):
# logger.info(f"Activating dealer {dealer} and its staff.")
# if not dealer.user.is_active:
# logger.info(f"Activating dealer {dealer}.")
# dealer.user.is_active = True
# dealer.user.save()
# for staff in dealer.get_staff():
# logger.info(f"Activating staff {staff}.")
# if not staff.user.is_active:
# staff.activate_account()
# logger.info(f"Order {order.id} for user {order.user} completed successfully. Payment history updated.")
# elif payment_status == "failed":
# logger.warning(f"Payment failed for transaction ID {payment_id}. Message: {message}")
# history.status = "failed"
# history.save()
# logger.info(f"Redirecting to payment failed page with message {message}.")
# return render(request, "payment_failed.html", {"message": message})
# logger.info(f"Redirecting to payment failed page with message {message}.")
# return render(request, "payment_failed.html", {"message": "Unknown payment status"})
# def payment_callback(request, dealer_slug): # def payment_callback(request, dealer_slug):
# message = request.GET.get("message") # message = request.GET.get("message")
# dealer = get_object_or_404(models.Dealer, slug=dealer_slug) # dealer = get_object_or_404(models.Dealer, slug=dealer_slug)
@ -10350,140 +10504,140 @@ def user_management(request, dealer_slug):
return render(request, "admin_management/user_management.html", context) return render(request, "admin_management/user_management.html", context)
@login_required # @login_required
@permission_required("inventory.change_dealer", raise_exception=True) # @permission_required("inventory.change_dealer", raise_exception=True)
def AuditLogDashboardView(request, dealer_slug): # def AuditLogDashboardView(request, dealer_slug):
""" # """
Displays audit logs (User Actions, Login Events, Request Events) with pagination. # Displays audit logs (User Actions, Login Events, Request Events) with pagination.
Log type is determined by the 'q' query parameter (e.g., ?q=userActions). # Log type is determined by the 'q' query parameter (e.g., ?q=userActions).
Pagination page number is passed as a query parameter (e.g., ?page=2). # Pagination page number is passed as a query parameter (e.g., ?page=2).
""" # """
get_object_or_404(models.Dealer, slug=dealer_slug) # get_object_or_404(models.Dealer, slug=dealer_slug)
q = request.GET.get("q") # Get the log type from the 'q' query parameter # q = request.GET.get("q") # Get the log type from the 'q' query parameter
current_pagination_page = request.GET.get("page", 1) # current_pagination_page = request.GET.get("page", 1)
context = {} # context = {}
template_name = None # template_name = None
logs_per_page = 30 # Define logs per page once # logs_per_page = 30 # Define logs per page once
# --- Determine Data Source and Template based on 'q' parameter --- # # --- Determine Data Source and Template based on 'q' parameter ---
if ( # if (
q == "userRequests" # q == "userRequests"
): # This block handles cases where 'q' is 'requestEvents', None, or any other invalid value. # ): # This block handles cases where 'q' is 'requestEvents', None, or any other invalid value.
# It defaults to Request Logs if 'q' is not 'userActions' or 'loginEvents'. # # It defaults to Request Logs if 'q' is not 'userActions' or 'loginEvents'.
template_name = "admin_management/request_logs.html" # template_name = "admin_management/request_logs.html"
context["title"] = "Request Logs Dashboard" # context["title"] = "Request Logs Dashboard"
request_events = RequestEvent.objects.all().order_by("-datetime") # request_events = RequestEvent.objects.all().order_by("-datetime")
paginator = Paginator(request_events, logs_per_page) # paginator = Paginator(request_events, logs_per_page)
try: # try:
page_obj = paginator.page(current_pagination_page) # page_obj = paginator.page(current_pagination_page)
except PageNotAnInteger: # except PageNotAnInteger:
page_obj = paginator.page(1) # page_obj = paginator.page(1)
except EmptyPage: # except EmptyPage:
page_obj = paginator.page(paginator.num_pages) # page_obj = paginator.page(paginator.num_pages)
elif q == "loginEvents": # elif q == "loginEvents":
template_name = "admin_management/auth_logs.html" # template_name = "admin_management/auth_logs.html"
context["title"] = "Login Events Dashboard" # context["title"] = "Login Events Dashboard"
auth_events = LoginEvent.objects.all().order_by("-datetime") # auth_events = LoginEvent.objects.all().order_by("-datetime")
paginator = Paginator(auth_events, logs_per_page) # paginator = Paginator(auth_events, logs_per_page)
try: # try:
page_obj = paginator.page(current_pagination_page) # page_obj = paginator.page(current_pagination_page)
except PageNotAnInteger: # except PageNotAnInteger:
page_obj = paginator.page(1) # page_obj = paginator.page(1)
except EmptyPage: # except EmptyPage:
page_obj = paginator.page(paginator.num_pages) # page_obj = paginator.page(paginator.num_pages)
else: # else:
template_name = "admin_management/model_logs.html" # template_name = "admin_management/model_logs.html"
context["title"] = "User Actions Dashboard" # context["title"] = "User Actions Dashboard"
# OPTIMIZATION: Get the QuerySet but don't evaluate it yet # # OPTIMIZATION: Get the QuerySet but don't evaluate it yet
model_events_queryset = CRUDEvent.objects.all().order_by("-datetime") # model_events_queryset = CRUDEvent.objects.all().order_by("-datetime")
# 1. Paginate the raw QuerySet FIRST # # 1. Paginate the raw QuerySet FIRST
paginator = Paginator(model_events_queryset, logs_per_page) # paginator = Paginator(model_events_queryset, logs_per_page)
try: # try:
# Get the page object, which contains only the raw QuerySet objects for the current page # # Get the page object, which contains only the raw QuerySet objects for the current page
page_obj_raw = paginator.page(current_pagination_page) # page_obj_raw = paginator.page(current_pagination_page)
except PageNotAnInteger: # except PageNotAnInteger:
page_obj_raw = paginator.page(1) # page_obj_raw = paginator.page(1)
except EmptyPage: # except EmptyPage:
page_obj_raw = paginator.page(paginator.num_pages) # page_obj_raw = paginator.page(paginator.num_pages)
# 2. Now, process 'field_changes' ONLY for the events on the current page # # 2. Now, process 'field_changes' ONLY for the events on the current page
processed_model_events_for_page = [] # processed_model_events_for_page = []
for ( # for (
event # event
) in page_obj_raw.object_list: # Loop only through the current page's items # ) in page_obj_raw.object_list: # Loop only through the current page's items
event_data = { # event_data = {
"datetime": event.datetime, # "datetime": event.datetime,
"user": event.user, # "user": event.user,
"event_type_display": event.get_event_type_display(), # "event_type_display": event.get_event_type_display(),
"model_name": event.content_type.model, # "model_name": event.content_type.model,
"object_id": event.object_id, # "object_id": event.object_id,
"object_repr": event.object_repr, # "object_repr": event.object_repr,
"field_changes": [], # "field_changes": [],
} # }
if event.changed_fields: # if event.changed_fields:
try: # try:
changes = json.loads(event.changed_fields) # changes = json.loads(event.changed_fields)
if isinstance(changes, dict): # if isinstance(changes, dict):
for field_name, values in changes.items(): # for field_name, values in changes.items():
old_value = ( # old_value = (
values[0] # values[0]
if isinstance(values, list) and len(values) > 0 # if isinstance(values, list) and len(values) > 0
else None # else None
) # )
new_value = ( # new_value = (
values[1] # values[1]
if isinstance(values, list) and len(values) > 1 # if isinstance(values, list) and len(values) > 1
else None # else None
) # )
event_data["field_changes"].append( # event_data["field_changes"].append(
{ # {
"field": field_name, # "field": field_name,
"old": old_value, # "old": old_value,
"new": new_value, # "new": new_value,
} # }
) # )
elif changes is None: # elif changes is None:
event_data["field_changes"].append( # event_data["field_changes"].append(
{ # {
"field": "Info", # "field": "Info",
"old": "", # "old": "",
"new": "No specific field changes recorded (JSON was null)", # "new": "No specific field changes recorded (JSON was null)",
} # }
) # )
else: # Handle valid JSON but not a dictionary (e.g., "[]", 123) # else: # Handle valid JSON but not a dictionary (e.g., "[]", 123)
event_data["field_changes"].append( # event_data["field_changes"].append(
{ # {
"field": "Error", # "field": "Error",
"old": "", # "old": "",
"new": f"Unexpected JSON format: {type(changes).__name__}", # "new": f"Unexpected JSON format: {type(changes).__name__}",
} # }
) # )
except json.JSONDecodeError: # except json.JSONDecodeError:
# Handle invalid JSON; you might log this error # # Handle invalid JSON; you might log this error
event_data["field_changes"].append( # event_data["field_changes"].append(
{ # {
"field": "Error", # "field": "Error",
"old": "", # "old": "",
"new": "Invalid JSON in changed_fields", # "new": "Invalid JSON in changed_fields",
} # }
) # )
processed_model_events_for_page.append(event_data) # processed_model_events_for_page.append(event_data)
# 3. Replace the object_list of the original page_obj with the processed data # # 3. Replace the object_list of the original page_obj with the processed data
# This keeps all pagination properties (has_next, number, etc.) intact. # # This keeps all pagination properties (has_next, number, etc.) intact.
page_obj_raw.object_list = processed_model_events_for_page # page_obj_raw.object_list = processed_model_events_for_page
page_obj = page_obj_raw # This will be passed to the context # page_obj = page_obj_raw # This will be passed to the context
# Pass the final page object to the context # # Pass the final page object to the context
context["page_obj"] = page_obj # context["page_obj"] = page_obj
return render(request, template_name, context) # return render(request, template_name, context)
@login_required @login_required