Autosave: 20260128-021353
This commit is contained in:
parent
a982102796
commit
e6c45971eb
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
core/__pycache__/notifications.cpython-311.pyc
Normal file
BIN
core/__pycache__/notifications.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,7 +1,7 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.contrib.auth.admin import UserAdmin
|
from django.contrib.auth.admin import UserAdmin
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from .models import Profile, Parcel, Country, Governate, City, PlatformProfile, Testimonial, DriverRating
|
from .models import Profile, Parcel, Country, Governate, City, PlatformProfile, Testimonial, DriverRating, NotificationTemplate
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.urls import path, reverse
|
from django.urls import path, reverse
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
@ -234,4 +234,30 @@ admin.site.register(Governate)
|
|||||||
admin.site.register(City)
|
admin.site.register(City)
|
||||||
admin.site.register(PlatformProfile, PlatformProfileAdmin)
|
admin.site.register(PlatformProfile, PlatformProfileAdmin)
|
||||||
admin.site.register(Testimonial, TestimonialAdmin)
|
admin.site.register(Testimonial, TestimonialAdmin)
|
||||||
admin.site.register(DriverRating)
|
admin.site.register(DriverRating)
|
||||||
|
class NotificationTemplateAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('key', 'description')
|
||||||
|
readonly_fields = ('key', 'description', 'available_variables')
|
||||||
|
search_fields = ('key', 'description')
|
||||||
|
|
||||||
|
fieldsets = (
|
||||||
|
(None, {
|
||||||
|
'fields': ('key', 'description', 'available_variables')
|
||||||
|
}),
|
||||||
|
(_('Email Content'), {
|
||||||
|
'fields': ('subject_en', 'subject_ar', 'email_body_en', 'email_body_ar'),
|
||||||
|
'description': _('For emails, the body is wrapped in a base template. Use HTML if needed.')
|
||||||
|
}),
|
||||||
|
(_('WhatsApp Content'), {
|
||||||
|
'fields': ('whatsapp_body_en', 'whatsapp_body_ar'),
|
||||||
|
'description': _('For WhatsApp, use plain text with newlines.')
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
def has_add_permission(self, request):
|
||||||
|
return False # Prevent adding new keys manually
|
||||||
|
|
||||||
|
def has_delete_permission(self, request, obj=None):
|
||||||
|
return False
|
||||||
|
|
||||||
|
admin.site.register(NotificationTemplate, NotificationTemplateAdmin)
|
||||||
|
|||||||
41
core/mail.py
41
core/mail.py
@ -55,31 +55,36 @@ def send_html_email(subject, message, recipient_list, title=None, action_url=Non
|
|||||||
def send_contact_message(name, email, message):
|
def send_contact_message(name, email, message):
|
||||||
"""
|
"""
|
||||||
Sends a contact form message to the platform admins.
|
Sends a contact form message to the platform admins.
|
||||||
|
|
||||||
Args:
|
|
||||||
name (str): Sender's name
|
|
||||||
email (str): Sender's email
|
|
||||||
message (str): The message content
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
bool: True if sent successfully, False otherwise
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
subject = f"New Contact Message from {name}"
|
from .notifications import get_notification_content
|
||||||
full_message = f"You have received a new message from your website contact form.\n\n" \
|
|
||||||
f"Name: {name}\n" \
|
context = {'name': name, 'email': email, 'message': message}
|
||||||
f"Email: {email}\n\n" \
|
# Admin alerts default to EN
|
||||||
f"Message:\n{message}"
|
subj, email_msg, wa_msg = get_notification_content('contact_form_admin', context, language='en')
|
||||||
|
|
||||||
recipient_list = settings.CONTACT_EMAIL_TO or [settings.DEFAULT_FROM_EMAIL]
|
recipient_list = settings.CONTACT_EMAIL_TO or [settings.DEFAULT_FROM_EMAIL]
|
||||||
|
|
||||||
# Use HTML email for contact form too, for consistency
|
# Email
|
||||||
return send_html_email(
|
email_sent = send_html_email(
|
||||||
subject=subject,
|
subject=subj,
|
||||||
message=full_message,
|
message=email_msg,
|
||||||
recipient_list=recipient_list,
|
recipient_list=recipient_list,
|
||||||
title="New Contact Message"
|
title="New Contact Message"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# WhatsApp (New feature: Notify admin on WhatsApp too)
|
||||||
|
try:
|
||||||
|
from .models import PlatformProfile
|
||||||
|
from .whatsapp_utils import send_whatsapp_message
|
||||||
|
|
||||||
|
profile = PlatformProfile.objects.first()
|
||||||
|
if profile and profile.phone_number:
|
||||||
|
send_whatsapp_message(profile.phone_number, wa_msg)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Failed to send admin WhatsApp for contact form: {e}")
|
||||||
|
|
||||||
|
return email_sent
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to send contact message: {e}")
|
logger.error(f"Failed to send contact message: {e}")
|
||||||
return False
|
return False
|
||||||
|
|||||||
Binary file not shown.
30
core/management/commands/init_notifications.py
Normal file
30
core/management/commands/init_notifications.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from core.models import NotificationTemplate
|
||||||
|
from core.notifications import DEFAULT_TEMPLATES, get_notification_content
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'Initialize default notification templates'
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
count = 0
|
||||||
|
for key, default in DEFAULT_TEMPLATES.items():
|
||||||
|
obj, created = NotificationTemplate.objects.get_or_create(
|
||||||
|
key=key,
|
||||||
|
defaults={
|
||||||
|
'description': default.get('description', ''),
|
||||||
|
'available_variables': default.get('variables', ''),
|
||||||
|
'subject_en': default.get('subject_en', ''),
|
||||||
|
'subject_ar': default.get('subject_ar', ''),
|
||||||
|
'email_body_en': default.get('email_body_en', ''),
|
||||||
|
'email_body_ar': default.get('email_body_ar', ''),
|
||||||
|
'whatsapp_body_en': default.get('whatsapp_body_en', ''),
|
||||||
|
'whatsapp_body_ar': default.get('whatsapp_body_ar', ''),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if created:
|
||||||
|
self.stdout.write(self.style.SUCCESS(f'Created template: {key}'))
|
||||||
|
count += 1
|
||||||
|
else:
|
||||||
|
self.stdout.write(f'Template exists: {key}')
|
||||||
|
|
||||||
|
self.stdout.write(self.style.SUCCESS(f'Initialized {count} new templates.'))
|
||||||
32
core/migrations/0022_notificationtemplate.py
Normal file
32
core/migrations/0022_notificationtemplate.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2026-01-28 01:04
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('core', '0021_remove_platformprofile_privacy_policy_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='NotificationTemplate',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('key', models.CharField(choices=[('otp_registration', 'OTP Registration'), ('otp_login', 'OTP Login'), ('otp_profile_update', 'OTP Profile Update'), ('shipment_created_shipper', 'Shipment Created (Shipper)'), ('payment_success_shipper', 'Payment Success (Shipper)'), ('shipment_visible_receiver', 'Shipment Visible (Receiver)'), ('driver_pickup_shipper', 'Driver Pickup (Shipper)'), ('driver_pickup_receiver', 'Driver Pickup (Receiver)'), ('driver_pickup_driver', 'Driver Pickup (Driver/Carrier)'), ('shipment_status_update', 'Shipment Status Update'), ('admin_alert_driver_accept', 'Admin Alert: Driver Accepted'), ('contact_form_admin', 'Contact Form (Admin)')], max_length=50, unique=True)),
|
||||||
|
('description', models.CharField(help_text='Description of where this notification is used.', max_length=255)),
|
||||||
|
('available_variables', models.TextField(blank=True, help_text='Comma-separated list of variables available in this template (e.g. {{ code }}, {{ name }}).')),
|
||||||
|
('subject_en', models.CharField(blank=True, max_length=255, verbose_name='Email Subject (EN)')),
|
||||||
|
('subject_ar', models.CharField(blank=True, max_length=255, verbose_name='Email Subject (AR)')),
|
||||||
|
('email_body_en', models.TextField(blank=True, help_text='HTML allowed.', verbose_name='Email Body (EN)')),
|
||||||
|
('email_body_ar', models.TextField(blank=True, help_text='HTML allowed.', verbose_name='Email Body (AR)')),
|
||||||
|
('whatsapp_body_en', models.TextField(blank=True, verbose_name='WhatsApp Message (EN)')),
|
||||||
|
('whatsapp_body_ar', models.TextField(blank=True, verbose_name='WhatsApp Message (AR)')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Notification Template',
|
||||||
|
'verbose_name_plural': 'Notification Templates',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
Binary file not shown.
@ -294,4 +294,41 @@ class DriverRating(models.Model):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('Driver Rating')
|
verbose_name = _('Driver Rating')
|
||||||
verbose_name_plural = _('Driver Ratings')
|
verbose_name_plural = _('Driver Ratings')
|
||||||
|
|
||||||
|
class NotificationTemplate(models.Model):
|
||||||
|
KEY_CHOICES = (
|
||||||
|
('otp_registration', 'OTP Registration'),
|
||||||
|
('otp_login', 'OTP Login'),
|
||||||
|
('otp_profile_update', 'OTP Profile Update'),
|
||||||
|
('shipment_created_shipper', 'Shipment Created (Shipper)'),
|
||||||
|
('payment_success_shipper', 'Payment Success (Shipper)'),
|
||||||
|
('shipment_visible_receiver', 'Shipment Visible (Receiver)'),
|
||||||
|
('driver_pickup_shipper', 'Driver Pickup (Shipper)'),
|
||||||
|
('driver_pickup_receiver', 'Driver Pickup (Receiver)'),
|
||||||
|
('driver_pickup_driver', 'Driver Pickup (Driver/Carrier)'),
|
||||||
|
('shipment_status_update', 'Shipment Status Update'),
|
||||||
|
('admin_alert_driver_accept', 'Admin Alert: Driver Accepted'),
|
||||||
|
('contact_form_admin', 'Contact Form (Admin)'),
|
||||||
|
)
|
||||||
|
|
||||||
|
key = models.CharField(max_length=50, choices=KEY_CHOICES, unique=True)
|
||||||
|
description = models.CharField(max_length=255, help_text="Description of where this notification is used.")
|
||||||
|
available_variables = models.TextField(help_text="Comma-separated list of variables available in this template (e.g. {{ code }}, {{ name }}).", blank=True)
|
||||||
|
|
||||||
|
# Email
|
||||||
|
subject_en = models.CharField(max_length=255, blank=True, verbose_name="Email Subject (EN)")
|
||||||
|
subject_ar = models.CharField(max_length=255, blank=True, verbose_name="Email Subject (AR)")
|
||||||
|
email_body_en = models.TextField(blank=True, verbose_name="Email Body (EN)", help_text="HTML allowed.")
|
||||||
|
email_body_ar = models.TextField(blank=True, verbose_name="Email Body (AR)", help_text="HTML allowed.")
|
||||||
|
|
||||||
|
# WhatsApp
|
||||||
|
whatsapp_body_en = models.TextField(blank=True, verbose_name="WhatsApp Message (EN)")
|
||||||
|
whatsapp_body_ar = models.TextField(blank=True, verbose_name="WhatsApp Message (AR)")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.get_key_display()}"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('Notification Template')
|
||||||
|
verbose_name_plural = _('Notification Templates')
|
||||||
183
core/notifications.py
Normal file
183
core/notifications.py
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
from django.utils.translation import get_language
|
||||||
|
from .models import NotificationTemplate
|
||||||
|
from django.template import Template, Context
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
DEFAULT_TEMPLATES = {
|
||||||
|
'otp_registration': {
|
||||||
|
'description': 'Sent when a user registers (Email/WhatsApp)',
|
||||||
|
'variables': '{{ code }}',
|
||||||
|
'subject_en': 'Verification Code',
|
||||||
|
'subject_ar': 'رمز التحقق',
|
||||||
|
'email_body_en': 'Your Masar Verification Code is {{ code }}',
|
||||||
|
'email_body_ar': 'رمز التحقق الخاص بك هو {{ code }}',
|
||||||
|
'whatsapp_body_en': 'Your Masar Verification Code is {{ code }}',
|
||||||
|
'whatsapp_body_ar': 'رمز التحقق الخاص بك هو {{ code }}',
|
||||||
|
},
|
||||||
|
'otp_login': {
|
||||||
|
'description': 'Sent for 2FA Login (Email/WhatsApp)',
|
||||||
|
'variables': '{{ code }}',
|
||||||
|
'subject_en': 'Login OTP',
|
||||||
|
'subject_ar': 'رمز الدخول',
|
||||||
|
'email_body_en': 'Your Masar Login Code is {{ code }}. Do not share this code.',
|
||||||
|
'email_body_ar': 'رمز دخول مسار هو {{ code }}. لا تشارك هذا الرمز.',
|
||||||
|
'whatsapp_body_en': 'Your Masar Login Code is {{ code }}. Do not share this code.',
|
||||||
|
'whatsapp_body_ar': 'رمز دخول مسار هو {{ code }}. لا تشارك هذا الرمز.',
|
||||||
|
},
|
||||||
|
'otp_profile_update': {
|
||||||
|
'description': 'Sent when updating profile sensitive info',
|
||||||
|
'variables': '{{ code }}',
|
||||||
|
'subject_en': 'Verification Code',
|
||||||
|
'subject_ar': 'رمز التحقق',
|
||||||
|
'email_body_en': 'Your Masar Update Code is {{ code }}',
|
||||||
|
'email_body_ar': 'رمز التحديث الخاص بك هو {{ code }}',
|
||||||
|
'whatsapp_body_en': 'Your Masar Update Code is {{ code }}',
|
||||||
|
'whatsapp_body_ar': 'رمز التحديث الخاص بك هو {{ code }}',
|
||||||
|
},
|
||||||
|
'shipment_created_shipper': {
|
||||||
|
'description': 'Sent to Shipper when they create a shipment',
|
||||||
|
'variables': '{{ name }}, {{ description }}, {{ tracking_number }}, {{ status }}',
|
||||||
|
'subject_en': 'Shipment Request Received - {{ tracking_number }}',
|
||||||
|
'subject_ar': 'تم استلام طلب الشحنة - {{ tracking_number }}',
|
||||||
|
'email_body_en': "Hello {{ name }},\n\nYour shipment request for '{{ description }}' has been received.\nTracking Number: {{ tracking_number }}\nStatus: {{ status }}\n\nPlease proceed to payment to make it visible to drivers.",
|
||||||
|
'email_body_ar': "مرحباً {{ name }}،\n\nتم استلام طلب الشحنة '{{ description }}'.\nرقم التتبع: {{ tracking_number }}\nالحالة: {{ status }}\n\nيرجى متابعة الدفع لجعلها مرئية للسائقين.",
|
||||||
|
'whatsapp_body_en': "Hello {{ name }},\nYour shipment request for '{{ description }}' has been received.\nTracking Number: {{ tracking_number }}\nStatus: {{ status }}\nPlease proceed to payment.",
|
||||||
|
'whatsapp_body_ar': "مرحباً {{ name }}،\nتم استلام طلب الشحنة '{{ description }}'.\nرقم التتبع: {{ tracking_number }}\nالحالة: {{ status }}\nيرجى الدفع.",
|
||||||
|
},
|
||||||
|
'payment_success_shipper': {
|
||||||
|
'description': 'Sent to Shipper after payment',
|
||||||
|
'variables': '{{ tracking_number }}',
|
||||||
|
'subject_en': 'Payment Successful - {{ tracking_number }}',
|
||||||
|
'subject_ar': 'تم الدفع بنجاح - {{ tracking_number }}',
|
||||||
|
'email_body_en': 'Payment successful for shipment {{ tracking_number }}.\nYour shipment is now visible to available drivers.',
|
||||||
|
'email_body_ar': 'تم الدفع بنجاح للشحنة {{ tracking_number }}.\nشحنتك الآن مرئية للسائقين المتاحين.',
|
||||||
|
'whatsapp_body_en': 'Payment successful for shipment {{ tracking_number }}.\nYour shipment is now visible to available drivers.',
|
||||||
|
'whatsapp_body_ar': 'تم الدفع بنجاح للشحنة {{ tracking_number }}.\nشحنتك الآن مرئية للسائقين المتاحين.',
|
||||||
|
},
|
||||||
|
'shipment_visible_receiver': {
|
||||||
|
'description': 'Sent to Receiver when shipment is paid/ready',
|
||||||
|
'variables': '{{ receiver_name }}, {{ shipper_name }}, {{ tracking_number }}, {{ status }}',
|
||||||
|
'subject_en': 'Incoming Shipment - {{ tracking_number }}',
|
||||||
|
'subject_ar': 'شحنة واردة - {{ tracking_number }}',
|
||||||
|
'email_body_en': 'Hello {{ receiver_name }},\n\nA shipment is coming your way from {{ shipper_name }}.\nTracking Number: {{ tracking_number }}\nStatus: {{ status }}',
|
||||||
|
'email_body_ar': 'مرحباً {{ receiver_name }}،\n\nشحنة قادمة إليك من {{ shipper_name }}.\nرقم التتبع: {{ tracking_number }}\nالحالة: {{ status }}',
|
||||||
|
'whatsapp_body_en': 'Hello {{ receiver_name }},\nA shipment is coming your way from {{ shipper_name }}.\nTracking Number: {{ tracking_number }}\nStatus: {{ status }}',
|
||||||
|
'whatsapp_body_ar': 'مرحباً {{ receiver_name }}،\nشحنة قادمة إليك من {{ shipper_name }}.\nرقم التتبع: {{ tracking_number }}\nالحالة: {{ status }}',
|
||||||
|
},
|
||||||
|
'driver_pickup_shipper': {
|
||||||
|
'description': 'Sent to Shipper when driver picks up',
|
||||||
|
'variables': '{{ tracking_number }}, {{ driver_name }}, {{ car_plate_number }}, {{ status }}',
|
||||||
|
'subject_en': 'Driver Assigned - {{ tracking_number }}',
|
||||||
|
'subject_ar': 'تم تعيين سائق - {{ tracking_number }}',
|
||||||
|
'email_body_en': 'Shipment {{ tracking_number }} has been picked up by {{ driver_name }}.\nCar Plate: {{ car_plate_number }}\nStatus: {{ status }}',
|
||||||
|
'email_body_ar': 'الشحنة {{ tracking_number }} تم استلامها بواسطة {{ driver_name }}.\nرقم اللوحة: {{ car_plate_number }}\nالحالة: {{ status }}',
|
||||||
|
'whatsapp_body_en': 'Shipment {{ tracking_number }} has been picked up by {{ driver_name }}.\nCar Plate: {{ car_plate_number }}\nStatus: {{ status }}',
|
||||||
|
'whatsapp_body_ar': 'الشحنة {{ tracking_number }} تم استلامها بواسطة {{ driver_name }}.\nرقم اللوحة: {{ car_plate_number }}\nالحالة: {{ status }}',
|
||||||
|
},
|
||||||
|
'driver_pickup_receiver': {
|
||||||
|
'description': 'Sent to Receiver when driver picks up',
|
||||||
|
'variables': '{{ tracking_number }}, {{ shipper_name }}, {{ driver_name }}, {{ car_plate_number }}',
|
||||||
|
'subject_en': 'Shipment On The Way - {{ tracking_number }}',
|
||||||
|
'subject_ar': 'الشحنة في الطريق - {{ tracking_number }}',
|
||||||
|
'email_body_en': 'Shipment {{ tracking_number }} from {{ shipper_name }} is on the way (Picked up).\nDriver: {{ driver_name }}\nCar Plate: {{ car_plate_number }}',
|
||||||
|
'email_body_ar': 'الشحنة {{ tracking_number }} من {{ shipper_name }} في الطريق (تم الاستلام).\nالسائق: {{ driver_name }}\nرقم اللوحة: {{ car_plate_number }}',
|
||||||
|
'whatsapp_body_en': 'Shipment {{ tracking_number }} from {{ shipper_name }} is on the way (Picked up).\nDriver: {{ driver_name }}\nCar Plate: {{ car_plate_number }}',
|
||||||
|
'whatsapp_body_ar': 'الشحنة {{ tracking_number }} من {{ shipper_name }} في الطريق (تم الاستلام).\nالسائق: {{ driver_name }}\nرقم اللوحة: {{ car_plate_number }}',
|
||||||
|
},
|
||||||
|
'driver_pickup_driver': {
|
||||||
|
'description': 'Sent to Driver upon acceptance',
|
||||||
|
'variables': '{{ tracking_number }}, {{ shipper_name }}, {{ pickup_address }}, {{ delivery_address }}, {{ price }}',
|
||||||
|
'subject_en': 'Shipment Accepted - {{ tracking_number }}',
|
||||||
|
'subject_ar': 'تم قبول الشحنة - {{ tracking_number }}',
|
||||||
|
'email_body_en': 'You have successfully accepted Shipment {{ tracking_number }}.\nShipper: {{ shipper_name }}\nPickup: {{ pickup_address }}\nDelivery: {{ delivery_address }}\nPrice: {{ price }} OMR',
|
||||||
|
'email_body_ar': 'لقد قبلت الشحنة {{ tracking_number }} بنجاح.\nالشاحن: {{ shipper_name }}\nالاستلام: {{ pickup_address }}\nالتوصيل: {{ delivery_address }}\nالسعر: {{ price }} ر.ع',
|
||||||
|
'whatsapp_body_en': 'You have successfully accepted Shipment {{ tracking_number }}.\nShipper: {{ shipper_name }}\nPickup: {{ pickup_address }}\nDelivery: {{ delivery_address }}\nPrice/Bid: {{ price }} OMR',
|
||||||
|
'whatsapp_body_ar': 'لقد قبلت الشحنة {{ tracking_number }} بنجاح.\nالشاحن: {{ shipper_name }}\nالاستلام: {{ pickup_address }}\nالتوصيل: {{ delivery_address }}\nالسعر: {{ price }} ر.ع',
|
||||||
|
},
|
||||||
|
'shipment_status_update': {
|
||||||
|
'description': 'Sent on general status change (In Transit, Delivered)',
|
||||||
|
'variables': '{{ tracking_number }}, {{ status }}',
|
||||||
|
'subject_en': 'Shipment Update - {{ tracking_number }}',
|
||||||
|
'subject_ar': 'تحديث الشحنة - {{ tracking_number }}',
|
||||||
|
'email_body_en': 'Update for shipment {{ tracking_number }}:\nNew Status: {{ status }}',
|
||||||
|
'email_body_ar': 'تحديث للشحنة {{ tracking_number }}:\nالحالة الجديدة: {{ status }}',
|
||||||
|
'whatsapp_body_en': 'Update for shipment {{ tracking_number }}:\nNew Status: {{ status }}',
|
||||||
|
'whatsapp_body_ar': 'تحديث للشحنة {{ tracking_number }}:\nالحالة الجديدة: {{ status }}',
|
||||||
|
},
|
||||||
|
'admin_alert_driver_accept': {
|
||||||
|
'description': 'Sent to Admin when driver accepts shipment',
|
||||||
|
'variables': '{{ driver_name }}, {{ car_plate_number }}, {{ tracking_number }}, {{ shipper_name }}, {{ price }}',
|
||||||
|
'subject_en': 'Shipment Accepted ({{ tracking_number }})',
|
||||||
|
'subject_ar': 'تم قبول الشحنة ({{ tracking_number }})',
|
||||||
|
'email_body_en': 'Driver {{ driver_name }} ({{ car_plate_number }}) accepted shipment {{ tracking_number }} from {{ shipper_name }}.\nPrice: {{ price }} OMR',
|
||||||
|
'email_body_ar': 'قام السائق {{ driver_name }} ({{ car_plate_number }}) بقبول الشحنة {{ tracking_number }} من {{ shipper_name }}.\nالسعر: {{ price }} ر.ع',
|
||||||
|
'whatsapp_body_en': 'Driver {{ driver_name }} ({{ car_plate_number }}) accepted shipment {{ tracking_number }} from {{ shipper_name }}.\nPrice: {{ price }} OMR',
|
||||||
|
'whatsapp_body_ar': 'قام السائق {{ driver_name }} ({{ car_plate_number }}) بقبول الشحنة {{ tracking_number }} من {{ shipper_name }}.\nالسعر: {{ price }} ر.ع',
|
||||||
|
},
|
||||||
|
'contact_form_admin': {
|
||||||
|
'description': 'Sent to Admin when contact form is submitted',
|
||||||
|
'variables': '{{ name }}, {{ email }}, {{ message }}',
|
||||||
|
'subject_en': 'New Contact Message from {{ name }}',
|
||||||
|
'subject_ar': 'رسالة جديدة من {{ name }}',
|
||||||
|
'email_body_en': 'You have received a new message from your website contact form.\n\nName: {{ name }}\nEmail: {{ email }}\n\nMessage:\n{{ message }}',
|
||||||
|
'email_body_ar': 'لقد تلقيت رسالة جديدة من نموذج الاتصال.\n\nالاسم: {{ name }}\nالبريد: {{ email }}\n\nالرسالة:\n{{ message }}',
|
||||||
|
'whatsapp_body_en': 'New Message from {{ name }}:\n{{ message }}',
|
||||||
|
'whatsapp_body_ar': 'رسالة جديدة من {{ name }}:\n{{ message }}',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_notification_content(key, context, language=None):
|
||||||
|
if not language:
|
||||||
|
language = get_language() or 'en'
|
||||||
|
|
||||||
|
# 1. Fetch or Create Template
|
||||||
|
try:
|
||||||
|
template_obj = NotificationTemplate.objects.get(key=key)
|
||||||
|
except NotificationTemplate.DoesNotExist:
|
||||||
|
# Create default
|
||||||
|
default = DEFAULT_TEMPLATES.get(key)
|
||||||
|
if default:
|
||||||
|
template_obj = NotificationTemplate.objects.create(
|
||||||
|
key=key,
|
||||||
|
description=default.get('description', ''),
|
||||||
|
available_variables=default.get('variables', ''),
|
||||||
|
subject_en=default.get('subject_en', ''),
|
||||||
|
subject_ar=default.get('subject_ar', ''),
|
||||||
|
email_body_en=default.get('email_body_en', ''),
|
||||||
|
email_body_ar=default.get('email_body_ar', ''),
|
||||||
|
whatsapp_body_en=default.get('whatsapp_body_en', ''),
|
||||||
|
whatsapp_body_ar=default.get('whatsapp_body_ar', ''),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Fallback if key unknown
|
||||||
|
return f"[{key}] Subject", f"[{key}] Body", f"[{key}] WA"
|
||||||
|
|
||||||
|
# 2. Select Language Fields
|
||||||
|
# Note: If translation is missing, fallback to EN
|
||||||
|
if language == 'ar':
|
||||||
|
subject = template_obj.subject_ar or template_obj.subject_en
|
||||||
|
email_body = template_obj.email_body_ar or template_obj.email_body_en
|
||||||
|
whatsapp_body = template_obj.whatsapp_body_ar or template_obj.whatsapp_body_en
|
||||||
|
else:
|
||||||
|
subject = template_obj.subject_en
|
||||||
|
email_body = template_obj.email_body_en
|
||||||
|
whatsapp_body = template_obj.whatsapp_body_en
|
||||||
|
|
||||||
|
# 3. Render
|
||||||
|
# Use Django Template engine for variable substitution
|
||||||
|
|
||||||
|
def render(text, ctx):
|
||||||
|
if not text: return ""
|
||||||
|
try:
|
||||||
|
# Convert context to dict if it isn't already
|
||||||
|
if not isinstance(ctx, dict):
|
||||||
|
ctx = {}
|
||||||
|
t = Template(text)
|
||||||
|
return t.render(Context(ctx))
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Template rendering error for {key}: {e}")
|
||||||
|
return text
|
||||||
|
|
||||||
|
return render(subject, context), render(email_body, context), render(whatsapp_body, context)
|
||||||
@ -20,6 +20,7 @@ from django.db.models import Avg, Count
|
|||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
|
from .notifications import get_notification_content
|
||||||
from .whatsapp_utils import (
|
from .whatsapp_utils import (
|
||||||
notify_shipment_created,
|
notify_shipment_created,
|
||||||
notify_payment_received,
|
notify_payment_received,
|
||||||
@ -100,16 +101,16 @@ def register_shipper(request):
|
|||||||
|
|
||||||
# Send OTP
|
# Send OTP
|
||||||
method = form.cleaned_data.get('verification_method', 'email')
|
method = form.cleaned_data.get('verification_method', 'email')
|
||||||
otp_msg = _("Your Masar Verification Code is %(code)s") % {'code': code}
|
subj, email_msg, wa_msg = get_notification_content('otp_registration', {'code': code}, language=get_language())
|
||||||
|
|
||||||
if method == 'whatsapp':
|
if method == 'whatsapp':
|
||||||
phone = user.profile.phone_number
|
phone = user.profile.phone_number
|
||||||
send_whatsapp_message(phone, otp_msg)
|
send_whatsapp_message(phone, wa_msg)
|
||||||
messages.info(request, _("Verification code sent to WhatsApp."))
|
messages.info(request, _("Verification code sent to WhatsApp."))
|
||||||
else:
|
else:
|
||||||
send_html_email(
|
send_html_email(
|
||||||
subject=_('Verification Code'),
|
subject=subj,
|
||||||
message=otp_msg,
|
message=email_msg,
|
||||||
recipient_list=[user.email],
|
recipient_list=[user.email],
|
||||||
title=_('Welcome to Masar!'),
|
title=_('Welcome to Masar!'),
|
||||||
request=request
|
request=request
|
||||||
@ -143,16 +144,16 @@ def register_driver(request):
|
|||||||
|
|
||||||
# Send OTP
|
# Send OTP
|
||||||
method = form.cleaned_data.get('verification_method', 'email')
|
method = form.cleaned_data.get('verification_method', 'email')
|
||||||
otp_msg = _("Your Masar Verification Code is %(code)s") % {'code': code}
|
subj, email_msg, wa_msg = get_notification_content('otp_registration', {'code': code}, language=get_language())
|
||||||
|
|
||||||
if method == 'whatsapp':
|
if method == 'whatsapp':
|
||||||
phone = user.profile.phone_number
|
phone = user.profile.phone_number
|
||||||
send_whatsapp_message(phone, otp_msg)
|
send_whatsapp_message(phone, wa_msg)
|
||||||
messages.info(request, _("Verification code sent to WhatsApp."))
|
messages.info(request, _("Verification code sent to WhatsApp."))
|
||||||
else:
|
else:
|
||||||
send_html_email(
|
send_html_email(
|
||||||
subject=_('Verification Code'),
|
subject=subj,
|
||||||
message=otp_msg,
|
message=email_msg,
|
||||||
recipient_list=[user.email],
|
recipient_list=[user.email],
|
||||||
title=_('Welcome to Masar!'),
|
title=_('Welcome to Masar!'),
|
||||||
request=request
|
request=request
|
||||||
@ -489,22 +490,22 @@ def edit_profile(request):
|
|||||||
|
|
||||||
# 4. Send OTP
|
# 4. Send OTP
|
||||||
method = data.get('otp_method', 'email')
|
method = data.get('otp_method', 'email')
|
||||||
otp_msg = _("Your Masar Update Code is %(code)s") % {'code': code}
|
subj, email_msg, wa_msg = get_notification_content('otp_profile_update', {'code': code}, language=get_language())
|
||||||
|
|
||||||
if method == 'whatsapp':
|
if method == 'whatsapp':
|
||||||
# Use current phone if available, else new phone
|
# Use current phone if available, else new phone
|
||||||
phone = request.user.profile.phone_number or data['phone_number']
|
phone = request.user.profile.phone_number or data['phone_number']
|
||||||
send_whatsapp_message(phone, otp_msg)
|
send_whatsapp_message(phone, wa_msg)
|
||||||
messages.info(request, _("Verification code sent to WhatsApp."))
|
messages.info(request, _("Verification code sent to WhatsApp."))
|
||||||
else:
|
else:
|
||||||
# Default to email
|
# Default to email
|
||||||
# Send to the NEW email address (from the form), not the old one
|
# Send to the NEW email address (from the form), not the old one
|
||||||
target_email = data['email']
|
target_email = data['email']
|
||||||
send_html_email(
|
send_html_email(
|
||||||
subject=_('Verification Code'),
|
subject=subj,
|
||||||
message=otp_msg,
|
message=email_msg,
|
||||||
recipient_list=[target_email],
|
recipient_list=[target_email],
|
||||||
title=_('Profile Update Verification'),
|
title=subj,
|
||||||
request=request
|
request=request
|
||||||
)
|
)
|
||||||
messages.info(request, _("Verification code sent to email."))
|
messages.info(request, _("Verification code sent to email."))
|
||||||
@ -641,20 +642,21 @@ def request_login_otp(request):
|
|||||||
|
|
||||||
# Generate OTP
|
# Generate OTP
|
||||||
code = ''.join(random.choices(string.digits, k=6))
|
code = ''.join(random.choices(string.digits, k=6))
|
||||||
|
subj, email_msg, wa_msg = get_notification_content('otp_login', {'code': code}, language=get_language())
|
||||||
OTPVerification.objects.create(user=user, code=code, purpose='login')
|
OTPVerification.objects.create(user=user, code=code, purpose='login')
|
||||||
|
|
||||||
# Send OTP
|
# Send OTP
|
||||||
otp_msg = _("Your Masar Login Code is %(code)s. Do not share this code.") % {'code': code}
|
subj, email_msg, wa_msg = get_notification_content('otp_login', {'code': code}, language=get_language())
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if method == 'whatsapp':
|
if method == 'whatsapp':
|
||||||
phone = user.profile.phone_number
|
phone = user.profile.phone_number
|
||||||
send_whatsapp_message(phone, otp_msg)
|
send_whatsapp_message(phone, wa_msg)
|
||||||
message_sent = _("OTP sent to your WhatsApp.")
|
message_sent = _("OTP sent to your WhatsApp.")
|
||||||
else:
|
else:
|
||||||
send_html_email(
|
send_html_email(
|
||||||
subject=_('Login OTP'),
|
subject=subj,
|
||||||
message=otp_msg,
|
message=email_msg,
|
||||||
recipient_list=[user.email],
|
recipient_list=[user.email],
|
||||||
title=_('Login Verification'),
|
title=_('Login Verification'),
|
||||||
request=request
|
request=request
|
||||||
@ -983,6 +985,7 @@ def select_2fa_method(request):
|
|||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
method = request.POST.get('method')
|
method = request.POST.get('method')
|
||||||
code = ''.join(random.choices(string.digits, k=6))
|
code = ''.join(random.choices(string.digits, k=6))
|
||||||
|
subj, email_msg, wa_msg = get_notification_content('otp_login', {'code': code}, language=get_language())
|
||||||
|
|
||||||
# Invalidate old login OTPs
|
# Invalidate old login OTPs
|
||||||
OTPVerification.objects.filter(user=user, purpose='login').delete()
|
OTPVerification.objects.filter(user=user, purpose='login').delete()
|
||||||
@ -993,7 +996,7 @@ def select_2fa_method(request):
|
|||||||
try:
|
try:
|
||||||
send_html_email(
|
send_html_email(
|
||||||
subject=_("Your Login OTP"),
|
subject=_("Your Login OTP"),
|
||||||
message=f"Your verification code is: {code}",
|
message=email_msg,
|
||||||
recipient_list=[user.email],
|
recipient_list=[user.email],
|
||||||
title=_("Login Verification")
|
title=_("Login Verification")
|
||||||
)
|
)
|
||||||
@ -1006,7 +1009,7 @@ def select_2fa_method(request):
|
|||||||
|
|
||||||
elif method == 'whatsapp':
|
elif method == 'whatsapp':
|
||||||
if hasattr(user, 'profile') and user.profile.phone_number:
|
if hasattr(user, 'profile') and user.profile.phone_number:
|
||||||
if send_whatsapp_message(user.profile.phone_number, f"Your login verification code is: {code}"):
|
if send_whatsapp_message(user.profile.phone_number, wa_msg):
|
||||||
messages.success(request, _("OTP sent to your WhatsApp."))
|
messages.success(request, _("OTP sent to your WhatsApp."))
|
||||||
return redirect('verify_2fa_otp')
|
return redirect('verify_2fa_otp')
|
||||||
else:
|
else:
|
||||||
|
|||||||
@ -6,6 +6,7 @@ from django.core.mail import send_mail
|
|||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from .models import PlatformProfile
|
from .models import PlatformProfile
|
||||||
from .mail import send_html_email
|
from .mail import send_html_email
|
||||||
|
from .notifications import get_notification_content
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -16,7 +17,6 @@ def get_whatsapp_credentials():
|
|||||||
"""
|
"""
|
||||||
# Defaults
|
# Defaults
|
||||||
api_token = settings.WHATSAPP_API_KEY if hasattr(settings, 'WHATSAPP_API_KEY') else ""
|
api_token = settings.WHATSAPP_API_KEY if hasattr(settings, 'WHATSAPP_API_KEY') else ""
|
||||||
# We repurpose Phone ID as Domain in settings if needed, or default to Wablas DEU
|
|
||||||
domain = "https://deu.wablas.com"
|
domain = "https://deu.wablas.com"
|
||||||
secret_key = ""
|
secret_key = ""
|
||||||
source = "Settings/Env"
|
source = "Settings/Env"
|
||||||
@ -25,19 +25,15 @@ def get_whatsapp_credentials():
|
|||||||
try:
|
try:
|
||||||
profile = PlatformProfile.objects.first()
|
profile = PlatformProfile.objects.first()
|
||||||
if profile:
|
if profile:
|
||||||
# Check for token override
|
|
||||||
if profile.whatsapp_access_token:
|
if profile.whatsapp_access_token:
|
||||||
api_token = profile.whatsapp_access_token.strip()
|
api_token = profile.whatsapp_access_token.strip()
|
||||||
source = "Database (PlatformProfile)"
|
source = "Database (PlatformProfile)"
|
||||||
|
|
||||||
# Check for secret key override
|
|
||||||
if profile.whatsapp_app_secret:
|
if profile.whatsapp_app_secret:
|
||||||
secret_key = profile.whatsapp_app_secret.strip()
|
secret_key = profile.whatsapp_app_secret.strip()
|
||||||
|
|
||||||
# Check for domain override
|
|
||||||
if profile.whatsapp_business_phone_number_id:
|
if profile.whatsapp_business_phone_number_id:
|
||||||
domain = profile.whatsapp_business_phone_number_id.strip()
|
domain = profile.whatsapp_business_phone_number_id.strip()
|
||||||
# Ensure no trailing slash
|
|
||||||
if domain.endswith('/'):
|
if domain.endswith('/'):
|
||||||
domain = domain[:-1]
|
domain = domain[:-1]
|
||||||
|
|
||||||
@ -71,28 +67,21 @@ def send_whatsapp_message_detailed(phone_number, message):
|
|||||||
logger.warning(msg)
|
logger.warning(msg)
|
||||||
return False, msg
|
return False, msg
|
||||||
|
|
||||||
# Normalize phone number (Wablas expects international format without +, e.g. 628123...)
|
|
||||||
clean_phone = str(phone_number).replace('+', '').replace(' ', '')
|
clean_phone = str(phone_number).replace('+', '').replace(' ', '')
|
||||||
|
|
||||||
# Endpoint: /api/send-message (Simple Text)
|
|
||||||
# Ensure domain has schema
|
|
||||||
if not domain.startswith('http'):
|
if not domain.startswith('http'):
|
||||||
domain = f"https://{domain}"
|
domain = f"https://{domain}"
|
||||||
|
|
||||||
# Using the exact endpoint provided in user example
|
|
||||||
url = f"{domain}/api/send-message"
|
url = f"{domain}/api/send-message"
|
||||||
|
|
||||||
# Header construction logic from user example
|
|
||||||
auth_header = api_token
|
auth_header = api_token
|
||||||
if secret_key:
|
if secret_key:
|
||||||
auth_header = f"{api_token}.{secret_key}"
|
auth_header = f"{api_token}.{secret_key}"
|
||||||
|
|
||||||
headers = {
|
headers = {
|
||||||
"Authorization": auth_header,
|
"Authorization": auth_header,
|
||||||
# requests will set Content-Type to application/x-www-form-urlencoded when using 'data' param
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Payload as form data (not JSON)
|
|
||||||
data = {
|
data = {
|
||||||
"phone": clean_phone,
|
"phone": clean_phone,
|
||||||
"message": message,
|
"message": message,
|
||||||
@ -100,18 +89,14 @@ def send_whatsapp_message_detailed(phone_number, message):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
logger.info(f"Attempting to send WhatsApp message to {clean_phone} via {url}")
|
logger.info(f"Attempting to send WhatsApp message to {clean_phone} via {url}")
|
||||||
# Use data=data for form-urlencoded
|
|
||||||
response = requests.post(url, headers=headers, data=data, timeout=15)
|
response = requests.post(url, headers=headers, data=data, timeout=15)
|
||||||
|
|
||||||
# Handle non-JSON response (HTML error pages)
|
|
||||||
try:
|
try:
|
||||||
response_data = response.json()
|
response_data = response.json()
|
||||||
except ValueError:
|
except ValueError:
|
||||||
response_data = response.text
|
response_data = response.text
|
||||||
|
|
||||||
# Wablas success usually has status: true
|
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
# Check for logical success in JSON
|
|
||||||
if isinstance(response_data, dict):
|
if isinstance(response_data, dict):
|
||||||
if response_data.get('status') is True:
|
if response_data.get('status') is True:
|
||||||
logger.info(f"WhatsApp message sent to {clean_phone} via Wablas")
|
logger.info(f"WhatsApp message sent to {clean_phone} via Wablas")
|
||||||
@ -119,7 +104,6 @@ def send_whatsapp_message_detailed(phone_number, message):
|
|||||||
else:
|
else:
|
||||||
return False, f"Wablas API Logic Error (Source: {source}): {response_data}"
|
return False, f"Wablas API Logic Error (Source: {source}): {response_data}"
|
||||||
else:
|
else:
|
||||||
# If text, assume success if 200 OK? Or inspect text.
|
|
||||||
return True, f"Message sent (Raw Response). (Source: {source})"
|
return True, f"Message sent (Raw Response). (Source: {source})"
|
||||||
else:
|
else:
|
||||||
error_msg = f"Wablas API error (Source: {source}): {response.status_code} - {response_data}"
|
error_msg = f"Wablas API error (Source: {source}): {response.status_code} - {response_data}"
|
||||||
@ -130,14 +114,19 @@ def send_whatsapp_message_detailed(phone_number, message):
|
|||||||
logger.error(error_msg)
|
logger.error(error_msg)
|
||||||
return False, error_msg
|
return False, error_msg
|
||||||
|
|
||||||
def notify_admin(subject, message):
|
def notify_admin_alert(key, context):
|
||||||
"""Notifies the admin via Email and WhatsApp (if configured in PlatformProfile)."""
|
"""Notifies the admin via Email and WhatsApp using a template key."""
|
||||||
|
# 1. Get Template Content (Admin likely prefers EN, or system default?)
|
||||||
|
# Let's force EN for admin alerts for now, or check generic language setting?
|
||||||
|
# Usually admin alerts are in EN or the default site language.
|
||||||
|
subject, email_body, whatsapp_body = get_notification_content(key, context, language='en')
|
||||||
|
|
||||||
# Email
|
# Email
|
||||||
try:
|
try:
|
||||||
if hasattr(settings, 'CONTACT_EMAIL_TO') and settings.CONTACT_EMAIL_TO:
|
if hasattr(settings, 'CONTACT_EMAIL_TO') and settings.CONTACT_EMAIL_TO:
|
||||||
send_html_email(
|
send_html_email(
|
||||||
subject=f"Admin Alert: {subject}",
|
subject=f"Admin Alert: {subject}",
|
||||||
message=message,
|
message=email_body,
|
||||||
recipient_list=settings.CONTACT_EMAIL_TO,
|
recipient_list=settings.CONTACT_EMAIL_TO,
|
||||||
title="Admin Alert"
|
title="Admin Alert"
|
||||||
)
|
)
|
||||||
@ -148,146 +137,141 @@ def notify_admin(subject, message):
|
|||||||
try:
|
try:
|
||||||
profile = PlatformProfile.objects.first()
|
profile = PlatformProfile.objects.first()
|
||||||
if profile and profile.phone_number:
|
if profile and profile.phone_number:
|
||||||
# Assuming profile.phone_number is a valid WhatsApp number for Admin alerts
|
send_whatsapp_message(profile.phone_number, f"ADMIN ALERT: {subject}\n{whatsapp_body}")
|
||||||
send_whatsapp_message(profile.phone_number, f"ADMIN ALERT: {subject}\n{message}")
|
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def notify_shipment_created(parcel):
|
def notify_shipment_created(parcel):
|
||||||
"""Notifies the shipper that the shipment request was received via WhatsApp and Email."""
|
|
||||||
shipper_name = parcel.shipper.get_full_name() or parcel.shipper.username
|
shipper_name = parcel.shipper.get_full_name() or parcel.shipper.username
|
||||||
message = f"""Hello {shipper_name},
|
context = {
|
||||||
|
'name': shipper_name,
|
||||||
Your shipment request for '{parcel.description}' has been received.
|
'description': parcel.description,
|
||||||
Tracking Number: {parcel.tracking_number}
|
'tracking_number': parcel.tracking_number,
|
||||||
Status: {parcel.get_status_display()}
|
'status': parcel.get_status_display()
|
||||||
|
}
|
||||||
Please proceed to payment to make it visible to drivers."""
|
|
||||||
|
|
||||||
|
# Render for Shipper (check user language preference? For now assume session/request unavailable so maybe default or EN,
|
||||||
|
# OR we need a user language profile field. The user didn't ask for user-pref language yet, just bilingual templates.
|
||||||
|
# I'll default to EN unless I can guess.)
|
||||||
|
# Actually, if I can't determine, EN is safe.
|
||||||
|
# Future improvement: Add language to User Profile.
|
||||||
|
|
||||||
|
subj, email_msg, wa_msg = get_notification_content('shipment_created_shipper', context)
|
||||||
|
|
||||||
# WhatsApp
|
# WhatsApp
|
||||||
if hasattr(parcel.shipper, 'profile') and parcel.shipper.profile.phone_number:
|
if hasattr(parcel.shipper, 'profile') and parcel.shipper.profile.phone_number:
|
||||||
send_whatsapp_message(parcel.shipper.profile.phone_number, message)
|
send_whatsapp_message(parcel.shipper.profile.phone_number, wa_msg)
|
||||||
else:
|
|
||||||
logger.warning(f"No phone number found for shipper {shipper_name}, skipping WhatsApp.")
|
|
||||||
|
|
||||||
# Email
|
# Email
|
||||||
if parcel.shipper.email:
|
if parcel.shipper.email:
|
||||||
try:
|
send_html_email(
|
||||||
send_html_email(
|
subject=subj,
|
||||||
subject='Shipment Request Received - ' + parcel.tracking_number,
|
message=email_msg,
|
||||||
message=message,
|
recipient_list=[parcel.shipper.email],
|
||||||
recipient_list=[parcel.shipper.email],
|
title=subj
|
||||||
title='Shipment Request Received'
|
)
|
||||||
)
|
|
||||||
logger.info(f"Shipment created email sent to {parcel.shipper.email}")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Failed to send shipment created email to {parcel.shipper.email}: {e}")
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def notify_payment_received(parcel):
|
def notify_payment_received(parcel):
|
||||||
"""Notifies the shipper and receiver about successful payment via WhatsApp and Email."""
|
# Shipper
|
||||||
# Notify Shipper
|
|
||||||
shipper_name = parcel.shipper.get_full_name() or parcel.shipper.username
|
shipper_name = parcel.shipper.get_full_name() or parcel.shipper.username
|
||||||
shipper_msg = f"""Payment successful for shipment {parcel.tracking_number}.
|
context_shipper = {
|
||||||
Your shipment is now visible to available drivers."""
|
'tracking_number': parcel.tracking_number
|
||||||
|
}
|
||||||
|
subj, email_msg, wa_msg = get_notification_content('payment_success_shipper', context_shipper)
|
||||||
|
|
||||||
# WhatsApp Shipper
|
|
||||||
if hasattr(parcel.shipper, 'profile') and parcel.shipper.profile.phone_number:
|
if hasattr(parcel.shipper, 'profile') and parcel.shipper.profile.phone_number:
|
||||||
send_whatsapp_message(parcel.shipper.profile.phone_number, shipper_msg)
|
send_whatsapp_message(parcel.shipper.profile.phone_number, wa_msg)
|
||||||
|
|
||||||
# Email Shipper
|
|
||||||
if parcel.shipper.email:
|
if parcel.shipper.email:
|
||||||
try:
|
send_html_email(
|
||||||
send_html_email(
|
subject=subj,
|
||||||
subject='Payment Successful - ' + parcel.tracking_number,
|
message=email_msg,
|
||||||
message=shipper_msg,
|
recipient_list=[parcel.shipper.email],
|
||||||
recipient_list=[parcel.shipper.email],
|
title=subj
|
||||||
title='Payment Successful'
|
)
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Failed to send payment email to {parcel.shipper.email}: {e}")
|
|
||||||
|
|
||||||
# Notify Receiver
|
# Receiver
|
||||||
receiver_msg = f"""Hello {parcel.receiver_name},
|
context_receiver = {
|
||||||
|
'receiver_name': parcel.receiver_name,
|
||||||
A shipment is coming your way from {shipper_name}.
|
'shipper_name': shipper_name,
|
||||||
Tracking Number: {parcel.tracking_number}
|
'tracking_number': parcel.tracking_number,
|
||||||
Status: {parcel.get_status_display()}"""
|
'status': parcel.get_status_display()
|
||||||
send_whatsapp_message(parcel.receiver_phone, receiver_msg)
|
}
|
||||||
|
_, _, wa_msg_rx = get_notification_content('shipment_visible_receiver', context_receiver)
|
||||||
|
send_whatsapp_message(parcel.receiver_phone, wa_msg_rx)
|
||||||
|
|
||||||
def notify_driver_assigned(parcel):
|
def notify_driver_assigned(parcel):
|
||||||
"""Notifies the shipper, receiver, driver, and admin that a driver has picked up the parcel."""
|
|
||||||
driver_name = parcel.carrier.get_full_name() or parcel.carrier.username
|
driver_name = parcel.carrier.get_full_name() or parcel.carrier.username
|
||||||
shipper_name = parcel.shipper.get_full_name() or parcel.shipper.username
|
shipper_name = parcel.shipper.get_full_name() or parcel.shipper.username
|
||||||
|
|
||||||
|
# Get Car Plate
|
||||||
|
car_plate = ""
|
||||||
|
if hasattr(parcel.carrier, 'profile'):
|
||||||
|
car_plate = parcel.carrier.profile.car_plate_number
|
||||||
|
|
||||||
# 1. Notify Shipper
|
# 1. Notify Shipper
|
||||||
msg_shipper = f"""Shipment {parcel.tracking_number} has been picked up by {driver_name}.
|
context_shipper = {
|
||||||
Status: {parcel.get_status_display()}"""
|
'tracking_number': parcel.tracking_number,
|
||||||
|
'driver_name': driver_name,
|
||||||
|
'car_plate_number': car_plate,
|
||||||
|
'status': parcel.get_status_display()
|
||||||
|
}
|
||||||
|
subj_s, email_s, wa_s = get_notification_content('driver_pickup_shipper', context_shipper)
|
||||||
|
|
||||||
if hasattr(parcel.shipper, 'profile') and parcel.shipper.profile.phone_number:
|
if hasattr(parcel.shipper, 'profile') and parcel.shipper.profile.phone_number:
|
||||||
send_whatsapp_message(parcel.shipper.profile.phone_number, msg_shipper)
|
send_whatsapp_message(parcel.shipper.profile.phone_number, wa_s)
|
||||||
|
|
||||||
if parcel.shipper.email:
|
if parcel.shipper.email:
|
||||||
try:
|
send_html_email(subject=subj_s, message=email_s, recipient_list=[parcel.shipper.email], title=subj_s)
|
||||||
send_html_email(
|
|
||||||
subject='Driver Assigned - ' + parcel.tracking_number,
|
|
||||||
message=msg_shipper,
|
|
||||||
recipient_list=[parcel.shipper.email],
|
|
||||||
title='Driver Assigned'
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# 2. Notify Receiver
|
# 2. Notify Receiver
|
||||||
msg_receiver = f"""Shipment {parcel.tracking_number} from {shipper_name} is on the way (Picked up).
|
context_receiver = {
|
||||||
Driver: {driver_name}"""
|
'tracking_number': parcel.tracking_number,
|
||||||
send_whatsapp_message(parcel.receiver_phone, msg_receiver)
|
'shipper_name': shipper_name,
|
||||||
|
'driver_name': driver_name,
|
||||||
|
'car_plate_number': car_plate
|
||||||
|
}
|
||||||
|
_, _, wa_r = get_notification_content('driver_pickup_receiver', context_receiver)
|
||||||
|
send_whatsapp_message(parcel.receiver_phone, wa_r)
|
||||||
|
|
||||||
# 3. Notify Driver (Confirmation)
|
# 3. Notify Driver
|
||||||
msg_driver = f"""You have successfully accepted Shipment {parcel.tracking_number}.
|
context_driver = {
|
||||||
Shipper: {shipper_name}
|
'tracking_number': parcel.tracking_number,
|
||||||
Pickup: {parcel.pickup_address}
|
'shipper_name': shipper_name,
|
||||||
Delivery: {parcel.delivery_address}
|
'pickup_address': parcel.pickup_address,
|
||||||
Price/Bid: {parcel.price} OMR"""
|
'delivery_address': parcel.delivery_address,
|
||||||
|
'price': parcel.price
|
||||||
|
}
|
||||||
|
subj_d, email_d, wa_d = get_notification_content('driver_pickup_driver', context_driver)
|
||||||
|
|
||||||
if hasattr(parcel.carrier, 'profile') and parcel.carrier.profile.phone_number:
|
if hasattr(parcel.carrier, 'profile') and parcel.carrier.profile.phone_number:
|
||||||
send_whatsapp_message(parcel.carrier.profile.phone_number, msg_driver)
|
send_whatsapp_message(parcel.carrier.profile.phone_number, wa_d)
|
||||||
|
|
||||||
if parcel.carrier.email:
|
if parcel.carrier.email:
|
||||||
try:
|
send_html_email(subject=subj_d, message=email_d, recipient_list=[parcel.carrier.email], title=subj_d)
|
||||||
send_html_email(
|
|
||||||
subject='Shipment Accepted - ' + parcel.tracking_number,
|
|
||||||
message=msg_driver,
|
|
||||||
recipient_list=[parcel.carrier.email],
|
|
||||||
title='Shipment Accepted'
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# 4. Notify Admin
|
# 4. Notify Admin
|
||||||
notify_admin(
|
context_admin = {
|
||||||
subject=f"Shipment Accepted ({parcel.tracking_number})",
|
'driver_name': driver_name,
|
||||||
message=f"Driver {driver_name} accepted shipment {parcel.tracking_number} from {shipper_name}.\nPrice: {parcel.price} OMR"
|
'car_plate_number': car_plate,
|
||||||
)
|
'tracking_number': parcel.tracking_number,
|
||||||
|
'shipper_name': shipper_name,
|
||||||
|
'price': parcel.price
|
||||||
|
}
|
||||||
|
notify_admin_alert('admin_alert_driver_accept', context_admin)
|
||||||
|
|
||||||
def notify_status_change(parcel):
|
def notify_status_change(parcel):
|
||||||
"""Notifies parties about general status updates (In Transit, Delivered)."""
|
context = {
|
||||||
msg = f"""Update for shipment {parcel.tracking_number}:
|
'tracking_number': parcel.tracking_number,
|
||||||
New Status: {parcel.get_status_display()}"""
|
'status': parcel.get_status_display()
|
||||||
|
}
|
||||||
|
subj, email_msg, wa_msg = get_notification_content('shipment_status_update', context)
|
||||||
|
|
||||||
if hasattr(parcel.shipper, 'profile') and parcel.shipper.profile.phone_number:
|
if hasattr(parcel.shipper, 'profile') and parcel.shipper.profile.phone_number:
|
||||||
send_whatsapp_message(parcel.shipper.profile.phone_number, msg)
|
send_whatsapp_message(parcel.shipper.profile.phone_number, wa_msg)
|
||||||
|
|
||||||
if parcel.shipper.email:
|
if parcel.shipper.email:
|
||||||
try:
|
send_html_email(subject=subj, message=email_msg, recipient_list=[parcel.shipper.email], title=subj)
|
||||||
send_html_email(
|
|
||||||
subject='Shipment Update - ' + parcel.tracking_number,
|
|
||||||
message=msg,
|
|
||||||
recipient_list=[parcel.shipper.email],
|
|
||||||
title='Shipment Update'
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
send_whatsapp_message(parcel.receiver_phone, msg)
|
send_whatsapp_message(parcel.receiver_phone, wa_msg)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user