from .thawani import ThawaniClient from datetime import timedelta from django.shortcuts import render, redirect, get_object_or_404 from django.contrib.auth.decorators import login_required from django.contrib.auth import login, authenticate, logout from django.utils import timezone from django.urls import reverse from .models import ( Profile, Truck, Shipment, Bid, Message, OTPCode, Country, City, AppSetting, Banner, HomeSection, Transaction, ContactMessage, Testimonial, WhatsAppConfig ) from .forms import ( CustomLoginForm, TruckForm, ShipmentForm, BidForm, UserRegistrationForm, OTPVerifyForm, ShipperOfferForm, RenewSubscriptionForm, AppSettingForm, ContactForm, ProfileForm ) from django.contrib import messages from django.utils.translation import gettext as _ from django.db.models import Q from django.contrib.auth.models import User from .whatsapp import send_whatsapp_message from .mail import send_otp_email, send_contact_message from django.contrib.auth.forms import AuthenticationForm from django.core.mail import send_mail from django.conf import settings from django.views.decorators.csrf import csrf_exempt from django.http import HttpResponse, JsonResponse from django.contrib.sites.shortcuts import get_current_site from django.utils.translation import get_language from ai.local_ai_api import LocalAIApi import json import logging import os logger = logging.getLogger(__name__) def home(request): """Render the landing screen for MASAR CARGO.""" banners = Banner.objects.filter(is_active=True) home_sections = HomeSection.objects.filter(is_active=True).order_by('order') testimonials = Testimonial.objects.filter(is_active=True).order_by('order') context = { "deployment_timestamp": timezone.now().timestamp(), "banners": banners, "home_sections": home_sections, "testimonials": testimonials, } return render(request, "core/index.html", context) def register(request): app_settings = AppSetting.objects.first() subscription_enabled = (app_settings.subscription_enabled and app_settings.thawani_enabled) if app_settings else False # Simplified fees dictionary for JS # Ensuring keys are exactly as they appear in Profile.ROLE_CHOICES fees = { 'SHIPPER': { 'MONTHLY': str(app_settings.shipper_monthly_fee) if app_settings else "0.00", 'ANNUAL': str(app_settings.shipper_annual_fee) if app_settings else "0.00", }, 'TRUCK_OWNER': { 'MONTHLY': str(app_settings.truck_owner_monthly_fee) if app_settings else "0.00", 'ANNUAL': str(app_settings.truck_owner_annual_fee) if app_settings else "0.00", } } if request.method == 'POST': form = UserRegistrationForm(request.POST) if form.is_valid(): # Store data in session to be used after OTP verification registration_data = { 'username': form.cleaned_data['username'], 'email': form.cleaned_data['email'], 'password': form.data['password1'], 'role': form.cleaned_data['role'], 'phone_number': form.cleaned_data['phone_number'], 'country_code': form.cleaned_data['country_code'], 'subscription_plan': form.cleaned_data.get('subscription_plan', 'NONE'), 'otp_method': form.cleaned_data.get('otp_method', 'whatsapp'), } request.session['registration_data'] = registration_data # Send OTP full_phone = f"{registration_data['country_code']}{registration_data['phone_number']}" email = registration_data['email'] otp_method = registration_data.get('otp_method', 'whatsapp') if otp_method == 'email': otp = OTPCode.generate_code(email=email) if send_otp_email(email, otp.code): messages.info(request, _("A verification code has been sent to your email.")) return redirect('verify_otp_registration') else: messages.error(request, _("Failed to send verification code to your email.")) else: otp = OTPCode.generate_code(phone_number=full_phone) msg = _("Your verification code for MASAR CARGO is: %(code)s") % {"code": otp.code} if send_whatsapp_message(full_phone, msg): messages.info(request, _("A verification code has been sent to your WhatsApp.")) return redirect('verify_otp_registration') else: messages.error(request, _("Failed to send verification code. Please check your phone number.")) else: messages.error(request, _("Please correct the errors below.")) else: form = UserRegistrationForm() return render(request, 'registration/register.html', { 'form': form, 'subscription_enabled': subscription_enabled, 'fees_json': json.dumps(fees) }) def verify_otp_registration(request): registration_data = request.session.get('registration_data') if not registration_data: return redirect('register') if request.method == 'POST': form = OTPVerifyForm(request.POST) if form.is_valid(): code = form.cleaned_data['otp_code'] full_phone = f"{registration_data['country_code']}{registration_data['phone_number']}" email = registration_data['email'] otp_method = registration_data.get('otp_method', 'whatsapp') if otp_method == 'email': otp_record = OTPCode.objects.filter(email=email, code=code, is_used=False).last() else: otp_record = OTPCode.objects.filter(phone_number=full_phone, code=code, is_used=False).last() if otp_record and otp_record.is_valid(): otp_record.is_used = True otp_record.save() # Create user user = User.objects.create_user( username=registration_data['username'], email=registration_data['email'], password=registration_data['password'] ) profile = user.profile profile.role = registration_data['role'] profile.phone_number = registration_data['phone_number'] profile.country_code = registration_data['country_code'] profile.subscription_plan = registration_data.get('subscription_plan', 'NONE') profile.save() login(request, user) if 'registration_data' in request.session: del request.session['registration_data'] if profile.subscription_plan != 'NONE': return thawani_checkout(request, profile.subscription_plan) messages.success(request, _("Registration successful. Welcome!")) return redirect('dashboard') else: messages.error(request, _("Invalid or expired verification code.")) else: form = OTPVerifyForm() return render(request, 'registration/verify_otp.html', {'form': form, 'purpose': 'registration', 'otp_method': registration_data.get('otp_method', 'whatsapp')}) def custom_login(request): if request.method == 'POST': form = CustomLoginForm(request, data=request.POST) if form.is_valid(): user = form.get_user() profile = user.profile otp_method = form.cleaned_data.get('otp_method', 'whatsapp') # Store user ID and method in session temporarily request.session['pre_otp_user_id'] = user.id request.session['pre_otp_method'] = otp_method if otp_method == 'email': if not user.email: messages.error(request, _("Your account does not have an email address. Please use WhatsApp or contact admin.")) return redirect('login') otp = OTPCode.generate_code(email=user.email) if send_otp_email(user.email, otp.code): messages.info(request, _("A verification code has been sent to your email.")) return redirect('verify_otp_login') else: messages.error(request, _("Failed to send verification code to your email.")) else: if not profile.phone_number: messages.error(request, _("Your account does not have a phone number. Please contact admin.")) return redirect('login') full_phone = profile.full_phone_number otp = OTPCode.generate_code(phone_number=full_phone) msg = _("Your login verification code for MASAR CARGO is: %(code)s") % {"code": otp.code} if send_whatsapp_message(full_phone, msg): messages.info(request, _("A verification code has been sent to your WhatsApp.")) return redirect('verify_otp_login') else: messages.error(request, _("Failed to send verification code. Please check your connection.")) else: messages.error(request, _("Invalid username or password.")) else: form = CustomLoginForm() return render(request, 'registration/login.html', {'form': form}) def verify_otp_login(request): user_id = request.session.get('pre_otp_user_id') otp_method = request.session.get('pre_otp_method', 'whatsapp') if not user_id: return redirect('login') user = get_object_or_404(User, id=user_id) profile = user.profile if request.method == 'POST': form = OTPVerifyForm(request.POST) if form.is_valid(): code = form.cleaned_data['otp_code'] if otp_method == 'email': otp_record = OTPCode.objects.filter(email=user.email, code=code, is_used=False).last() else: full_phone = profile.full_phone_number otp_record = OTPCode.objects.filter(phone_number=full_phone, code=code, is_used=False).last() if otp_record and otp_record.is_valid(): otp_record.is_used = True otp_record.save() login(request, user) if 'pre_otp_user_id' in request.session: del request.session['pre_otp_user_id'] messages.success(request, _("Logged in successfully!")) return redirect('dashboard') else: messages.error(request, _("Invalid or expired verification code.")) else: form = OTPVerifyForm() return render(request, 'registration/verify_otp.html', {'form': form, 'purpose': 'login', 'otp_method': otp_method}) @login_required def profile_view(request): profile = request.user.profile if request.method == 'POST': form = ProfileForm(request.POST, request.FILES, instance=profile, user=request.user) if form.is_valid(): form.save() messages.success(request, _("Profile updated successfully!")) return redirect('profile') else: form = ProfileForm(instance=profile, user=request.user) return render(request, 'core/profile.html', {'form': form, 'profile': profile}) @login_required def dashboard(request): profile, created = Profile.objects.get_or_create(user=request.user) if profile.role == 'SHIPPER': my_shipments = Shipment.objects.filter(shipper=request.user).order_by('-created_at') # In the new flow, Shippers place bids. my_bids = Bid.objects.filter(shipment__shipper=request.user).order_by('-created_at') return render(request, 'core/shipper_dashboard.html', { 'shipments': my_shipments, 'bids': my_bids }) elif profile.role == 'TRUCK_OWNER': approved_trucks = Truck.objects.filter(owner=request.user, is_approved=True) pending_trucks = Truck.objects.filter(owner=request.user, is_approved=False) # Truck owners receive bids in the new flow my_received_bids = Bid.objects.filter(truck_owner=request.user).order_by('-created_at') return render(request, 'core/truck_owner_dashboard.html', { 'trucks': approved_trucks, 'pending_trucks': pending_trucks, 'bids': my_received_bids }) elif profile.role == 'ADMIN' or request.user.is_superuser: pending_trucks = Truck.objects.filter(is_approved=False).order_by('-created_at') approved_trucks = Truck.objects.filter(is_approved=True).order_by('-created_at') # Subscription stats today = timezone.now().date() total_profiles = Profile.objects.exclude(role='ADMIN') active_subscriptions = 0 expired_subscriptions = 0 for p in total_profiles: if p.is_expired(): expired_subscriptions += 1 else: active_subscriptions += 1 context = { 'total_users': User.objects.count(), 'total_trucks': Truck.objects.count(), 'total_shipments': Shipment.objects.count(), 'total_bids': Bid.objects.count(), 'pending_trucks': pending_trucks, 'approved_trucks': approved_trucks, 'active_subscriptions': active_subscriptions, 'expired_subscriptions': expired_subscriptions, } return render(request, 'core/admin_dashboard.html', context) else: return redirect('/') @login_required def truck_register(request): if request.user.profile.role != 'TRUCK_OWNER': return redirect('dashboard') if request.method == 'POST': form = TruckForm(request.POST, request.FILES) if form.is_valid(): truck = form.save(commit=False) truck.owner = request.user truck.is_approved = False truck.save() # Notify Admin via WhatsApp config = WhatsAppConfig.objects.filter(is_active=True).first() if config and config.admin_phone: admin_msg = _("New Truck Registration: %(owner)s registered a truck with plate %(plate)s. Please review it in the admin dashboard.") % { "owner": truck.owner.username, "plate": truck.plate_no } send_whatsapp_message(config.admin_phone, admin_msg) messages.success(request, _("Truck registered successfully! It will be visible after admin approval.")) return redirect('dashboard') else: messages.error(request, _("There was an error in your registration. Please check the form.")) else: form = TruckForm() return render(request, 'core/truck_register.html', {'form': form}) @login_required def edit_truck(request, truck_id): truck = get_object_or_404(Truck, id=truck_id, owner=request.user) if request.method == 'POST': form = TruckForm(request.POST, request.FILES, instance=truck) if form.is_valid(): truck = form.save(commit=False) truck.is_approved = False truck.save() # Notify Admin via WhatsApp config = WhatsAppConfig.objects.filter(is_active=True).first() if config and config.admin_phone: admin_msg = _("Truck Update: %(owner)s updated truck with plate %(plate)s. Please review it again in the admin dashboard.") % { "owner": truck.owner.username, "plate": truck.plate_no } send_whatsapp_message(config.admin_phone, admin_msg) messages.success(request, _("Truck data updated successfully! It will be reviewed by admin again.")) return redirect('dashboard') else: messages.error(request, _("There was an error updating your truck. Please check the form.")) else: form = TruckForm(instance=truck) return render(request, 'core/truck_register.html', {'form': form, 'edit_mode': True, 'truck': truck}) @login_required def approve_truck(request, truck_id): if not (request.user.profile.role == 'ADMIN' or request.user.is_superuser): return redirect('dashboard') truck = get_object_or_404(Truck, id=truck_id) truck.is_approved = True truck.save() owner_phone = getattr(truck.owner.profile, 'full_phone_number', None) if owner_phone: msg = _("Your truck (%(plate)s) has been approved! You can now receive offers for shipments.") % {"plate": truck.plate_no} send_whatsapp_message(owner_phone, msg) messages.success(request, _("Truck approved successfully!")) return redirect('dashboard') @login_required def suspend_truck(request, truck_id): if not (request.user.profile.role == 'ADMIN' or request.user.is_superuser): return redirect('dashboard') truck = get_object_or_404(Truck, id=truck_id) truck.is_approved = False truck.save() messages.warning(request, _("Truck has been suspended.")) return redirect('dashboard') @login_required def post_shipment(request): """Note: This is used as the 'Add A Bid' / 'Add Post' action for Shippers.""" if request.user.profile.role != 'SHIPPER': return redirect('dashboard') if request.method == 'POST': form = ShipmentForm(request.POST) if form.is_valid(): shipment = form.save(commit=False) shipment.shipper = request.user shipment.save() messages.success(request, _("Shipment posted successfully! It is now open for bids or you can browse trucks to send it as an offer.")) return redirect('dashboard') else: logger.error(f"Post Shipment Form Errors: {form.errors}") messages.error(request, _("Please correct the errors in the form.")) else: form = ShipmentForm() return render(request, 'core/post_shipment.html', {'form': form}) @login_required def marketplace(request): """Shippers browse available trucks here.""" if request.user.profile.role != 'SHIPPER': return redirect('dashboard') trucks = Truck.objects.filter(is_approved=True).order_by('-created_at') return render(request, 'core/marketplace.html', {'trucks': trucks}) @login_required def place_bid(request, truck_id): """Shipper makes an offer to a specific truck.""" truck = get_object_or_404(Truck, id=truck_id, is_approved=True) if request.user.profile.role != 'SHIPPER': return redirect('dashboard') if request.method == 'POST': form = ShipperOfferForm(request.POST) if form.is_valid(): # Create Shipment shipment = Shipment.objects.create( shipper=request.user, description=form.cleaned_data['description'], required_truck_type_link=form.cleaned_data["required_truck_type_link"], weight=form.cleaned_data['weight'], origin_country=form.cleaned_data['origin_country'], origin_city=form.cleaned_data['origin_city'], destination_country=form.cleaned_data['destination_country'], destination_city=form.cleaned_data['destination_city'], delivery_date=form.cleaned_data['delivery_date'], status='OPEN' ) # Create Bid (Offer) bid = Bid.objects.create( shipment=shipment, truck_owner=truck.owner, truck=truck, amount=form.cleaned_data['amount'], comments=form.cleaned_data.get('comments', ''), status='PENDING' ) # Notify Truck Owner via WhatsApp owner_phone = getattr(truck.owner.profile, 'full_phone_number', None) if owner_phone: msg = _("New offer received for your truck (%(plate)s)! Route: %(origin)s to %(dest)s. Amount: %(amount)s") % {"plate": truck.plate_no, "origin": shipment.display_origin, "dest": shipment.display_destination, "amount": bid.amount} send_whatsapp_message(owner_phone, msg) messages.success(request, _("Offer sent successfully!")) return redirect('dashboard') else: logger.error(f"Place Bid Form Errors: {form.errors}") messages.error(request, _("Error sending offer. Please check the form.")) else: form = ShipperOfferForm() return render(request, 'core/place_bid.html', {'form': form, 'truck': truck}) @login_required def shipment_detail(request, shipment_id): shipment = get_object_or_404(Shipment, id=shipment_id) # Security: check if user is shipper, truck owner of a bid, or admin is_involved = Bid.objects.filter(shipment=shipment, truck_owner=request.user).exists() or shipment.shipper == request.user if not is_involved and not (request.user.profile.role == 'ADMIN' or request.user.is_superuser): return redirect('dashboard') bids = shipment.bids.all() return render(request, 'core/shipment_detail.html', {'shipment': shipment, 'bids': bids}) @login_required def accept_bid(request, bid_id): """Truck owner accepts an offer from a shipper.""" bid = get_object_or_404(Bid, id=bid_id) if bid.truck_owner != request.user: messages.error(request, _("You are not authorized to accept this offer.")) return redirect('dashboard') # Accept this bid bid.status = 'ACCEPTED' bid.save() # Update shipment bid.shipment.status = 'IN_PROGRESS' bid.shipment.assigned_truck = bid.truck bid.shipment.save() # Notify Shipper via WhatsApp shipper_phone = getattr(bid.shipment.shipper.profile, 'full_phone_number', None) if shipper_phone: msg = _("Your offer for truck %(plate)s (%(origin)s to %(dest)s) has been accepted!") % {"plate": bid.truck.plate_no, "origin": bid.shipment.display_origin, "dest": bid.shipment.display_destination} send_whatsapp_message(shipper_phone, msg) messages.success(request, _("Offer accepted! Shipment is now in progress.")) return redirect('dashboard') @login_required def reject_bid(request, bid_id): """Truck owner rejects an offer.""" bid = get_object_or_404(Bid, id=bid_id) if bid.truck_owner != request.user: return redirect('dashboard') bid.status = 'REJECTED' bid.save() messages.info(request, _("Offer rejected.")) return redirect('dashboard') def privacy_policy(request): app_settings = AppSetting.objects.first() context = { 'article': { 'title': _('Privacy Policy'), 'content': app_settings.privacy_policy if app_settings else _("Privacy policy is coming soon.") } } return render(request, 'core/article_detail.html', context) def terms_of_service(request): app_settings = AppSetting.objects.first() context = { 'article': { 'title': _('Terms of Service'), 'content': app_settings.terms_of_service if app_settings else _("Terms of service are soon.") } } return render(request, 'core/article_detail.html', context) @login_required def subscription_expired(request): profile = request.user.profile if not profile.is_expired(): return redirect('dashboard') app_settings = AppSetting.objects.first() form = RenewSubscriptionForm() # Simplified fees dictionary for JS fees = { 'SHIPPER': { 'MONTHLY': str(app_settings.shipper_monthly_fee) if app_settings else "0.00", 'ANNUAL': str(app_settings.shipper_annual_fee) if app_settings else "0.00", }, 'TRUCK_OWNER': { 'MONTHLY': str(app_settings.truck_owner_monthly_fee) if app_settings else "0.00", 'ANNUAL': str(app_settings.truck_owner_annual_fee) if app_settings else "0.00", } } return render(request, 'core/subscription_expired.html', { 'profile': profile, 'app_settings': app_settings, 'form': form, 'fees_json': json.dumps(fees) }) @login_required def renew_subscription(request): if request.method == 'POST': form = RenewSubscriptionForm(request.POST) if form.is_valid(): plan = form.cleaned_data['subscription_plan'] return thawani_checkout(request, plan) return redirect('subscription_expired') @login_required def thawani_checkout(request, plan): profile = request.user.profile app_settings = AppSetting.objects.first() if app_settings and not app_settings.thawani_enabled: messages.error(request, _("Online payment is currently disabled. Please contact support.")) return redirect("dashboard") amount = 0 if profile.role == 'SHIPPER': amount = app_settings.shipper_monthly_fee if plan == 'MONTHLY' else app_settings.shipper_annual_fee elif profile.role == 'TRUCK_OWNER': amount = app_settings.truck_owner_monthly_fee if plan == 'MONTHLY' else app_settings.truck_owner_annual_fee # Thawani expects amount in baisa (1 OMR = 1000 baisa) amount_in_baisa = int(amount * 1000) client = ThawaniClient() # Generate a unique reference transaction = Transaction.objects.create( user=request.user, amount=amount, transaction_type='PAYMENT', status='PENDING', description=f"Subscription: {plan}", payment_method="Thawani" ) payload = { "client_reference_id": transaction.receipt_number, "products": [ { "name": f"MASAR CARGO {plan} Subscription", "unit_amount": amount_in_baisa, "quantity": 1 } ], "success_url": request.build_absolute_uri(reverse('thawani_success')), "cancel_url": request.build_absolute_uri(reverse('thawani_cancel')), "metadata": { "plan": plan, "user_id": request.user.id, "transaction_id": transaction.id } } session = client.create_checkout_session(payload) if session and session.get('success'): session_id = session['data']['session_id'] transaction.session_id = session_id transaction.save() return redirect(client.get_payment_url(session_id)) else: messages.error(request, _("Failed to initiate payment. Please try again later.")) return redirect('dashboard') def _activate_subscription_and_notify(transaction, plan, request=None): """ Helper to activate subscription, send WhatsApp/Email, and handle receipt link. """ # Activate Subscription profile = transaction.user.profile profile.subscription_plan = plan profile.is_subscription_active = True if plan == 'MONTHLY': profile.subscription_expiry = timezone.now().date() + timedelta(days=30) elif plan == 'ANNUAL': profile.subscription_expiry = timezone.now().date() + timedelta(days=365) profile.save() # Get receipt URL if request: receipt_url = request.build_absolute_uri(reverse('transaction_receipt', args=[transaction.receipt_number])) else: # Fallback for webhook or when request is missing host = os.getenv('HOST_FQDN', 'masarcargo.com') receipt_url = f"https://{host}{reverse('transaction_receipt', args=[transaction.receipt_number])}" # Notifications expiry_date = profile.subscription_expiry.strftime('%Y-%m-%d') msg = _("Your subscription for MASAR CARGO has been successfully renewed! Your new expiry date is %(date)s. You can view your receipt here: %(url)s") % { "date": expiry_date, "url": receipt_url } if profile.full_phone_number: send_whatsapp_message(profile.full_phone_number, msg) if transaction.user.email: send_mail( _("Subscription Activated - MASAR CARGO"), msg, settings.DEFAULT_FROM_EMAIL, [transaction.user.email], fail_silently=True, ) return receipt_url @login_required def thawani_success(request): session_id = request.GET.get('session_id') if not session_id: transaction = Transaction.objects.filter(user=request.user, status='PENDING', payment_method='Thawani').first() else: transaction = get_object_or_404(Transaction, session_id=session_id, user=request.user) client = ThawaniClient() session_data = client.get_checkout_session(transaction.session_id) if session_data and session_data.get('success'): payment_status = session_data['data']['payment_status'] transaction.payment_status = payment_status if payment_status == 'paid': transaction.status = 'COMPLETED' transaction.save() plan = session_data['data']['metadata'].get('plan', transaction.user.profile.subscription_plan) _activate_subscription_and_notify(transaction, plan, request) messages.success(request, _("Payment successful! Your subscription is now active.")) # Redirect directly to the receipt for "Automated Invoice" feel return redirect('transaction_receipt', receipt_number=transaction.receipt_number) else: transaction.status = 'FAILED' transaction.save() messages.error(request, _("Payment was not successful. Status: %(status)s") % {'status': payment_status}) else: messages.error(request, _("Failed to verify payment status.")) return redirect('dashboard') @login_required def thawani_cancel(request): messages.warning(request, _("Payment was cancelled.")) return redirect('dashboard') @csrf_exempt def thawani_webhook(request): if request.method == 'POST': try: data = json.loads(request.body) event_type = data.get('event_type') if event_type == 'checkout.completed': session_id = data['data']['session_id'] payment_status = data['data']['payment_status'] transaction = Transaction.objects.filter(session_id=session_id).first() if transaction and transaction.status == 'PENDING': transaction.payment_status = payment_status if payment_status == 'paid': transaction.status = 'COMPLETED' transaction.save() plan = data['data']['metadata'].get('plan', transaction.user.profile.subscription_plan) _activate_subscription_and_notify(transaction, plan, request) else: transaction.status = 'FAILED' transaction.save() return HttpResponse(status=200) except Exception as e: return HttpResponse(status=400) return HttpResponse(status=405) @login_required def financial_history(request): transactions = Transaction.objects.filter(user=request.user) return render(request, 'core/financial_history.html', {'transactions': transactions}) @login_required def transaction_receipt(request, receipt_number): transaction = get_object_or_404(Transaction, receipt_number=receipt_number, user=request.user) app_settings = AppSetting.objects.first() return render(request, 'core/receipt.html', { 'transaction': transaction, 'app_settings': app_settings }) @login_required def admin_financials(request): if request.user.profile.role != 'ADMIN': return redirect('dashboard') transactions = Transaction.objects.all() total_revenue = sum(t.amount for t in transactions if t.transaction_type == 'PAYMENT' and t.status == 'COMPLETED') return render(request, 'core/admin_financials.html', { 'transactions': transactions, 'total_revenue': total_revenue }) @login_required def issue_refund(request, receipt_number): if request.user.profile.role != 'ADMIN': return redirect('dashboard') transaction = get_object_or_404(Transaction, receipt_number=receipt_number) if transaction.transaction_type == 'REFUND': messages.error(request, _("This is already a refund transaction.")) return redirect('admin_financials') # Create a refund transaction refund = Transaction.objects.create( user=transaction.user, amount=transaction.amount, transaction_type='REFUND', status='COMPLETED', description=f"Refund for Receipt: {transaction.receipt_number}", payment_method=transaction.payment_method ) messages.success(request, _("Refund issued successfully! Receipt: %(receipt)s") % {'receipt': refund.receipt_number}) return redirect('admin_financials') @login_required def admin_app_settings(request): if not (request.user.profile.role == 'ADMIN' or request.user.is_superuser): return redirect('dashboard') settings_obj = AppSetting.objects.first() if request.method == 'POST': form = AppSettingForm(request.POST, request.FILES, instance=settings_obj) if form.is_valid(): form.save() messages.success(request, _("Application settings updated successfully.")) return redirect('admin_app_settings') else: form = AppSettingForm(instance=settings_obj) return render(request, 'core/app_settings.html', {'form': form}) def contact(request): app_settings = AppSetting.objects.first() if request.method == 'POST': form = ContactForm(request.POST) if form.is_valid(): form.save() messages.success(request, _("Your message has been sent successfully! We will get back to you soon.")) return redirect('contact') else: messages.error(request, _("There was an error in your form. Please check the fields below.")) else: form = ContactForm() if request.user.is_authenticated: # Pre-fill name and email if user is logged in form.fields['name'].initial = request.user.get_full_name() or request.user.username form.fields['email'].initial = request.user.email return render(request, 'core/contact.html', { 'form': form, 'app_settings': app_settings }) @csrf_exempt def chat_api(request): if request.method != 'POST': return JsonResponse({'success': False, 'error': 'Method not allowed'}, status=405) try: data = json.loads(request.body) user_message = data.get('message', '') if not user_message: return JsonResponse({'success': False, 'error': 'Message is empty'}) # Maintain conversation history in session history = request.session.get('chat_history', []) # System prompt to define the bot's personality system_prompt = { "role": "system", "content": "You are MASAR, the AI assistant for MASAR CARGO, a logistics and trucking marketplace in Oman. " "You help shippers find trucks and truck owners find shipments. " "Answer questions politely and concisely. If the user asks for something you can't do, " "suggest contacting support via WhatsApp. " "Current language: " + (get_language() or 'en') } messages_input = [system_prompt] + history + [{"role": "user", "content": user_message}] response = LocalAIApi.create_response({ "input": messages_input }) if response.get("success"): ai_reply = LocalAIApi.extract_text(response) # Update history history.append({"role": "user", "content": user_message}) history.append({"role": "assistant", "content": ai_reply}) # Keep only last 10 messages for context efficiency request.session['chat_history'] = history[-10:] return JsonResponse({'success': True, 'reply': ai_reply}) else: logger.error(f"AI Error: {response.get('error')}") return JsonResponse({'success': False, 'error': 'AI failed to respond'}) except Exception as e: logger.error(f"Chat API Error: {str(e)}") return JsonResponse({'success': False, 'error': str(e)}) @login_required @csrf_exempt def send_otp_profile(request): if request.method != 'POST': return JsonResponse({'success': False, 'error': 'Method not allowed'}, status=405) try: data = json.loads(request.body) method = data.get('method') # 'email' or 'phone' value = data.get('value') if method == 'email': otp = OTPCode.generate_code(email=value) if send_otp_email(value, otp.code): return JsonResponse({'success': True}) else: return JsonResponse({'success': False, 'error': _('Failed to send email')}) elif method == 'phone': country_code = data.get('country_code', '966') full_phone = f"{country_code}{value}" otp = OTPCode.generate_code(phone_number=full_phone) msg = _("Your verification code is: %(code)s") % {"code": otp.code} if send_whatsapp_message(full_phone, msg): return JsonResponse({'success': True}) else: return JsonResponse({'success': False, 'error': _('Failed to send WhatsApp message')}) return JsonResponse({'success': False, 'error': 'Invalid method'}) except Exception as e: return JsonResponse({'success': False, 'error': str(e)}) @login_required @csrf_exempt def verify_otp_profile(request): if request.method != 'POST': return JsonResponse({'success': False, 'error': 'Method not allowed'}, status=405) try: data = json.loads(request.body) method = data.get('method') value = data.get('value') code = data.get('code') if method == 'email': otp_record = OTPCode.objects.filter(email=value, code=code, is_used=False).last() elif method == 'phone': country_code = data.get('country_code', '966') full_phone = f"{country_code}{value}" otp_record = OTPCode.objects.filter(phone_number=full_phone, code=code, is_used=False).last() else: return JsonResponse({'success': False, 'error': 'Invalid method'}) if otp_record and otp_record.is_valid(): otp_record.is_used = True otp_record.save() return JsonResponse({'success': True}) else: return JsonResponse({'success': False, 'error': _('Invalid or expired code')}) except Exception as e: return JsonResponse({'success': False, 'error': str(e)})