|
|
|
|
@ -8,43 +8,41 @@ from django.db import transaction |
|
|
|
|
|
|
|
|
|
@receiver([post_save, post_delete], sender=Order) |
|
|
|
|
def send_order_notification(sender, instance, **kwargs): |
|
|
|
|
print("send_order_notification") |
|
|
|
|
""" |
|
|
|
|
Send an email notification when an order is created, updated, or deleted. |
|
|
|
|
""" |
|
|
|
|
# Use transaction.on_commit to ensure database operations are complete |
|
|
|
|
"""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): |
|
|
|
|
created = kwargs.get('created', False) |
|
|
|
|
update_fields = kwargs.get('update_fields', None) |
|
|
|
|
|
|
|
|
|
# Determine the action type |
|
|
|
|
if 'signal' in kwargs and kwargs['signal'] == post_delete: |
|
|
|
|
action = "DELETED" |
|
|
|
|
return |
|
|
|
|
elif created: |
|
|
|
|
action = "CREATED" |
|
|
|
|
# Skip processing for PENDING orders |
|
|
|
|
if instance.status == OrderStatus.PENDING: |
|
|
|
|
return |
|
|
|
|
else: |
|
|
|
|
action = "UPDATED" |
|
|
|
|
print('updated', update_fields) |
|
|
|
|
if update_fields and 'status' in update_fields: |
|
|
|
|
action = "UPDATED" |
|
|
|
|
|
|
|
|
|
# Get order details |
|
|
|
|
order_id = instance.id |
|
|
|
|
status = instance.status |
|
|
|
|
# Determine action type |
|
|
|
|
action = _determine_action_type(kwargs) |
|
|
|
|
if action in ["DELETED", "CREATED"]: |
|
|
|
|
return # No emails for these actions |
|
|
|
|
|
|
|
|
|
print("status", status) |
|
|
|
|
if status == OrderStatus.PENDING: |
|
|
|
|
return |
|
|
|
|
# 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) |
|
|
|
|
|
|
|
|
|
total_price = instance.total_price |
|
|
|
|
# Send customer notification if applicable |
|
|
|
|
if order_details['customer_email']: |
|
|
|
|
_send_customer_notification(instance, order_details, items_list) |
|
|
|
|
|
|
|
|
|
# Generate admin URL |
|
|
|
|
admin_url = f"{settings.SHOP_SITE_ROOT_URL}{reverse('admin:shop_order_change', args=[order_id])}" |
|
|
|
|
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: |
|
|
|
|
@ -56,60 +54,65 @@ def _send_order_email(instance, **kwargs): |
|
|
|
|
else: |
|
|
|
|
customer_info = "Client inconnu" |
|
|
|
|
|
|
|
|
|
# Build order item details |
|
|
|
|
# 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": |
|
|
|
|
# Use a fresh query to ensure we have the most recent data |
|
|
|
|
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" |
|
|
|
|
item_line = f"- {item.quantity}x {item.product.title} (Couleur: {color}, Taille: {size}, Prix: {item.price}€)\n" |
|
|
|
|
items_list += item_line |
|
|
|
|
|
|
|
|
|
# Compose the email |
|
|
|
|
if action == "CREATED": |
|
|
|
|
action_fr = "CRÉÉE" |
|
|
|
|
elif action == "UPDATED": |
|
|
|
|
action_fr = "MISE À JOUR" |
|
|
|
|
elif action == "DELETED": |
|
|
|
|
action_fr = "SUPPRIMÉE" |
|
|
|
|
else: |
|
|
|
|
action_fr = action |
|
|
|
|
items_list += f"- {item.quantity}x {item.product.title} (Couleur: {color}, Taille: {size}, Prix: {item.price}€)\n" |
|
|
|
|
return items_list |
|
|
|
|
|
|
|
|
|
# Translate current status |
|
|
|
|
status_fr_map = { |
|
|
|
|
"PENDING": "EN ATTENTE", |
|
|
|
|
"PAID": "PAYÉE", |
|
|
|
|
"SHIPPED": "EXPÉDIÉE", |
|
|
|
|
"DELIVERED": "LIVRÉE", |
|
|
|
|
"CANCELED": "ANNULÉE" |
|
|
|
|
def _translate_action(action): |
|
|
|
|
"""Translate action to French.""" |
|
|
|
|
translations = { |
|
|
|
|
"CREATED": "CRÉÉE", "UPDATED": "MISE À JOUR", "DELETED": "SUPPRIMÉE" |
|
|
|
|
} |
|
|
|
|
status_fr = status_fr_map.get(status, status) |
|
|
|
|
return translations.get(action, action) |
|
|
|
|
|
|
|
|
|
# Translate payment status |
|
|
|
|
payment_status_fr_map = { |
|
|
|
|
"UNPAID": "NON PAYÉE", |
|
|
|
|
"PAID": "PAYÉE", |
|
|
|
|
"FAILED": "ÉCHOUÉE" |
|
|
|
|
} |
|
|
|
|
payment_status_fr = payment_status_fr_map.get(instance.payment_status, instance.payment_status) |
|
|
|
|
def _send_internal_notification(instance, action, order_details, items_list): |
|
|
|
|
"""Send notification email to shop managers.""" |
|
|
|
|
action_fr = _translate_action(action) |
|
|
|
|
|
|
|
|
|
# Send internal notification email |
|
|
|
|
subject = f"Commande #{order_id} {action_fr}: {status_fr}" |
|
|
|
|
subject = f"Commande #{order_details['order_id']} {action_fr}: {order_details['status_fr']}" |
|
|
|
|
message = f""" |
|
|
|
|
La commande #{order_id} a été {action_fr.lower()} |
|
|
|
|
La commande #{order_details['order_id']} a été {action_fr.lower()} |
|
|
|
|
|
|
|
|
|
Statut: {status_fr} |
|
|
|
|
Statut de paiement: {payment_status_fr} |
|
|
|
|
Prix total: {total_price}€ |
|
|
|
|
Statut: {order_details['status_fr']} |
|
|
|
|
Statut de paiement: {order_details['payment_status_fr']} |
|
|
|
|
Prix total: {order_details['total_price']}€ |
|
|
|
|
|
|
|
|
|
{customer_info} |
|
|
|
|
{order_details['customer_info']} |
|
|
|
|
|
|
|
|
|
Articles: |
|
|
|
|
{items_list} |
|
|
|
|
|
|
|
|
|
Voir la commande dans le panneau d'administration: {admin_url} |
|
|
|
|
Voir la commande dans le panneau d'administration: {order_details['admin_url']} |
|
|
|
|
|
|
|
|
|
Ceci est un message automatique. Merci de ne pas répondre. |
|
|
|
|
""" |
|
|
|
|
@ -127,20 +130,93 @@ Ceci est un message automatique. Merci de ne pas répondre. |
|
|
|
|
fail_silently=False, |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
# Only send customer email for PAID status and if we have customer email |
|
|
|
|
if status == OrderStatus.PAID and customer_email and instance.payment_status == "PAID": |
|
|
|
|
# Generate customer-facing URLs |
|
|
|
|
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" |
|
|
|
|
contact_email = f"{settings.SHOP_SUPPORT_EMAIL}" |
|
|
|
|
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, |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
# Create a customer receipt email |
|
|
|
|
customer_subject = f"Confirmation de votre commande #{order_id} - PadelClub" |
|
|
|
|
customer_message = f""" |
|
|
|
|
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 {instance.date_ordered.strftime('%d/%m/%Y')} : |
|
|
|
|
Récapitulatif de votre commande #{order_id} du {date} : |
|
|
|
|
|
|
|
|
|
Statut: {status_fr} |
|
|
|
|
Prix total: {total_price}€ |
|
|
|
|
@ -161,11 +237,73 @@ Merci de votre confiance et à bientôt sur PadelClub ! |
|
|
|
|
L'équipe PadelClub |
|
|
|
|
""" |
|
|
|
|
|
|
|
|
|
# Send email to customer |
|
|
|
|
send_mail( |
|
|
|
|
subject=customer_subject, |
|
|
|
|
message=customer_message, |
|
|
|
|
from_email=settings.DEFAULT_FROM_EMAIL, |
|
|
|
|
recipient_list=[customer_email], |
|
|
|
|
fail_silently=False, |
|
|
|
|
) |
|
|
|
|
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 |
|
|
|
|
""" |
|
|
|
|
|