from django.db.models.signals import post_save, post_delete from django.dispatch import receiver from django.core.mail import send_mail from django.conf import settings from django.urls import reverse from .models import Order, OrderItem, OrderStatus from django.db import transaction from django.contrib.auth.signals import user_logged_in from .cart import transfer_cart @receiver([post_save, post_delete], sender=Order) def send_order_notification(sender, instance, **kwargs): """Send an email notification when an order is created, updated, or deleted.""" transaction.on_commit(lambda: _send_order_email(instance, **kwargs)) def _send_order_email(instance, **kwargs): # Skip processing for PENDING orders if instance.status == OrderStatus.PENDING: return # Determine action type action = _determine_action_type(kwargs) if action in ["DELETED", "CREATED"]: return # No emails for these actions # Build common email components order_details = _get_order_details(instance) items_list = _build_items_list(instance.id, action) # Send internal notification _send_internal_notification(instance, action, order_details, items_list) # Send customer notification if applicable if order_details['customer_email']: _send_customer_notification(instance, order_details, items_list) def _determine_action_type(kwargs): """Determine the action type from signal kwargs.""" if 'signal' in kwargs and kwargs['signal'] == post_delete: return "DELETED" elif kwargs.get('created', False): return "CREATED" else: return "UPDATED" def _get_order_details(instance): """Extract and build order details dictionary.""" # Get customer info customer_email = None if instance.user: customer_info = f"Utilisateur: {instance.user.email}" customer_email = instance.user.email elif instance.guest_user: customer_info = f"Invité: {instance.guest_user.email} ({instance.guest_user.phone})" customer_email = instance.guest_user.email else: customer_info = "Client inconnu" # Translate statuses status_fr_map = { "PENDING": "EN ATTENTE", "PAID": "PAYÉE", "SHIPPED": "EXPÉDIÉE", "DELIVERED": "LIVRÉE", "CANCELED": "ANNULÉE" } payment_status_fr_map = { "UNPAID": "NON PAYÉE", "PAID": "PAYÉE", "FAILED": "ÉCHOUÉE" } return { 'order_id': instance.id, 'status': instance.status, 'status_fr': status_fr_map.get(instance.status, instance.status), 'payment_status': instance.payment_status, 'payment_status_fr': payment_status_fr_map.get(instance.payment_status, instance.payment_status), 'total_price': instance.total_price, 'customer_info': customer_info, 'customer_email': customer_email, 'date_ordered': instance.date_ordered, 'admin_url': f"{settings.SHOP_SITE_ROOT_URL}{reverse('admin:shop_order_change', args=[instance.id])}" } def _build_items_list(order_id, action): """Build the list of order items.""" items_list = "" if action != "DELETED": order_items = OrderItem.objects.filter(order_id=order_id).select_related('product', 'color', 'size') for item in order_items: color = item.color.name if item.color else "N/A" size = item.size.name if item.size else "N/A" items_list += f"- {item.quantity}x {item.product.title} (Couleur: {color}, Taille: {size}, Prix: {item.price}€)\n" return items_list def _translate_action(action): """Translate action to French.""" translations = { "CREATED": "CRÉÉE", "UPDATED": "MISE À JOUR", "DELETED": "SUPPRIMÉE" } return translations.get(action, action) def _send_internal_notification(instance, action, order_details, items_list): """Send notification email to shop managers.""" action_fr = _translate_action(action) subject = f"Commande #{order_details['order_id']} {action_fr}: {order_details['status_fr']}" message = f""" La commande #{order_details['order_id']} a été {action_fr.lower()} Statut: {order_details['status_fr']} Statut de paiement: {order_details['payment_status_fr']} Prix total: {order_details['total_price']}€ {order_details['customer_info']} Articles: {items_list} Voir la commande dans le panneau d'administration: {order_details['admin_url']} Ceci est un message automatique. Merci de ne pas répondre. """ # Send internal email recipient_list = [email for name, email in settings.SHOP_MANAGERS] if not recipient_list: recipient_list = [settings.DEFAULT_FROM_EMAIL] send_mail( subject=subject, message=message, from_email=settings.DEFAULT_FROM_EMAIL, recipient_list=recipient_list, fail_silently=False, ) def _send_customer_notification(instance, order_details, items_list): """Send appropriate notification email to customer based on order status.""" # Common email variables contact_email = settings.SHOP_SUPPORT_EMAIL shop_url = f"{settings.SHOP_SITE_ROOT_URL}/shop" date_formatted = order_details['date_ordered'].strftime('%d/%m/%Y') # Determine email content based on status and payment status email_content = _get_customer_email_content( instance.status, order_details['payment_status'], order_details['order_id'], date_formatted, order_details['status_fr'], order_details['total_price'], items_list, contact_email, shop_url ) # Skip if no email content returned if not email_content: return # Send email to customer send_mail( subject=email_content['subject'], message=email_content['message'], from_email=settings.DEFAULT_FROM_EMAIL, recipient_list=[order_details['customer_email']], fail_silently=False, ) def _get_customer_email_content(status, payment_status, order_id, date, status_fr, total_price, items_list, contact_email, shop_url): """Get the appropriate customer email content based on order status.""" # Payment confirmation email if status == OrderStatus.PAID and payment_status == "PAID": return { 'subject': f"Confirmation de votre commande #{order_id} - PadelClub", 'message': _build_payment_confirmation_email(order_id, date, status_fr, total_price, items_list, contact_email, shop_url) } # Order status update email elif status in [OrderStatus.SHIPPED, OrderStatus.DELIVERED, OrderStatus.CANCELED]: status_message = { OrderStatus.SHIPPED: "Votre commande a été expédiée et est en cours de livraison.", OrderStatus.DELIVERED: "Votre commande a été livrée. Nous espérons que vous apprécierez vos produits !", OrderStatus.CANCELED: "Votre commande a été annulée. Si vous n'êtes pas à l'origine de cette annulation, veuillez nous contacter immédiatement." }.get(status, "") return { 'subject': f"Mise à jour de votre commande #{order_id} - PadelClub", 'message': _build_status_update_email(order_id, date, status_message, status_fr, total_price, items_list, contact_email) } # Payment issue notification elif payment_status == "FAILED": return { 'subject': f"Problème de paiement pour votre commande #{order_id} - PadelClub", 'message': _build_payment_issue_email(order_id, date, total_price, items_list, contact_email, shop_url) } # Payment reminder for unpaid orders elif payment_status == "UNPAID" and status != OrderStatus.PENDING: return { 'subject': f"Rappel de paiement pour votre commande #{order_id} - PadelClub", 'message': _build_payment_reminder_email(order_id, date, total_price, items_list, contact_email) } # No email needed return None def _build_payment_confirmation_email(order_id, date, status_fr, total_price, items_list, contact_email, shop_url): """Build payment confirmation email message.""" return f""" Bonjour, Nous vous remercions pour votre commande sur PadelClub ! Récapitulatif de votre commande #{order_id} du {date} : Statut: {status_fr} Prix total: {total_price}€ Détail de votre commande : {items_list} Nous nous occupons de préparer votre commande dans les plus brefs délais. Pour toute question concernant votre commande, n'hésitez pas à contacter notre service client : {contact_email} Visitez notre boutique pour découvrir d'autres produits : {shop_url} Merci de votre confiance et à bientôt sur PadelClub ! L'équipe PadelClub """ def _build_status_update_email(order_id, date, status_message, status_fr, total_price, items_list, contact_email): """Build status update email message.""" return f""" Bonjour, Mise à jour concernant votre commande PadelClub #{order_id} du {date} : {status_message} Statut actuel: {status_fr} Prix total: {total_price}€ Détail de votre commande : {items_list} Pour toute question concernant votre commande, n'hésitez pas à contacter notre service client : {contact_email} Merci de votre confiance et à bientôt sur PadelClub ! L'équipe PadelClub """ def _build_payment_issue_email(order_id, date, total_price, items_list, contact_email, shop_url): """Build payment issue email message.""" return f""" Bonjour, Nous avons rencontré un problème lors du traitement du paiement de votre commande PadelClub #{order_id}. Détails de la commande : Date: {date} Prix total: {total_price}€ Articles: {items_list} Veuillez vérifier vos informations de paiement et réessayer. Si le problème persiste, n'hésitez pas à contacter notre service client : {contact_email} Vous pouvez également visiter notre boutique pour finaliser votre achat : {shop_url} Merci de votre compréhension. L'équipe PadelClub """ def _build_payment_reminder_email(order_id, date, total_price, items_list, contact_email): """Build payment reminder email message.""" return f""" Bonjour, Nous vous rappelons que votre commande PadelClub #{order_id} du {date} n'a pas encore été payée. Détails de la commande : Prix total: {total_price}€ Articles: {items_list} Pour finaliser votre commande, veuillez procéder au paiement dès que possible. Si vous rencontrez des difficultés ou si vous avez des questions, n'hésitez pas à contacter notre service client : {contact_email} Merci de votre confiance. L'équipe PadelClub """ @receiver(user_logged_in) def user_logged_in_handler(sender, request, user, **kwargs): """ When a user logs in, transfer any cart items from their anonymous session """ # Get the anonymous session key if hasattr(request, 'session') and not request.session.is_empty(): anonymous_session_key = request.session.session_key # After the user logs in, the session key changes # So we transfer cart from the old session to the new session if anonymous_session_key: transfer_cart(request, anonymous_session_key)