adding notifications
This commit is contained in:
parent
95e2847b94
commit
f213aed6e7
Binary file not shown.
@ -185,6 +185,23 @@ CONTACT_EMAIL_TO = [
|
||||
# When both TLS and SSL flags are enabled, prefer SSL explicitly
|
||||
if EMAIL_USE_SSL:
|
||||
EMAIL_USE_TLS = False
|
||||
|
||||
# Thawani Payment Settings
|
||||
THAWANI_API_KEY = os.getenv("THAWANI_API_KEY", "rRQ26GcsZ60u9YCD9As60reHscS3Jt") # Placeholder Test Key
|
||||
THAWANI_PUBLISHABLE_KEY = os.getenv("THAWANI_PUBLISHABLE_KEY", "HGvTMLsnssOfssSshvSOfssOfsSshv") # Placeholder
|
||||
THAWANI_MODE = os.getenv("THAWANI_MODE", "test") # 'test' or 'live'
|
||||
|
||||
if THAWANI_MODE == 'live':
|
||||
THAWANI_API_URL = "https://checkout.thawani.om/api/v1"
|
||||
else:
|
||||
THAWANI_API_URL = "https://uatcheckout.thawani.om/api/v1"
|
||||
|
||||
# WhatsApp Notification Settings
|
||||
WHATSAPP_API_KEY = os.getenv("WHATSAPP_API_KEY", "")
|
||||
WHATSAPP_PHONE_ID = os.getenv("WHATSAPP_PHONE_ID", "")
|
||||
WHATSAPP_BUSINESS_ACCOUNT_ID = os.getenv("WHATSAPP_BUSINESS_ACCOUNT_ID", "")
|
||||
WHATSAPP_ENABLED = os.getenv("WHATSAPP_ENABLED", "false").lower() == "true"
|
||||
|
||||
# Default primary key field type
|
||||
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
BIN
core/__pycache__/payment_utils.cpython-311.pyc
Normal file
BIN
core/__pycache__/payment_utils.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
core/__pycache__/whatsapp_utils.cpython-311.pyc
Normal file
BIN
core/__pycache__/whatsapp_utils.cpython-311.pyc
Normal file
Binary file not shown.
@ -69,7 +69,7 @@ class ParcelForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Parcel
|
||||
fields = [
|
||||
'description', 'weight',
|
||||
'description', 'weight', 'price',
|
||||
'pickup_country', 'pickup_governate', 'pickup_city', 'pickup_address',
|
||||
'delivery_country', 'delivery_governate', 'delivery_city', 'delivery_address',
|
||||
'receiver_name', 'receiver_phone'
|
||||
@ -77,6 +77,7 @@ class ParcelForm(forms.ModelForm):
|
||||
widgets = {
|
||||
'description': forms.Textarea(attrs={'rows': 3, 'class': 'form-control', 'placeholder': _('What are you sending?')}),
|
||||
'weight': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.1'}),
|
||||
'price': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.001'}),
|
||||
|
||||
'pickup_country': forms.Select(attrs={'class': 'form-control'}),
|
||||
'pickup_governate': forms.Select(attrs={'class': 'form-control'}),
|
||||
@ -94,6 +95,7 @@ class ParcelForm(forms.ModelForm):
|
||||
labels = {
|
||||
'description': _('Package Description'),
|
||||
'weight': _('Weight (kg)'),
|
||||
'price': _('Shipping Price (OMR)'),
|
||||
'pickup_country': _('Pickup Country'),
|
||||
'pickup_governate': _('Pickup Governate'),
|
||||
'pickup_city': _('Pickup City'),
|
||||
@ -142,4 +144,4 @@ class ParcelForm(forms.ModelForm):
|
||||
gov_id = int(self.data.get('delivery_governate'))
|
||||
self.fields['delivery_city'].queryset = City.objects.filter(governate_id=gov_id).order_by('name')
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
pass
|
||||
@ -0,0 +1,34 @@
|
||||
# Generated by Django 5.2.7 on 2026-01-25 07:48
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0003_city_country_parcel_delivery_city_parcel_pickup_city_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='parcel',
|
||||
name='payment_status',
|
||||
field=models.CharField(choices=[('pending', 'Pending'), ('paid', 'Paid'), ('failed', 'Failed')], default='pending', max_length=20, verbose_name='Payment Status'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='parcel',
|
||||
name='price',
|
||||
field=models.DecimalField(decimal_places=3, default=0.0, max_digits=10, verbose_name='Price (OMR)'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='parcel',
|
||||
name='thawani_session_id',
|
||||
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Thawani Session ID'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='parcel',
|
||||
name='delivery_city',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='delivery_city_parcels', to='core.city', verbose_name='Delivery City'),
|
||||
),
|
||||
]
|
||||
Binary file not shown.
@ -76,12 +76,19 @@ class Parcel(models.Model):
|
||||
('cancelled', _('Cancelled')),
|
||||
)
|
||||
|
||||
PAYMENT_STATUS_CHOICES = (
|
||||
('pending', _('Pending')),
|
||||
('paid', _('Paid')),
|
||||
('failed', _('Failed')),
|
||||
)
|
||||
|
||||
tracking_number = models.CharField(_('Tracking Number'), max_length=20, unique=True, blank=True)
|
||||
shipper = models.ForeignKey(User, on_delete=models.CASCADE, related_name='sent_parcels', verbose_name=_('Shipper'))
|
||||
carrier = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='carried_parcels', verbose_name=_('Carrier'))
|
||||
|
||||
description = models.TextField(_('Description'))
|
||||
weight = models.DecimalField(_('Weight (kg)'), max_digits=5, decimal_places=2, help_text=_("Weight in kg"))
|
||||
price = models.DecimalField(_('Price (OMR)'), max_digits=10, decimal_places=3, default=0.000)
|
||||
|
||||
# Pickup Location
|
||||
pickup_country = models.ForeignKey(Country, on_delete=models.SET_NULL, null=True, blank=True, related_name='pickup_parcels', verbose_name=_('Pickup Country'))
|
||||
@ -92,13 +99,16 @@ class Parcel(models.Model):
|
||||
# Delivery Location
|
||||
delivery_country = models.ForeignKey(Country, on_delete=models.SET_NULL, null=True, blank=True, related_name='delivery_parcels', verbose_name=_('Delivery Country'))
|
||||
delivery_governate = models.ForeignKey(Governate, on_delete=models.SET_NULL, null=True, blank=True, related_name='delivery_parcels', verbose_name=_('Delivery Governate'))
|
||||
delivery_city = models.ForeignKey(City, on_delete=models.SET_NULL, null=True, blank=True, related_name='delivery_parcels', verbose_name=_('Delivery City'))
|
||||
delivery_city = models.ForeignKey(City, on_delete=models.SET_NULL, null=True, blank=True, related_name='delivery_city_parcels', verbose_name=_('Delivery City'))
|
||||
delivery_address = models.CharField(_('Delivery Address'), max_length=255)
|
||||
|
||||
receiver_name = models.CharField(_('Receiver Name'), max_length=100)
|
||||
receiver_phone = models.CharField(_('Receiver Phone'), max_length=20)
|
||||
|
||||
status = models.CharField(_('Status'), max_length=20, choices=STATUS_CHOICES, default='pending')
|
||||
payment_status = models.CharField(_('Payment Status'), max_length=20, choices=PAYMENT_STATUS_CHOICES, default='pending')
|
||||
thawani_session_id = models.CharField(_('Thawani Session ID'), max_length=255, blank=True, null=True)
|
||||
|
||||
created_at = models.DateTimeField(_('Created At'), auto_now_add=True)
|
||||
updated_at = models.DateTimeField(_('Updated At'), auto_now=True)
|
||||
|
||||
@ -112,4 +122,4 @@ class Parcel(models.Model):
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Parcel')
|
||||
verbose_name_plural = _('Parcels')
|
||||
verbose_name_plural = _('Parcels')
|
||||
|
||||
66
core/payment_utils.py
Normal file
66
core/payment_utils.py
Normal file
@ -0,0 +1,66 @@
|
||||
import requests
|
||||
from django.conf import settings
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class ThawaniPay:
|
||||
def __init__(self):
|
||||
self.api_key = settings.THAWANI_API_KEY
|
||||
self.base_url = settings.THAWANI_API_URL
|
||||
self.headers = {
|
||||
"thawani-api-key": self.api_key,
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
def create_checkout_session(self, parcel, success_url, cancel_url):
|
||||
endpoint = f"{self.base_url}/checkout/session"
|
||||
|
||||
# Thawani expects price in baiza (1 OMR = 1000 baiza)
|
||||
# We need to convert Decimal price to integer baiza
|
||||
amount_baiza = int(parcel.price * 1000)
|
||||
|
||||
payload = {
|
||||
"client_reference_id": str(parcel.tracking_number),
|
||||
"mode": "payment",
|
||||
"products": [
|
||||
{
|
||||
"name": f"Shipping for Parcel {parcel.tracking_number}",
|
||||
"unit_amount": amount_baiza,
|
||||
"quantity": 1
|
||||
}
|
||||
],
|
||||
"success_url": success_url,
|
||||
"cancel_url": cancel_url,
|
||||
"metadata": {
|
||||
"parcel_id": parcel.id,
|
||||
"customer_name": parcel.shipper.get_full_name() or parcel.shipper.username,
|
||||
"customer_phone": parcel.shipper.profile.phone_number
|
||||
}
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(endpoint, json=payload, headers=self.headers)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
if data.get("success"):
|
||||
return data["data"]["session_id"]
|
||||
else:
|
||||
logger.error(f"Thawani Error: {data.get('description')}")
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error(f"Thawani Request Failed: {str(e)}")
|
||||
return None
|
||||
|
||||
def get_checkout_session(self, session_id):
|
||||
endpoint = f"{self.base_url}/checkout/session/{session_id}"
|
||||
try:
|
||||
response = requests.get(endpoint, headers=self.headers)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
if data.get("success"):
|
||||
return data["data"]
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error(f"Thawani Check Failed: {str(e)}")
|
||||
return None
|
||||
@ -26,7 +26,10 @@
|
||||
<h5 class="card-title">{{ parcel.description|truncatechars:30 }}</h5>
|
||||
<p class="card-text mb-1 small"><strong>{% trans "Pickup" %}:</strong> {{ parcel.pickup_address }}</p>
|
||||
<p class="card-text mb-3 small"><strong>{% trans "Delivery" %}:</strong> {{ parcel.delivery_address }}</p>
|
||||
<p class="card-text mb-3 small text-muted"><strong>{% trans "Weight" %}:</strong> {{ parcel.weight }} kg</p>
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<span class="text-muted small"><strong>{% trans "Weight" %}:</strong> {{ parcel.weight }} kg</span>
|
||||
<span class="text-primary fw-bold">{{ parcel.price }} OMR</span>
|
||||
</div>
|
||||
<form action="{% url 'accept_parcel' parcel.id %}" method="POST">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn btn-masarx-primary w-100">{% trans "Accept Shipment" %}</button>
|
||||
@ -79,4 +82,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
@ -23,6 +23,20 @@
|
||||
<h5 class="card-title">{{ parcel.description|truncatechars:30 }}</h5>
|
||||
<p class="card-text mb-1 small text-muted"><i class="fas fa-map-marker-alt"></i> <strong>{% trans "From" %}:</strong> {{ parcel.pickup_address }}</p>
|
||||
<p class="card-text mb-3 small text-muted"><i class="fas fa-flag-checkered"></i> <strong>{% trans "To" %}:</strong> {{ parcel.delivery_address }}</p>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<span class="text-primary fw-bold">{{ parcel.price }} OMR</span>
|
||||
<span class="badge {% if parcel.payment_status == 'paid' %}bg-success{% else %}bg-secondary{% endif %}">
|
||||
{{ parcel.get_payment_status_display }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{% if parcel.payment_status == 'pending' %}
|
||||
<a href="{% url 'initiate_payment' parcel.id %}" class="btn btn-sm btn-outline-primary w-100 mb-3">
|
||||
<i class="fas fa-credit-card me-1"></i> {% trans "Pay Now" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
<hr>
|
||||
<p class="card-text small mb-0"><strong>{% trans "Receiver" %}:</strong> {{ parcel.receiver_name }}</p>
|
||||
<p class="card-text small"><strong>{% trans "Carrier" %}:</strong> {% if parcel.carrier %}{{ parcel.carrier.get_full_name|default:parcel.carrier.username }}{% else %}{% trans "Waiting for pickup" %}{% endif %}</p>
|
||||
@ -38,4 +52,4 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
@ -1,6 +1,6 @@
|
||||
from django.urls import path
|
||||
from django.contrib.auth import views as auth_views
|
||||
from . import views
|
||||
from django.contrib.auth import views as auth_views
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.index, name='index'),
|
||||
@ -16,4 +16,9 @@ urlpatterns = [
|
||||
# AJAX for locations
|
||||
path('ajax/get-governates/', views.get_governates, name='get_governates'),
|
||||
path('ajax/get-cities/', views.get_cities, name='get_cities'),
|
||||
]
|
||||
|
||||
# Thawani Payment
|
||||
path('payment/initiate/<int:parcel_id>/', views.initiate_payment, name='initiate_payment'),
|
||||
path('payment/success/', views.payment_success, name='payment_success'),
|
||||
path('payment/cancel/', views.payment_cancel, name='payment_cancel'),
|
||||
]
|
||||
@ -7,6 +7,15 @@ from .forms import UserRegistrationForm, ParcelForm
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.contrib import messages
|
||||
from django.http import JsonResponse
|
||||
from django.urls import reverse
|
||||
from .payment_utils import ThawaniPay
|
||||
from django.conf import settings
|
||||
from .whatsapp_utils import (
|
||||
notify_shipment_created,
|
||||
notify_payment_received,
|
||||
notify_driver_assigned,
|
||||
notify_status_change
|
||||
)
|
||||
|
||||
def index(request):
|
||||
tracking_id = request.GET.get('tracking_id')
|
||||
@ -45,7 +54,7 @@ def dashboard(request):
|
||||
return render(request, 'core/shipper_dashboard.html', {'parcels': parcels})
|
||||
else:
|
||||
# Car Owner view
|
||||
available_parcels = Parcel.objects.filter(status='pending').order_by('-created_at')
|
||||
available_parcels = Parcel.objects.filter(status='pending', payment_status='paid').order_by('-created_at')
|
||||
my_parcels = Parcel.objects.filter(carrier=request.user).exclude(status='delivered').order_by('-created_at')
|
||||
return render(request, 'core/driver_dashboard.html', {
|
||||
'available_parcels': available_parcels,
|
||||
@ -65,6 +74,10 @@ def shipment_request(request):
|
||||
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:
|
||||
@ -78,10 +91,14 @@ def accept_parcel(request, parcel_id):
|
||||
messages.error(request, _("Only car owners can accept shipments."))
|
||||
return redirect('dashboard')
|
||||
|
||||
parcel = get_object_or_404(Parcel, id=parcel_id, status='pending')
|
||||
parcel = get_object_or_404(Parcel, id=parcel_id, status='pending', payment_status='paid')
|
||||
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')
|
||||
|
||||
@ -93,9 +110,59 @@ def update_status(request, parcel_id):
|
||||
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):
|
||||
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')
|
||||
|
||||
@ -107,4 +174,4 @@ def get_governates(request):
|
||||
def get_cities(request):
|
||||
governate_id = request.GET.get('governate_id')
|
||||
cities = City.objects.filter(governate_id=governate_id).values('id', 'name')
|
||||
return JsonResponse(list(cities), safe=False)
|
||||
return JsonResponse(list(cities), safe=False)
|
||||
92
core/whatsapp_utils.py
Normal file
92
core/whatsapp_utils.py
Normal file
@ -0,0 +1,92 @@
|
||||
import requests
|
||||
import logging
|
||||
from django.conf import settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def send_whatsapp_message(phone_number, message):
|
||||
"""
|
||||
Sends a WhatsApp message using the configured gateway.
|
||||
This implementation assumes Meta WhatsApp Business API (Graph API).
|
||||
"""
|
||||
if not settings.WHATSAPP_ENABLED:
|
||||
logger.info("WhatsApp notifications are disabled.")
|
||||
return False
|
||||
|
||||
if not settings.WHATSAPP_API_KEY or not settings.WHATSAPP_PHONE_ID:
|
||||
logger.warning("WhatsApp API configuration is missing.")
|
||||
return False
|
||||
|
||||
# Normalize phone number (ensure it has country code and no +)
|
||||
clean_phone = "".join(filter(str.isdigit, str(phone_number)))
|
||||
|
||||
url = f"https://graph.facebook.com/v17.0/{settings.WHATSAPP_PHONE_ID}/messages"
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {settings.WHATSAPP_API_KEY}",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
payload = {
|
||||
"messaging_product": "whatsapp",
|
||||
"to": clean_phone,
|
||||
"type": "text",
|
||||
"text": {"body": message}
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(url, headers=headers, json=payload, timeout=10)
|
||||
response_data = response.json()
|
||||
|
||||
if response.status_code == 200:
|
||||
logger.info(f"WhatsApp message sent to {clean_phone}")
|
||||
return True
|
||||
else:
|
||||
logger.error(f"WhatsApp API error: {response.status_code} - {response_data}")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to send WhatsApp message: {str(e)}")
|
||||
return False
|
||||
|
||||
def notify_shipment_created(parcel):
|
||||
"""Notifies the shipper that the shipment request was received."""
|
||||
shipper_name = parcel.shipper.get_full_name() or parcel.shipper.username
|
||||
message = f"""Hello {shipper_name},
|
||||
|
||||
Your shipment request for '{parcel.description}' has been received.
|
||||
Tracking Number: {parcel.tracking_number}
|
||||
Status: {parcel.get_status_display()}
|
||||
|
||||
Please proceed to payment to make it visible to drivers."""
|
||||
return send_whatsapp_message(parcel.shipper.profile.phone_number, message)
|
||||
|
||||
def notify_payment_received(parcel):
|
||||
"""Notifies the shipper and receiver about successful payment."""
|
||||
# Notify Shipper
|
||||
shipper_name = parcel.shipper.get_full_name() or parcel.shipper.username
|
||||
shipper_msg = f"""Payment successful for shipment {parcel.tracking_number}.
|
||||
Your shipment is now visible to available drivers."""
|
||||
send_whatsapp_message(parcel.shipper.profile.phone_number, shipper_msg)
|
||||
|
||||
# Notify Receiver
|
||||
receiver_msg = f"""Hello {parcel.receiver_name},
|
||||
|
||||
A shipment is coming your way from {shipper_name}.
|
||||
Tracking Number: {parcel.tracking_number}
|
||||
Status: {parcel.get_status_display()}"""
|
||||
send_whatsapp_message(parcel.receiver_phone, receiver_msg)
|
||||
|
||||
def notify_driver_assigned(parcel):
|
||||
"""Notifies the shipper and receiver that a driver has picked up the parcel."""
|
||||
driver_name = parcel.carrier.get_full_name() or parcel.carrier.username
|
||||
msg = f"""Shipment {parcel.tracking_number} has been picked up by {driver_name}.
|
||||
Status: {parcel.get_status_display()}"""
|
||||
send_whatsapp_message(parcel.shipper.profile.phone_number, msg)
|
||||
send_whatsapp_message(parcel.receiver_phone, msg)
|
||||
|
||||
def notify_status_change(parcel):
|
||||
"""Notifies parties about general status updates (In Transit, Delivered)."""
|
||||
msg = f"""Update for shipment {parcel.tracking_number}:
|
||||
New Status: {parcel.get_status_display()}"""
|
||||
send_whatsapp_message(parcel.shipper.profile.phone_number, msg)
|
||||
send_whatsapp_message(parcel.receiver_phone, msg)
|
||||
Loading…
x
Reference in New Issue
Block a user