37794-vm/core/views.py
2026-01-28 00:04:21 +00:00

932 lines
35 KiB
Python

from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth import login, authenticate, logout
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from .models import Parcel, Profile, Country, Governate, City, OTPVerification, PlatformProfile, Testimonial, DriverRating
from .forms import UserRegistrationForm, ParcelForm, ContactForm, UserProfileForm, DriverRatingForm, ShipperRegistrationForm, DriverRegistrationForm
from django.utils.translation import gettext_lazy as _
from django.utils.translation import get_language
from django.contrib import messages
from django.http import JsonResponse, HttpResponse
from django.urls import reverse
from .payment_utils import ThawaniPay
from django.conf import settings
from django.core.mail import send_mail
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.views.decorators.http import require_POST
from django.db.models import Avg, Count
from django.template.loader import render_to_string
import random
import string
from .whatsapp_utils import (
notify_shipment_created,
notify_payment_received,
notify_driver_assigned,
notify_status_change,
send_whatsapp_message
)
from .mail import send_contact_message, send_html_email
import json
from ai.local_ai_api import LocalAIApi
import weasyprint
import qrcode
from io import BytesIO
import base64
def index(request):
# If tracking_id is present, redirect to the new track view
tracking_id = request.GET.get('tracking_id')
if tracking_id:
return redirect(f"{reverse('track')}?tracking_number={tracking_id}")
testimonials = Testimonial.objects.filter(is_active=True)
# Top 5 Drivers (by Average Rating)
top_drivers = Profile.objects.filter(role='car_owner').annotate(
avg_rating=Avg('user__received_ratings__rating'),
rating_count=Count('user__received_ratings')
).filter(rating_count__gt=0).order_by('-avg_rating')[:5]
# Top 5 Shippers (by Shipment Count)
top_shippers = Profile.objects.filter(role='shipper').annotate(
shipment_count=Count('user__sent_parcels')
).order_by('-shipment_count')[:5]
return render(request, 'core/index.html', {
'testimonials': testimonials,
'top_drivers': top_drivers,
'top_shippers': top_shippers
})
def track_parcel(request):
tracking_number = request.GET.get('tracking_number')
parcel = None
error = None
if tracking_number:
try:
parcel = Parcel.objects.get(tracking_number__iexact=tracking_number.strip())
except Parcel.DoesNotExist:
error = _("Parcel not found with this Tracking ID.")
return render(request, 'core/track.html', {
'parcel': parcel,
'error': error,
'tracking_number': tracking_number
})
def register(request):
return render(request, 'core/register_choice.html')
def register_shipper(request):
if request.method == 'POST':
form = ShipperRegistrationForm(request.POST)
if form.is_valid():
# Save user but inactive
user = form.save(commit=True)
user.is_active = False
user.save()
# Generate OTP
code = ''.join(random.choices(string.digits, k=6))
OTPVerification.objects.create(user=user, code=code, purpose='registration')
# Send OTP
method = form.cleaned_data.get('verification_method', 'email')
otp_msg = _("Your Masar Verification Code is %(code)s") % {'code': code}
if method == 'whatsapp':
phone = user.profile.phone_number
send_whatsapp_message(phone, otp_msg)
messages.info(request, _("Verification code sent to WhatsApp."))
else:
send_html_email(
subject=_('Verification Code'),
message=otp_msg,
recipient_list=[user.email],
title=_('Welcome to Masar!'),
request=request
)
messages.info(request, _("Verification code sent to email."))
request.session['registration_user_id'] = user.id
return redirect('verify_registration')
else:
form = ShipperRegistrationForm()
return render(request, 'core/register_shipper.html', {'form': form})
def register_driver(request):
if request.method == 'POST':
form = DriverRegistrationForm(request.POST, request.FILES)
if form.is_valid():
# Save user but inactive
user = form.save(commit=True)
user.is_active = False
user.save()
# Generate OTP
code = ''.join(random.choices(string.digits, k=6))
OTPVerification.objects.create(user=user, code=code, purpose='registration')
# Send OTP
method = form.cleaned_data.get('verification_method', 'email')
otp_msg = _("Your Masar Verification Code is %(code)s") % {'code': code}
if method == 'whatsapp':
phone = user.profile.phone_number
send_whatsapp_message(phone, otp_msg)
messages.info(request, _("Verification code sent to WhatsApp."))
else:
send_html_email(
subject=_('Verification Code'),
message=otp_msg,
recipient_list=[user.email],
title=_('Welcome to Masar!'),
request=request
)
messages.info(request, _("Verification code sent to email."))
request.session['registration_user_id'] = user.id
return redirect('verify_registration')
else:
form = DriverRegistrationForm()
return render(request, 'core/register_driver.html', {'form': form})
def verify_registration(request):
if 'registration_user_id' not in request.session:
messages.error(request, _("Session expired or invalid."))
return redirect('register')
if request.method == 'POST':
code = request.POST.get('code')
user_id = request.session['registration_user_id']
try:
user = User.objects.get(id=user_id)
otp = OTPVerification.objects.filter(
user=user,
code=code,
purpose='registration',
is_verified=False
).latest('created_at')
if otp.is_valid():
# Activate User
user.is_active = True
user.save()
# Cleanup
otp.is_verified = True
otp.save()
del request.session['registration_user_id']
# Login
login(request, user)
messages.success(request, _("Account verified successfully!"))
return redirect('dashboard')
else:
messages.error(request, _("Invalid or expired code."))
except (User.DoesNotExist, OTPVerification.DoesNotExist):
messages.error(request, _("Invalid code."))
return render(request, 'core/verify_registration.html')
@login_required
def dashboard(request):
# Ensure profile exists
profile, created = Profile.objects.get_or_create(user=request.user)
if profile.role == 'shipper':
all_parcels = Parcel.objects.filter(shipper=request.user).order_by('-created_at')
active_parcels_list = all_parcels.exclude(status__in=['delivered', 'cancelled'])
# Split history into delivered and cancelled
delivered_parcels = all_parcels.filter(status='delivered')
cancelled_parcels = all_parcels.filter(status='cancelled')
# Pagination for Active Shipments
page = request.GET.get('page', 1)
paginator = Paginator(active_parcels_list, 9) # Show 9 parcels per page
try:
active_parcels = paginator.page(page)
except PageNotAnInteger:
active_parcels = paginator.page(1)
except EmptyPage:
active_parcels = paginator.page(paginator.num_pages)
platform_profile = PlatformProfile.objects.first()
payments_enabled = platform_profile.enable_payment if platform_profile else True
return render(request, 'core/shipper_dashboard.html', {
'active_parcels': active_parcels,
'delivered_parcels': delivered_parcels,
'cancelled_parcels': cancelled_parcels,
'payments_enabled': payments_enabled,
'platform_profile': platform_profile # Pass full profile just in case
})
else:
# Car Owner view
platform_profile = PlatformProfile.objects.first()
payments_enabled = platform_profile.enable_payment if platform_profile else True
if payments_enabled:
available_parcels_list = Parcel.objects.filter(status='pending', payment_status='paid').order_by('-created_at')
else:
available_parcels_list = Parcel.objects.filter(status='pending').order_by('-created_at')
# Pagination for Available Shipments
page = request.GET.get('page', 1)
paginator = Paginator(available_parcels_list, 9) # Show 9 parcels per page
try:
available_parcels = paginator.page(page)
except PageNotAnInteger:
available_parcels = paginator.page(1)
except EmptyPage:
available_parcels = paginator.page(paginator.num_pages)
# Active: Picked up or In Transit
my_parcels = Parcel.objects.filter(carrier=request.user).exclude(status__in=['delivered', 'cancelled']).order_by('-created_at')
# History: Delivered
completed_parcels = Parcel.objects.filter(carrier=request.user, status='delivered').order_by('-created_at')
# Cancelled
cancelled_parcels = Parcel.objects.filter(carrier=request.user, status='cancelled').order_by('-created_at')
return render(request, 'core/driver_dashboard.html', {
'available_parcels': available_parcels,
'my_parcels': my_parcels,
'completed_parcels': completed_parcels,
'cancelled_parcels': cancelled_parcels
})
@login_required
def shipment_request(request):
profile, created = Profile.objects.get_or_create(user=request.user)
if profile.role != 'shipper':
messages.error(request, _("Only shippers can request shipments."))
return redirect('dashboard')
if request.method == 'POST':
form = ParcelForm(request.POST)
if form.is_valid():
parcel = form.save(commit=False)
parcel.shipper = request.user
parcel.save()
# WhatsApp Notification
notify_shipment_created(parcel)
messages.success(request, _("Shipment requested successfully! Tracking ID: ") + parcel.tracking_number)
return redirect('dashboard')
else:
form = ParcelForm()
return render(request, 'core/shipment_request.html', {'form': form})
@login_required
def accept_parcel(request, parcel_id):
profile, created = Profile.objects.get_or_create(user=request.user)
if profile.role != 'car_owner':
messages.error(request, _("Only car owners can accept shipments."))
return redirect('dashboard')
platform_profile = PlatformProfile.objects.first()
payments_enabled = platform_profile.enable_payment if platform_profile else True
if payments_enabled:
parcel = get_object_or_404(Parcel, id=parcel_id, status='pending', payment_status='paid')
else:
parcel = get_object_or_404(Parcel, id=parcel_id, status='pending')
parcel.carrier = request.user
parcel.status = 'picked_up'
parcel.save()
# WhatsApp Notification
notify_driver_assigned(parcel)
messages.success(request, _("You have accepted the shipment!"))
return redirect('dashboard')
@login_required
def update_status(request, parcel_id):
parcel = get_object_or_404(Parcel, id=parcel_id, carrier=request.user)
if request.method == 'POST':
new_status = request.POST.get('status')
if new_status in dict(Parcel.STATUS_CHOICES):
parcel.status = new_status
parcel.save()
# WhatsApp Notification
notify_status_change(parcel)
messages.success(request, _("Status updated successfully!"))
return redirect('dashboard')
@login_required
def initiate_payment(request, parcel_id):
# Check if payments are enabled
platform_profile = PlatformProfile.objects.first()
if platform_profile and not platform_profile.enable_payment:
messages.error(request, _("Payments are currently disabled by the administrator."))
return redirect('dashboard')
parcel = get_object_or_404(Parcel, id=parcel_id, shipper=request.user, payment_status='pending')
thawani = ThawaniPay()
success_url = request.build_absolute_uri(reverse('payment_success')) + f"?session_id={{CHECKOUT_SESSION_ID}}&parcel_id={parcel.id}"
cancel_url = request.build_absolute_uri(reverse('payment_cancel')) + f"?parcel_id={parcel.id}"
session_id = thawani.create_checkout_session(parcel, success_url, cancel_url)
if session_id:
parcel.thawani_session_id = session_id
parcel.save()
checkout_url = f"{settings.THAWANI_API_URL.replace('/api/v1', '')}/pay/{session_id}?key={settings.THAWANI_PUBLISHABLE_KEY}"
return redirect(checkout_url)
else:
messages.error(request, _("Could not initiate payment. Please try again later."))
return redirect('dashboard')
@login_required
def payment_success(request):
session_id = request.GET.get('session_id')
parcel_id = request.GET.get('parcel_id')
parcel = get_object_or_404(Parcel, id=parcel_id, shipper=request.user)
thawani = ThawaniPay()
session_data = thawani.get_checkout_session(session_id)
if session_data and session_data.get('payment_status') == 'paid':
parcel.payment_status = 'paid'
parcel.save()
# WhatsApp Notification
notify_payment_received(parcel)
messages.success(request, _("Payment successful! Your shipment is now active."))
else:
messages.warning(request, _("Payment status is pending or failed. Please check your dashboard."))
return redirect('dashboard')
@login_required
def payment_cancel(request):
messages.info(request, _("Payment was cancelled."))
return redirect('dashboard')
def article_detail(request):
return render(request, 'core/article_detail.html')
def get_governates(request):
country_id = request.GET.get('country_id')
lang = get_language()
field_name = 'name_ar' if lang == 'ar' else 'name_en'
governates = Governate.objects.filter(country_id=country_id).order_by(field_name)
data = [{'id': g.id, 'name': getattr(g, field_name)} for g in governates]
return JsonResponse(data, safe=False)
def get_cities(request):
governate_id = request.GET.get('governate_id')
lang = get_language()
field_name = 'name_ar' if lang == 'ar' else 'name_en'
cities = City.objects.filter(governate_id=governate_id).order_by(field_name)
data = [{'id': c.id, 'name': getattr(c, field_name)} for c in cities]
return JsonResponse(data, safe=False)
def privacy_policy(request):
return render(request, 'core/privacy_policy.html')
def terms_conditions(request):
return render(request, 'core/terms_conditions.html')
def contact(request):
if request.method == 'POST':
form = ContactForm(request.POST)
if form.is_valid():
# Send email
sent = send_contact_message(
name=form.cleaned_data['name'],
email=form.cleaned_data['email'],
message=form.cleaned_data['message']
)
if sent:
messages.success(request, _("Your message has been sent successfully!"))
else:
messages.error(request, _("There was an error sending your message. Please try again later."))
return redirect('contact')
else:
form = ContactForm()
return render(request, 'core/contact.html', {'form': form})
@login_required
def profile_view(request):
profile = request.user.profile
reviews = []
if profile.role == 'car_owner':
reviews = request.user.received_ratings.all().order_by('-created_at')
return render(request, 'core/profile.html', {
'profile': profile,
'reviews': reviews
})
@login_required
def edit_profile(request):
if request.method == 'POST':
form = UserProfileForm(request.POST, request.FILES, instance=request.user.profile)
if form.is_valid():
# 1. Handle Image immediately (easier than session storage)
if 'profile_picture' in request.FILES:
request.user.profile.profile_picture = request.FILES['profile_picture']
request.user.profile.save()
# 2. Store other data in session for verification
data = form.cleaned_data
# Remove objects that can't be serialized or we've already handled
safe_data = {
'first_name': data['first_name'],
'last_name': data['last_name'],
'email': data['email'],
'phone_number': data['phone_number'],
'address': data['address'],
'country_id': data['country'].id if data['country'] else None,
'governate_id': data['governate'].id if data['governate'] else None,
'city_id': data['city'].id if data['city'] else None,
}
request.session['pending_profile_update'] = safe_data
# 3. Generate OTP
code = ''.join(random.choices(string.digits, k=6))
OTPVerification.objects.create(user=request.user, code=code, purpose='profile_update')
# 4. Send OTP
method = data.get('otp_method', 'email')
otp_msg = _("Your Masar Update Code is %(code)s") % {'code': code}
if method == 'whatsapp':
# Use current phone if available, else new phone
phone = request.user.profile.phone_number or data['phone_number']
send_whatsapp_message(phone, otp_msg)
messages.info(request, _("Verification code sent to WhatsApp."))
else:
# Default to email
# Send to the NEW email address (from the form), not the old one
target_email = data['email']
send_html_email(
subject=_('Verification Code'),
message=otp_msg,
recipient_list=[target_email],
title=_('Profile Update Verification'),
request=request
)
messages.info(request, _("Verification code sent to email."))
return redirect('verify_otp')
else:
form = UserProfileForm(instance=request.user.profile)
return render(request, 'core/edit_profile.html', {'form': form})
@login_required
def verify_otp_view(request):
if request.method == 'POST':
code = request.POST.get('code')
try:
otp = OTPVerification.objects.filter(
user=request.user,
code=code,
purpose='profile_update',
is_verified=False
).latest('created_at')
if otp.is_valid():
# Apply changes
data = request.session.get('pending_profile_update')
if data:
# Update User
request.user.first_name = data['first_name']
request.user.last_name = data['last_name']
request.user.email = data['email']
request.user.save()
# Update Profile
profile = request.user.profile
profile.phone_number = data['phone_number']
profile.address = data['address']
if data.get('country_id'):
profile.country_id = data['country_id']
if data.get('governate_id'):
profile.governate_id = data['governate_id']
if data.get('city_id'):
profile.city_id = data['city_id']
profile.save()
# Cleanup
otp.is_verified = True
otp.save()
del request.session['pending_profile_update']
messages.success(request, _("Profile updated successfully!"))
return redirect('profile')
else:
messages.error(request, _("Session expired. Please try again."))
return redirect('edit_profile')
else:
messages.error(request, _("Invalid or expired code."))
except OTPVerification.DoesNotExist:
messages.error(request, _("Invalid code."))
return render(request, 'core/verify_otp.html')
@login_required
def rate_driver(request, parcel_id):
parcel = get_object_or_404(Parcel, id=parcel_id)
# Validation
if parcel.shipper != request.user:
messages.error(request, _("You are not authorized to rate this shipment."))
return redirect('dashboard')
if parcel.status != 'delivered':
messages.error(request, _("You can only rate delivered shipments."))
return redirect('dashboard')
if not parcel.carrier:
messages.error(request, _("No driver was assigned to this shipment."))
return redirect('dashboard')
if hasattr(parcel, 'rating'):
messages.info(request, _("You have already rated this shipment."))
return redirect('dashboard')
if request.method == 'POST':
form = DriverRatingForm(request.POST)
if form.is_valid():
rating = form.save(commit=False)
rating.parcel = parcel
rating.driver = parcel.carrier
rating.shipper = request.user
rating.save()
messages.success(request, _("Thank you for your feedback!"))
return redirect('dashboard')
else:
form = DriverRatingForm()
return render(request, 'core/rate_driver.html', {
'form': form,
'parcel': parcel
})
@require_POST
def request_login_otp(request):
identifier = request.POST.get('identifier')
if not identifier:
return JsonResponse({'success': False, 'message': _('Please enter an email or phone number.')})
# Clean identifier
identifier = identifier.strip()
user = None
method = 'email'
# Try to find user by email
user = User.objects.filter(email__iexact=identifier).first()
# If not found, try by phone number
if not user:
profile = Profile.objects.filter(phone_number=identifier).first()
if profile:
user = profile.user
method = 'whatsapp'
else:
# Fallback: maybe they entered a phone without country code or with?
# For now, simplistic search
pass
if not user:
# Don't reveal if user exists or not for security, but for UX on this project we can be a bit more helpful
return JsonResponse({'success': False, 'message': _('User not found with this email or phone number.')})
if not user.is_active:
return JsonResponse({'success': False, 'message': _('Account is inactive. Please verify registration first.')})
# Generate OTP
code = ''.join(random.choices(string.digits, k=6))
OTPVerification.objects.create(user=user, code=code, purpose='login')
# Send OTP
otp_msg = _("Your Masar Login Code is %(code)s. Do not share this code.") % {'code': code}
try:
if method == 'whatsapp':
phone = user.profile.phone_number
send_whatsapp_message(phone, otp_msg)
message_sent = _("OTP sent to your WhatsApp.")
else:
send_html_email(
subject=_('Login OTP'),
message=otp_msg,
recipient_list=[user.email],
title=_('Login Verification'),
request=request
)
message_sent = _("OTP sent to your email.")
return JsonResponse({'success': True, 'message': message_sent, 'user_id': user.id})
except Exception as e:
return JsonResponse({'success': False, 'message': _('Failed to send OTP. Please try again.')})
@require_POST
def verify_login_otp(request):
user_id = request.POST.get('user_id')
code = request.POST.get('code')
if not user_id or not code:
return JsonResponse({'success': False, 'message': _('Invalid request.')})
try:
user = User.objects.get(id=user_id)
otp = OTPVerification.objects.filter(
user=user,
code=code,
purpose='login',
is_verified=False
).latest('created_at')
if otp.is_valid():
# Cleanup
otp.is_verified = True
otp.save()
# Login
login(request, user)
return JsonResponse({'success': True, 'redirect_url': reverse('dashboard')})
else:
return JsonResponse({'success': False, 'message': _('Invalid or expired OTP.')})
except (User.DoesNotExist, OTPVerification.DoesNotExist):
return JsonResponse({'success': False, 'message': _('Invalid OTP.')})
@require_POST
def chatbot(request):
try:
data = json.loads(request.body)
user_message = data.get("message", "")
language = data.get("language", "en")
if not user_message:
return JsonResponse({"success": False, "error": "Empty message"})
system_prompt = (
"You are MasarX AI, a helpful and professional assistant for the Masar logistics platform. "
"The platform connects shippers with drivers for small parcel deliveries. "
"Answer the user's questions about shipping, tracking, becoming a driver, or general support. "
"If the user speaks Arabic, reply in Arabic. If English, reply in English. "
"Keep responses concise and helpful."
)
if language == "ar":
system_prompt += " The user is currently browsing in Arabic."
else:
system_prompt += " The user is currently browsing in English."
response = LocalAIApi.create_response({
"input": [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_message},
]
})
if response.get("success"):
text = LocalAIApi.extract_text(response)
return JsonResponse({"success": True, "response": text})
else:
return JsonResponse({"success": False, "error": response.get("error", "AI Error")})
except json.JSONDecodeError:
return JsonResponse({"success": False, "error": "Invalid JSON"})
except Exception as e:
return JsonResponse({"success": False, "error": str(e)})
@login_required
def generate_parcel_label(request, parcel_id):
parcel = get_object_or_404(Parcel, id=parcel_id)
# Security check: only shipper or carrier can print label
if parcel.shipper != request.user and parcel.carrier != request.user:
messages.error(request, _("You are not authorized to print this label."))
return redirect('dashboard')
# Generate QR Code
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=10,
border=4,
)
qr.add_data(parcel.tracking_number)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
buffer = BytesIO()
img.save(buffer, format="PNG")
qr_image_base64 = base64.b64encode(buffer.getvalue()).decode()
# Get Logo Base64
logo_base64 = None
platform_profile = PlatformProfile.objects.first()
if platform_profile and platform_profile.logo:
try:
with open(platform_profile.logo.path, "rb") as image_file:
logo_base64 = base64.b64encode(image_file.read()).decode()
except Exception:
pass
# Render Template
html_string = render_to_string('core/parcel_label.html', {
'parcel': parcel,
'qr_code': qr_image_base64,
'logo_base64': logo_base64,
'platform_profile': platform_profile,
})
# Generate PDF
html = weasyprint.HTML(string=html_string, base_url=request.build_absolute_uri())
pdf_file = html.write_pdf()
response = HttpResponse(pdf_file, content_type='application/pdf')
response['Content-Disposition'] = f'inline; filename="label_{parcel.tracking_number}.pdf"'
return response
@login_required
def generate_invoice(request, parcel_id):
parcel = get_object_or_404(Parcel, id=parcel_id)
# Security check: only shipper can view invoice (or admin)
if parcel.shipper != request.user and not request.user.is_staff:
messages.error(request, _("You are not authorized to view this invoice."))
return redirect('dashboard')
# Get Logo Base64
logo_base64 = None
platform_profile = PlatformProfile.objects.first()
if platform_profile and platform_profile.logo:
try:
with open(platform_profile.logo.path, "rb") as image_file:
logo_base64 = base64.b64encode(image_file.read()).decode()
except Exception:
pass
# Render Template
html_string = render_to_string('core/invoice.html', {
'parcel': parcel,
'logo_base64': logo_base64,
'platform_profile': platform_profile,
'request': request,
})
# Generate PDF
html = weasyprint.HTML(string=html_string, base_url=request.build_absolute_uri())
pdf_file = html.write_pdf()
response = HttpResponse(pdf_file, content_type='application/pdf')
response['Content-Disposition'] = f'inline; filename="invoice_{parcel.tracking_number}.pdf"'
return response
@login_required
def scan_qr_view(request):
"""Renders the QR Scanner page for drivers."""
# Optional: Restrict to drivers only
# if request.user.profile.role != 'car_owner':
# messages.error(request, _("Only drivers can access the scanner."))
# return redirect('dashboard')
return render(request, 'core/scan_qr.html')
@login_required
def get_parcel_details(request):
"""API to fetch parcel details by tracking number."""
tracking_number = request.GET.get('tracking_number')
if not tracking_number:
return JsonResponse({'success': False, 'error': _('Tracking number required')})
try:
parcel = Parcel.objects.get(tracking_number__iexact=tracking_number.strip())
# Check permissions: Is user the assigned driver? Or is it pending (for acceptance)?
is_driver = request.user.profile.role == 'car_owner'
is_assigned = parcel.carrier == request.user
can_update = False
if is_driver:
if is_assigned:
can_update = True
elif parcel.status == 'pending':
# Check if payments are enabled and paid
platform_profile = PlatformProfile.objects.first()
payments_enabled = platform_profile.enable_payment if platform_profile else True
if not payments_enabled or parcel.payment_status == 'paid':
can_update = True
data = {
'success': True,
'parcel': {
'id': parcel.id,
'tracking_number': parcel.tracking_number,
'status': parcel.status,
'status_display': parcel.get_status_display(),
'price': float(parcel.price),
'from': f"{parcel.pickup_governate.name} / {parcel.pickup_city.name}",
'to': f"{parcel.delivery_governate.name} / {parcel.delivery_city.name}",
'description': parcel.description,
'can_update': can_update
}
}
return JsonResponse(data)
except Parcel.DoesNotExist:
return JsonResponse({'success': False, 'error': _('Parcel not found')})
@login_required
@require_POST
def update_parcel_status_ajax(request):
"""API to update parcel status from scanner."""
try:
data = json.loads(request.body)
parcel_id = data.get('parcel_id')
action = data.get('action')
parcel = get_object_or_404(Parcel, id=parcel_id)
# Logic for actions
if action == 'accept':
# Similar to accept_parcel view
if request.user.profile.role != 'car_owner':
return JsonResponse({'success': False, 'error': _('Only drivers can accept shipments')})
if parcel.status != 'pending':
return JsonResponse({'success': False, 'error': _('Parcel is not available')})
# Check payment status if enabled
platform_profile = PlatformProfile.objects.first()
payments_enabled = platform_profile.enable_payment if platform_profile else True
if payments_enabled and parcel.payment_status != 'paid':
return JsonResponse({'success': False, 'error': _('Payment pending')})
parcel.carrier = request.user
parcel.status = 'picked_up' # Or 'assigned'? Logic says 'picked_up' in accept_parcel
parcel.save()
notify_driver_assigned(parcel)
elif action == 'delivered':
if parcel.carrier != request.user:
return JsonResponse({'success': False, 'error': _('Not authorized')})
parcel.status = 'delivered'
parcel.save()
notify_status_change(parcel)
else:
return JsonResponse({'success': False, 'error': _('Invalid action')})
return JsonResponse({'success': True})
except Exception as e:
return JsonResponse({'success': False, 'error': str(e)})
@login_required
def edit_parcel(request, parcel_id):
parcel = get_object_or_404(Parcel, id=parcel_id, shipper=request.user)
if parcel.status != 'pending':
messages.error(request, _("You can only edit pending shipments."))
return redirect('dashboard')
if request.method == 'POST':
form = ParcelForm(request.POST, instance=parcel)
if form.is_valid():
form.save()
messages.success(request, _("Shipment updated successfully."))
return redirect('dashboard')
else:
form = ParcelForm(instance=parcel)
return render(request, 'core/edit_parcel.html', {'form': form, 'parcel': parcel})
@login_required
def cancel_parcel(request, parcel_id):
parcel = get_object_or_404(Parcel, id=parcel_id, shipper=request.user)
if parcel.status != 'pending':
messages.error(request, _("You can only cancel pending shipments."))
else:
parcel.status = 'cancelled'
parcel.save()
messages.success(request, _("Shipment cancelled successfully."))
return redirect('dashboard')