37733-vm/core/views.py
Flatlogic Bot bbe3291894 updates 27
2026-01-24 04:36:47 +00:00

586 lines
24 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
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')