diff --git a/config/__pycache__/settings.cpython-311.pyc b/config/__pycache__/settings.cpython-311.pyc index cb560c3..71feee1 100644 Binary files a/config/__pycache__/settings.cpython-311.pyc and b/config/__pycache__/settings.cpython-311.pyc differ diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc index 214e066..ab1cbbb 100644 Binary files a/core/__pycache__/admin.cpython-311.pyc and b/core/__pycache__/admin.cpython-311.pyc differ diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index 1e2cecb..29a3374 100644 Binary files a/core/__pycache__/models.cpython-311.pyc and b/core/__pycache__/models.cpython-311.pyc differ diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index aa0fd4f..f76538a 100644 Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ diff --git a/core/__pycache__/whatsapp.cpython-311.pyc b/core/__pycache__/whatsapp.cpython-311.pyc new file mode 100644 index 0000000..9b55fa8 Binary files /dev/null and b/core/__pycache__/whatsapp.cpython-311.pyc differ diff --git a/core/admin.py b/core/admin.py index 3e1ca68..ee7d00e 100644 --- a/core/admin.py +++ b/core/admin.py @@ -1,5 +1,5 @@ from django.contrib import admin -from .models import Profile, Truck, Shipment, Bid, Message +from .models import Profile, Truck, Shipment, Bid, Message, WhatsAppConfig @admin.register(Profile) class ProfileAdmin(admin.ModelAdmin): @@ -28,3 +28,13 @@ class BidAdmin(admin.ModelAdmin): class MessageAdmin(admin.ModelAdmin): list_display = ('shipment', 'sender', 'timestamp') search_fields = ('content', 'sender__username') + +@admin.register(WhatsAppConfig) +class WhatsAppConfigAdmin(admin.ModelAdmin): + list_display = ('__str__', 'is_active') + + def has_add_permission(self, request): + # Only allow one configuration record + if self.model.objects.exists(): + return False + return super().has_add_permission(request) \ No newline at end of file diff --git a/core/migrations/0006_whatsappconfig.py b/core/migrations/0006_whatsappconfig.py new file mode 100644 index 0000000..47f0ff3 --- /dev/null +++ b/core/migrations/0006_whatsappconfig.py @@ -0,0 +1,26 @@ +# Generated by Django 5.2.7 on 2026-01-23 12:03 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0005_truck_registration_expiry_date'), + ] + + operations = [ + migrations.CreateModel( + name='WhatsAppConfig', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('api_token', models.CharField(max_length=255, verbose_name='Wablas API Token')), + ('secret_key', models.CharField(blank=True, max_length=255, null=True, verbose_name='Wablas Secret Key')), + ('is_active', models.BooleanField(default=True, verbose_name='Is Active')), + ], + options={ + 'verbose_name': 'WhatsApp Configuration', + 'verbose_name_plural': 'WhatsApp Configuration', + }, + ), + ] diff --git a/core/migrations/__pycache__/0006_whatsappconfig.cpython-311.pyc b/core/migrations/__pycache__/0006_whatsappconfig.cpython-311.pyc new file mode 100644 index 0000000..32e57b7 Binary files /dev/null and b/core/migrations/__pycache__/0006_whatsappconfig.cpython-311.pyc differ diff --git a/core/models.py b/core/models.py index 94a9344..209eb65 100644 --- a/core/models.py +++ b/core/models.py @@ -122,6 +122,18 @@ class Message(models.Model): def __str__(self): return f"From {self.sender.username} at {self.timestamp}" +class WhatsAppConfig(models.Model): + api_token = models.CharField(_('Wablas API Token'), max_length=255) + secret_key = models.CharField(_('Wablas Secret Key'), max_length=255, blank=True, null=True) + is_active = models.BooleanField(_('Is Active'), default=True) + + class Meta: + verbose_name = _('WhatsApp Configuration') + verbose_name_plural = _('WhatsApp Configuration') + + def __str__(self): + return str(_("WhatsApp Configuration")) + @receiver(post_save, sender=User) def create_user_profile(sender, instance, created, **kwargs): if created: diff --git a/core/views.py b/core/views.py index e95fd1a..e86b592 100644 --- a/core/views.py +++ b/core/views.py @@ -8,6 +8,7 @@ from django.contrib import messages from django.utils.translation import gettext as _ from django.db.models import Q from django.contrib.auth.models import User +from .whatsapp import send_whatsapp_message def home(request): """Render the landing screen for MASAR CARGO.""" @@ -113,6 +114,13 @@ def approve_truck(request, truck_id): truck = get_object_or_404(Truck, id=truck_id) truck.is_approved = True truck.save() + + # Notify Truck Owner via WhatsApp + owner_phone = getattr(truck.owner.profile, 'phone_number', None) + if owner_phone: + msg = f"Your truck ({truck.plate_no}) has been approved! You can now place bids on shipments." + send_whatsapp_message(owner_phone, msg) + messages.success(request, _("Truck approved successfully!")) return redirect('dashboard') @@ -173,6 +181,13 @@ def place_bid(request, shipment_id): bid.truck_owner = request.user bid.shipment = shipment bid.save() + + # Notify Shipper via WhatsApp + shipper_phone = getattr(shipment.shipper.profile, 'phone_number', None) + if shipper_phone: + msg = f"New bid placed on your shipment from {shipment.origin} to {shipment.destination}! Amount: {bid.amount}" + send_whatsapp_message(shipper_phone, msg) + messages.success(request, _("Bid placed successfully!")) return redirect('marketplace') else: @@ -211,5 +226,11 @@ def accept_bid(request, bid_id): bid.shipment.assigned_truck = bid.truck bid.shipment.save() + # Notify Truck Owner via WhatsApp + owner_phone = getattr(bid.truck_owner.profile, 'phone_number', None) + if owner_phone: + msg = f"Congratulations! Your bid for the shipment from {bid.shipment.origin} to {bid.shipment.destination} has been accepted." + send_whatsapp_message(owner_phone, msg) + messages.success(request, _("Bid accepted! Shipment is now in progress.")) - return redirect('shipment_detail', shipment_id=bid.shipment.id) \ No newline at end of file + return redirect('shipment_detail', shipment_id=bid.shipment.id) diff --git a/core/whatsapp.py b/core/whatsapp.py new file mode 100644 index 0000000..78943a3 --- /dev/null +++ b/core/whatsapp.py @@ -0,0 +1,62 @@ +import requests +import logging +from django.conf import settings +from .models import WhatsAppConfig + +logger = logging.getLogger(__name__) + +def send_whatsapp_message(phone, message): + """ + Sends a WhatsApp message using the Wablas API. + """ + try: + config = WhatsAppConfig.objects.filter(is_active=True).first() + except Exception as e: + logger.error(f"Error fetching WhatsApp configuration: {e}") + config = None + + if not config or not config.api_token: + # Fallback to settings for backward compatibility or if DB entry is missing + token = getattr(settings, 'WABLAS_API_TOKEN', None) + secret_key = getattr(settings, 'WABLAS_SECRET_KEY', None) + else: + token = config.api_token + secret_key = config.secret_key + + if not token: + logger.warning("Wablas API credentials not configured.") + return False + + url = "https://deu.wablas.com/api/send-message" + + # Headers logic based on whether secret key exists + auth_header = token + if secret_key: + auth_header = f"{token}.{secret_key}" + + headers = { + "Authorization": auth_header, + "Content-Type": "application/x-www-form-urlencoded" + } + + # Normalize phone number + phone = str(phone).replace('+', '').replace(' ', '') + + data = { + "phone": phone, + "message": message, + } + + try: + response = requests.post(url, headers=headers, data=data, timeout=10) + response_json = response.json() + + if response.status_code == 200 and response_json.get('status') == True: + logger.info(f"WhatsApp message sent to {phone}") + return True + else: + logger.error(f"Failed to send WhatsApp message to {phone}: {response.text}") + return False + except Exception as e: + logger.exception(f"Exception while sending WhatsApp message: {str(e)}") + return False \ No newline at end of file