37733-vm/core/views.py
Flatlogic Bot 3b66669105 update29
2026-01-24 06:19:14 +00:00

605 lines
25 KiB
Python

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 .models import Profile, Truck, Shipment, Bid, Message, OTPCode, Country, City, AppSetting, Banner, HomeSection, Transaction
from .forms import (
TruckForm, ShipmentForm, BidForm, UserRegistrationForm,
OTPVerifyForm, ShipperOfferForm, RenewSubscriptionForm, AppSettingForm
)
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 django.contrib.auth.forms import AuthenticationForm
from django.core.mail import send_mail
from django.conf import settings
import json
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')
context = {
"deployment_timestamp": timezone.now().timestamp(),
"banners": banners,
"home_sections": home_sections,
}
return render(request, "core/index.html", context)
def register(request):
app_settings = AppSetting.objects.first()
subscription_enabled = app_settings.subscription_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'], # We need raw password to create user later
'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'),
}
request.session['registration_data'] = registration_data
# Send OTP
full_phone = f"{registration_data['country_code']}{registration_data['phone_number']}"
otp = OTPCode.generate_code(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']}"
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')
if profile.subscription_plan != 'NONE':
profile.is_subscription_active = True
if profile.subscription_plan == 'MONTHLY':
profile.subscription_expiry = timezone.now().date() + timedelta(days=30)
elif profile.subscription_plan == 'ANNUAL':
profile.subscription_expiry = timezone.now().date() + timedelta(days=365)
profile.save()
login(request, user)
if 'registration_data' in request.session:
del request.session['registration_data']
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'})
def custom_login(request):
if request.method == 'POST':
form = AuthenticationForm(request, data=request.POST)
if form.is_valid():
user = form.get_user()
profile = user.profile
if not profile.phone_number:
messages.error(request, _("Your account does not have a phone number. Please contact admin."))
return redirect('login')
# Store user ID in session temporarily
request.session['pre_otp_user_id'] = user.id
# Send OTP
full_phone = profile.full_phone_number
otp = OTPCode.generate_code(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:
# If WhatsApp fails, maybe allow login but warn? Or strictly enforce?
# For now, strictly enforce
messages.error(request, _("Failed to send verification code. Please check your connection."))
else:
messages.error(request, _("Invalid username or password."))
else:
form = AuthenticationForm()
return render(request, 'registration/login.html', {'form': form})
def verify_otp_login(request):
user_id = request.session.get('pre_otp_user_id')
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']
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'})
@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()
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()
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:
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'],
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:
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():
profile = request.user.profile
plan = form.cleaned_data['subscription_plan']
# Calculate amount based on role and plan
app_settings = AppSetting.objects.first()
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
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()
# Create Transaction record
Transaction.objects.create(
user=request.user,
amount=amount,
transaction_type='PAYMENT',
status='COMPLETED',
description=f"Subscription Renewal: {plan}",
payment_method="Online Payment"
)
# 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. Thank you for using our service.") % {"date": expiry_date}
# WhatsApp
if profile.full_phone_number:
send_whatsapp_message(profile.full_phone_number, msg)
# Email
if request.user.email:
send_mail(
_("Subscription Renewed - MASAR CARGO"),
msg,
settings.DEFAULT_FROM_EMAIL,
[request.user.email],
fail_silently=True,
)
messages.success(request, _("Subscription renewed successfully!"))
return redirect('dashboard')
return redirect('subscription_expired')
@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})