You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
381 lines
14 KiB
381 lines
14 KiB
from django.db.models.signals import pre_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([pre_save, post_delete], sender=Order)
|
|
def send_order_notification(sender, instance, **kwargs):
|
|
"""Send an email notification when an order is created, updated, or deleted."""
|
|
# For pre_save, we need to check if the instance exists in the database
|
|
if kwargs.get('signal', None) == pre_save:
|
|
try:
|
|
# Get the current instance from the database
|
|
old_instance = Order.objects.get(pk=instance.pk)
|
|
# Only send notification if status has changed
|
|
if old_instance.status != instance.status:
|
|
# Execute on commit to ensure DB consistency
|
|
transaction.on_commit(lambda: _send_order_email(instance, old_status=old_instance.status, **kwargs))
|
|
except Order.DoesNotExist:
|
|
# This is a new instance (creation)
|
|
# You might want to handle creation differently or just pass
|
|
pass
|
|
else:
|
|
# Handle post_delete
|
|
transaction.on_commit(lambda: _send_order_email(instance, **kwargs))
|
|
|
|
def _send_order_email(instance, old_status=None, **kwargs):
|
|
# Skip processing for PENDING orders
|
|
if instance.status == OrderStatus.PENDING:
|
|
return
|
|
|
|
# Determine action type
|
|
if 'signal' in kwargs and kwargs['signal'] == post_delete:
|
|
action = "DELETED"
|
|
elif old_status is None:
|
|
action = "CREATED"
|
|
else:
|
|
action = "UPDATED"
|
|
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 _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"
|
|
}
|
|
|
|
# Calculate discount information
|
|
has_coupon = instance.coupon is not None
|
|
coupon_info = ""
|
|
final_price = instance.total_price
|
|
|
|
if has_coupon:
|
|
coupon_code = instance.coupon.code
|
|
discount_amount = instance.discount_amount
|
|
final_price = instance.get_total_after_discount()
|
|
|
|
if instance.coupon.discount_percent > 0:
|
|
coupon_info = f"Code promo: {coupon_code} ({instance.coupon.discount_percent}%)"
|
|
else:
|
|
coupon_info = f"Code promo: {coupon_code} (€{discount_amount})"
|
|
|
|
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,
|
|
'has_coupon': has_coupon,
|
|
'coupon_info': coupon_info,
|
|
'discount_amount': instance.discount_amount if has_coupon else 0,
|
|
'final_price': final_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)
|
|
|
|
# Build price information with coupon details if applicable
|
|
price_info = f"Prix total: {order_details['total_price']}€"
|
|
server = ""
|
|
if settings.DEBUG:
|
|
server = "DEBUG: "
|
|
|
|
if order_details['has_coupon']:
|
|
price_info = f"""
|
|
Prix total: {order_details['total_price']}€
|
|
{order_details['coupon_info']}
|
|
Réduction: -{order_details['discount_amount']}€
|
|
Montant payé: {order_details['final_price']}€"""
|
|
subject = f"{server}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']}
|
|
{price_info}
|
|
|
|
{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'],
|
|
order_details['has_coupon'],
|
|
order_details['coupon_info'],
|
|
order_details['discount_amount'],
|
|
order_details['final_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, has_coupon, coupon_info, discount_amount,
|
|
final_price, items_list, contact_email, shop_url):
|
|
"""Get the appropriate customer email content based on order status."""
|
|
|
|
# Build price information with coupon details if applicable
|
|
price_info = f"Prix total: {total_price}€"
|
|
if has_coupon:
|
|
price_info = f"""Prix total: {total_price}€
|
|
{coupon_info}
|
|
Réduction: -{discount_amount}€
|
|
Montant payé: {final_price}€"""
|
|
|
|
# Payment confirmation email
|
|
if status == OrderStatus.PAID and payment_status == "PAID":
|
|
return {
|
|
'subject': f"Confirmation de votre commande #{order_id} - Padel Club",
|
|
'message': _build_payment_confirmation_email(order_id, date, status_fr,
|
|
price_info, 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} - Padel Club",
|
|
'message': _build_status_update_email(order_id, date, status_message, status_fr,
|
|
price_info, items_list, contact_email)
|
|
}
|
|
|
|
# Payment issue notification
|
|
elif payment_status == "FAILED":
|
|
return {
|
|
'subject': f"Problème de paiement pour votre commande #{order_id} - Padel Club",
|
|
'message': _build_payment_issue_email(order_id, date, price_info,
|
|
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} - Padel Club",
|
|
'message': _build_payment_reminder_email(order_id, date, price_info,
|
|
items_list, contact_email)
|
|
}
|
|
|
|
# No email needed
|
|
return None
|
|
|
|
def _build_payment_confirmation_email(order_id, date, status_fr, price_info, items_list, contact_email, shop_url):
|
|
"""Build payment confirmation email message."""
|
|
return f"""
|
|
Bonjour,
|
|
|
|
Nous vous remercions pour votre commande sur Padel Club !
|
|
|
|
Récapitulatif de votre commande #{order_id} du {date} :
|
|
|
|
Statut: {status_fr}
|
|
{price_info}
|
|
|
|
Détail de votre commande :
|
|
{items_list}
|
|
|
|
IMPORTANT - COMMENT RÉCUPÉRER VOTRE COMMANDE :
|
|
Notre boutique fonctionne entre amis 'Padel Club'. Nous allons préparer votre commande et vous la remettre en main propre lors d'une prochaine session de padel ! Aucune expédition n'est prévue, nous vous remettrons directement vos articles sur place.
|
|
|
|
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 Padel Club !
|
|
|
|
L'équipe Padel Club
|
|
"""
|
|
|
|
def _build_status_update_email(order_id, date, status_message, status_fr, price_info, items_list, contact_email):
|
|
"""Build status update email message."""
|
|
return f"""
|
|
Bonjour,
|
|
|
|
Mise à jour concernant votre commande Padel Club #{order_id} du {date} :
|
|
|
|
{status_message}
|
|
|
|
Statut actuel: {status_fr}
|
|
{price_info}
|
|
|
|
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 Padel Club !
|
|
|
|
L'équipe Padel Club
|
|
"""
|
|
|
|
def _build_payment_issue_email(order_id, date, price_info, 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 Padel Club #{order_id}.
|
|
|
|
Détails de la commande :
|
|
Date: {date}
|
|
{price_info}
|
|
|
|
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 Padel Club
|
|
"""
|
|
|
|
def _build_payment_reminder_email(order_id, date, price_info, items_list, contact_email):
|
|
"""Build payment reminder email message."""
|
|
return f"""
|
|
Bonjour,
|
|
|
|
Nous vous rappelons que votre commande Padel Club #{order_id} du {date} n'a pas encore été payée.
|
|
|
|
Détails de la commande :
|
|
{price_info}
|
|
|
|
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 Padel Club
|
|
"""
|
|
|
|
@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)
|
|
|