diff --git a/shop/admin.py b/shop/admin.py index 72d69c6..9c61ae7 100644 --- a/shop/admin.py +++ b/shop/admin.py @@ -1,7 +1,10 @@ from django.contrib import admin from django.shortcuts import render -from .models import Product, Color, Size, Order, OrderItem, GuestUser, Coupon, CouponUsage, OrderStatus +from .models import Product, Color, Size, Order, OrderItem, GuestUser, Coupon, CouponUsage, OrderStatus, ShippingAddress from django.utils.html import format_html +from django.urls import path +from django.contrib import admin +from django.shortcuts import redirect @admin.register(Product) class ProductAdmin(admin.ModelAdmin): @@ -37,11 +40,63 @@ class OrderItemInline(admin.TabularInline): extra = 0 readonly_fields = ('product', 'quantity', 'color', 'size', 'price') +@admin.register(ShippingAddress) +class ShippingAddressAdmin(admin.ModelAdmin): + list_display = ('street_address', 'city', 'postal_code', 'country') + search_fields = ('street_address', 'city', 'postal_code', 'country') + @admin.register(Order) class OrderAdmin(admin.ModelAdmin): - list_display = ('id', 'date_ordered', 'status', 'total_price') + list_display = ('id', 'date_ordered', 'status', 'total_price', 'get_shipping_address') inlines = [OrderItemInline] list_filter = ('status', 'payment_status') + readonly_fields = ('shipping_address_details',) + + def get_shipping_address(self, obj): + if obj.shipping_address: + return f"{obj.shipping_address.street_address}, {obj.shipping_address.city}" + return "No shipping address" + get_shipping_address.short_description = 'Shipping Address' + + def shipping_address_details(self, obj): + if obj.shipping_address: + return format_html( + """ +
+ Street: {}
+ {} + City: {}
+ State: {}
+ Postal Code: {}
+ Country: {} +
+ """, + obj.shipping_address.street_address, + f"Apartment: {obj.shipping_address.apartment}
" if obj.shipping_address.apartment else "", + obj.shipping_address.city, + obj.shipping_address.state, + obj.shipping_address.postal_code, + obj.shipping_address.country, + ) + return "No shipping address set" + shipping_address_details.short_description = 'Shipping Address Details' + + fieldsets = ( + (None, { + 'fields': ('user', 'guest_user', 'status', 'payment_status', 'total_price') + }), + ('Shipping Information', { + 'fields': ('shipping_address_details',), + }), + ('Payment Details', { + 'fields': ('stripe_payment_intent_id', 'stripe_checkout_session_id', 'stripe_mode'), + 'classes': ('collapse',) + }), + ('Discount Information', { + 'fields': ('coupon', 'discount_amount'), + 'classes': ('collapse',) + }), + ) def changelist_view(self, request, extra_context=None): # If 'show_preparation' parameter is in the request, show the preparation view @@ -103,6 +158,41 @@ class OrderAdmin(admin.ModelAdmin): context ) + def get_urls(self): + urls = super().get_urls() + custom_urls = [ + path('prepare-all-orders/', self.admin_site.admin_view(self.prepare_all_orders), name='prepare_all_orders'), + path('prepare-order//', self.admin_site.admin_view(self.prepare_order), name='prepare_order'), + path('cancel-and-refund-order//', self.admin_site.admin_view(self.cancel_and_refund_order), name='cancel_and_refund_order'), + ] + return custom_urls + urls + + def prepare_all_orders(self, request): + if request.method == 'POST': + Order.objects.filter(status=OrderStatus.PAID).update(status=OrderStatus.PREPARED) + self.message_user(request, "All orders have been marked as prepared.") + return redirect('admin:shop_order_changelist') + + def prepare_order(self, request, order_id): + if request.method == 'POST': + order = Order.objects.get(id=order_id) + order.status = OrderStatus.PREPARED + order.save() + self.message_user(request, f"Order #{order_id} has been marked as prepared.") + return redirect('admin:shop_order_changelist') + + def cancel_and_refund_order(self, request, order_id): + if request.method == 'POST': + order = Order.objects.get(id=order_id) + try: + # Reuse the cancel_order logic from your views + from .views import cancel_order + cancel_order(request, order_id) + self.message_user(request, f"Order #{order_id} has been cancelled and refunded.") + except Exception as e: + self.message_user(request, f"Error cancelling order: {str(e)}", level='ERROR') + return redirect('admin:shop_order_changelist') + class GuestUserOrderInline(admin.TabularInline): model = Order extra = 0 diff --git a/shop/forms.py b/shop/forms.py index 9a9befc..76e5b53 100644 --- a/shop/forms.py +++ b/shop/forms.py @@ -1,5 +1,6 @@ from django import forms from .models import Coupon +from .models import ShippingAddress class GuestCheckoutForm(forms.Form): email = forms.EmailField(required=True) @@ -7,3 +8,15 @@ class GuestCheckoutForm(forms.Form): class CouponApplyForm(forms.Form): code = forms.CharField(max_length=50) + +class ShippingAddressForm(forms.ModelForm): + class Meta: + model = ShippingAddress + fields = ['street_address', 'apartment', 'city', 'postal_code', 'country'] + widgets = { + 'street_address': forms.TextInput(attrs={'placeholder': 'Adresse'}), + 'apartment': forms.TextInput(attrs={'placeholder': 'Appartement (optionnel)'}), + 'city': forms.TextInput(attrs={'placeholder': 'Ville'}), + 'postal_code': forms.TextInput(attrs={'placeholder': 'Code postal'}), + 'country': forms.TextInput(attrs={'placeholder': 'Pays'}), + } diff --git a/shop/migrations/0027_shippingaddress_alter_order_payment_status_and_more.py b/shop/migrations/0027_shippingaddress_alter_order_payment_status_and_more.py new file mode 100644 index 0000000..fba6820 --- /dev/null +++ b/shop/migrations/0027_shippingaddress_alter_order_payment_status_and_more.py @@ -0,0 +1,41 @@ +# Generated by Django 5.1 on 2025-05-06 10:21 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('shop', '0026_alter_order_user'), + ] + + operations = [ + migrations.CreateModel( + name='ShippingAddress', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('street_address', models.CharField(max_length=255)), + ('apartment', models.CharField(blank=True, max_length=50, null=True)), + ('city', models.CharField(max_length=100)), + ('state', models.CharField(blank=True, max_length=100, null=True)), + ('postal_code', models.CharField(max_length=20)), + ('country', models.CharField(max_length=100)), + ], + ), + migrations.AlterField( + model_name='order', + name='payment_status', + field=models.CharField(choices=[('UNPAID', 'Unpaid'), ('PAID', 'Paid'), ('FAILED', 'Failed'), ('REFUNDED', 'Refunded')], default='UNPAID', max_length=20), + ), + migrations.AlterField( + model_name='order', + name='status', + field=models.CharField(choices=[('PENDING', 'Pending'), ('PAID', 'Paid'), ('SHIPPED', 'Shipped'), ('DELIVERED', 'Delivered'), ('CANCELED', 'Canceled'), ('REFUNDED', 'Refunded'), ('PREPARED', 'Prepared')], default='PENDING', max_length=20), + ), + migrations.AddField( + model_name='order', + name='shipping_address', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='shop.shippingaddress'), + ), + ] diff --git a/shop/models.py b/shop/models.py index 55e8345..ae55bbe 100644 --- a/shop/models.py +++ b/shop/models.py @@ -8,6 +8,8 @@ class OrderStatus(models.TextChoices): SHIPPED = 'SHIPPED', 'Shipped' DELIVERED = 'DELIVERED', 'Delivered' CANCELED = 'CANCELED', 'Canceled' + REFUNDED = 'REFUNDED', 'Refunded' + PREPARED = 'PREPARED', 'Prepared' class CutChoices(models.IntegerChoices): UNISEX = 0, 'Unisex' @@ -71,6 +73,14 @@ class CartItem(models.Model): def get_total_price(self): return self.product.price * self.quantity +class ShippingAddress(models.Model): + street_address = models.CharField(max_length=255) + apartment = models.CharField(max_length=50, blank=True, null=True) + city = models.CharField(max_length=100) + state = models.CharField(max_length=100, blank=True, null=True) + postal_code = models.CharField(max_length=20) + country = models.CharField(max_length=100) + class GuestUser(models.Model): email = models.EmailField() phone = models.CharField(max_length=20) @@ -112,6 +122,7 @@ class Coupon(models.Model): class Order(models.Model): user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True) + shipping_address = models.ForeignKey(ShippingAddress, on_delete=models.SET_NULL, null=True, blank=True) date_ordered = models.DateTimeField(auto_now_add=True) status = models.CharField(max_length=20, choices=OrderStatus.choices, default=OrderStatus.PENDING) total_price = models.DecimalField(max_digits=10, decimal_places=2, default=0.00) @@ -122,6 +133,7 @@ class Order(models.Model): ('UNPAID', 'Unpaid'), ('PAID', 'Paid'), ('FAILED', 'Failed'), + ('REFUNDED', 'Refunded') ]) webhook_processed = models.BooleanField(default=False) stripe_mode = models.CharField(max_length=10, default='test', choices=[ @@ -137,6 +149,13 @@ class Order(models.Model): def get_total_after_discount(self): return max(self.total_price - self.discount_amount, 0) + def is_cancellable(self): + """Check if the order can be cancelled""" + return self.status in [OrderStatus.PENDING, OrderStatus.PAID] + + def shipping_address_can_be_edited(self): + return self.status in [OrderStatus.PENDING, OrderStatus.PAID, OrderStatus.PREPARED] + class OrderItem(models.Model): order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name='items') product = models.ForeignKey(Product, on_delete=models.CASCADE) diff --git a/shop/static/shop/css/shop.css b/shop/static/shop/css/shop.css index 1384488..11e764f 100644 --- a/shop/static/shop/css/shop.css +++ b/shop/static/shop/css/shop.css @@ -546,3 +546,229 @@ v .cart-table { border: 3px solid #90ee90 !important; /* Use your light-green color */ transform: scale(1.1); /* Makes the selected color slightly larger */ } + +.status-badge { + padding: 4px 8px; + border-radius: 12px; + font-size: 12px; + color: white; + display: inline-block; +} +.status-badge.pending { + background-color: #f39200; +} +.status-badge.paid { + background-color: #27ae60; +} +.status-badge.shipped { + background-color: #3498db; +} +.status-badge.delivered { + background-color: #2c3e50; +} +.status-badge.canceled { + background-color: #e74c3c; +} +.status-badge.refunded { + background-color: #e74c3c; +} +.status-badge.prepared { + background-color: #27ae60; +} + +.original-price { + text-decoration: line-through; + color: #777; + font-size: 0.9em; + display: block; + font-weight: bold; +} + +.discounted-price { + font-weight: bold; +} + +.view-btn { + background-color: #3498db; + color: white; + border: none; + padding: 5px 10px; + border-radius: 12px; + cursor: pointer; + font-size: 12px; + font-weight: 600; + transition: background-color 0.3s ease; +} + +.view-btn:hover { + background-color: #2980b9; + color: white; +} + +.inline-form { + display: inline; +} + +.empty-orders { + text-align: center; + padding: 20px; +} + +.actions { + text-align: center; +} + +.order-meta { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 10px; + margin-bottom: 20px; + border-bottom: 1px solid #eee; + padding-bottom: 15px; +} + +.discount-section { + margin-top: 20px; + border-top: 1px dashed #eee; + padding-top: 15px; +} + +.discount-row { + display: flex; + justify-content: space-between; + margin-bottom: 5px; +} + +.total-row { + font-weight: bold; + margin-top: 5px; + padding-top: 5px; + border-top: 1px solid #eee; +} + +.coupon-info { + margin-top: 10px; + font-size: 0.9em; + color: #666; +} + +.order-actions { + margin-top: 20px; + text-align: right; + padding-top: 15px; + border-top: 1px solid #eee; +} + +.order-items-section { + margin-bottom: 20px; +} + +.order-items-section h3 { + margin-bottom: 10px; + padding-bottom: 5px; + border-bottom: 1px solid #eee; +} + +.order-table { + width: 100%; + border-collapse: separate; + border-spacing: 0; + border-radius: 12px; + overflow: hidden; + padding: 0px; +} + +.order-table tbody tr.odd-row { + background-color: #f0f0f0; +} + +.order-table tbody tr.even-row { + background-color: #e8e8e8; +} + +.shipping-address-section { + padding: 15px; + background: #f9f9f9; + border-radius: 5px; +} + +.address-details { + margin: 10px 0; +} + +.address-actions { + margin-top: 10px; +} + +.edit-address-btn, +.add-address-btn { + background-color: #007bff; + color: white; + border: none; + padding: 5px 10px; + border-radius: 4px; + cursor: pointer; +} + +.edit-address-btn:hover, +.add-address-btn:hover { + background-color: #0056b3; +} + +.shipping-address-form { + margin-top: 15px; + padding: 15px; + background: #fff; + border: 1px solid #ddd; + border-radius: 4px; +} + +.form-actions { + margin-top: 10px; + display: flex; + gap: 10px; +} + +.save-btn { + background-color: #27ae60; + color: white; + border: none; + padding: 5px 15px; + border-radius: 4px; + cursor: pointer; +} + +.cancel-btn { + background-color: #dc3545; + color: white; + border: none; + padding: 5px 15px; + border-radius: 4px; + cursor: pointer; +} + +.save-btn:hover { + background-color: #218838; +} + +.cancel-btn:hover { + background-color: #c82333; +} + +.address-input { + padding: 8px; + border: 1px solid #ddd; + border-radius: 12px; + font-size: 14px; +} + +.address-section { + margin: 20px 0; + padding: 15px; + border-radius: 5px; +} + +#address-message { + margin-top: 10px; + font-size: 14px; +} diff --git a/shop/stripe_utils.py b/shop/stripe_utils.py index 2fe4ede..15e7a87 100644 --- a/shop/stripe_utils.py +++ b/shop/stripe_utils.py @@ -76,5 +76,65 @@ class StripeService: """Retrieve a payment intent by ID""" return stripe.PaymentIntent.retrieve(payment_intent_id) + def create_refund(self, payment_intent_id, amount=None, reason=None): + """ + Create a refund for a payment intent + + Args: + payment_intent_id (str): The payment intent ID to refund + amount (int, optional): Amount to refund in cents. If None, refunds the entire amount. + reason (str, optional): The reason for the refund, one of 'duplicate', 'fraudulent', or 'requested_by_customer' + + Returns: + stripe.Refund: The created refund object + """ + try: + refund_params = { + 'payment_intent': payment_intent_id, + } + + if amount is not None: + refund_params['amount'] = amount + + if reason in ['duplicate', 'fraudulent', 'requested_by_customer']: + refund_params['reason'] = reason + + # Log the refund attempt + mode_str = "TEST" if self.is_test_mode else "LIVE" + logger.info(f"[{mode_str}] Creating refund for payment intent {payment_intent_id}") + + # Process the refund + refund = stripe.Refund.create(**refund_params) + + # Log success + logger.info(f"Refund created successfully: {refund.id}") + + return refund + + except stripe.error.StripeError as e: + # Log the error + logger.error(f"Stripe error creating refund: {str(e)}") + raise + except Exception as e: + # Log any other errors + logger.error(f"Unexpected error creating refund: {str(e)}") + raise + + def get_refund(self, refund_id): + """ + Retrieve a refund by ID + + Args: + refund_id (str): The ID of the refund to retrieve + + Returns: + stripe.Refund: The refund object + """ + try: + return stripe.Refund.retrieve(refund_id) + except stripe.error.StripeError as e: + logger.error(f"Stripe error retrieving refund {refund_id}: {str(e)}") + raise + # Create a singleton instance for import and use throughout the app stripe_service = StripeService() diff --git a/shop/templates/admin/shop/order/preparation_view.html b/shop/templates/admin/shop/order/preparation_view.html index 63213a5..fd6566a 100644 --- a/shop/templates/admin/shop/order/preparation_view.html +++ b/shop/templates/admin/shop/order/preparation_view.html @@ -5,8 +5,20 @@

Total orders with status PAID: {{ total_orders }}

Total items to prepare: {{ total_items }}

- - Back to Orders +
+ + Back to Orders + + +
+ {% csrf_token %} + +
+

Items Summary

@@ -35,7 +47,7 @@ @@ -54,13 +66,15 @@ + + {% for order in orders %} - + + + {% empty %} - + {% endfor %} diff --git a/shop/templates/shop/cart.html b/shop/templates/shop/cart.html index d9d2a3f..43f5401 100644 --- a/shop/templates/shop/cart.html +++ b/shop/templates/shop/cart.html @@ -5,17 +5,7 @@ {% block second_title %}La Boutique{% endblock %} {% block content %} - +{% include 'shop/partials/navigation_base.html' %} {% if STRIPE_IS_TEST_MODE %}
⚠️ Test Mode: Stripe is currently in test mode. No real payments will be processed. @@ -26,18 +16,18 @@
-

Votre panier

+

Votre panier

{% if display_data.items %} -
+

Comment fonctionne la livraison ?

Cette boutique fonctionne entre amis 'Padel Club'. Les commandes sont :

  1. Passées en ligne via notre système
  2. Préparées par notre équipe
  3. -
  4. Remises en main propre lors d'une prochaine session de padel
  5. +
  6. Remises en main propre lors d'une prochaine session de padel ou livrées à l'adresse indiquée dans la mesure du possible
-

Pas d'expédition : nous vous remettrons votre commande personnellement au club !

+

Livraison : En général, nous vous remettrons votre commande personnellement au club. Les livraisons peuvent être possible en fonction du lieu, n'hésitez donc pas à indiquer une adresse de livraison.

{% include 'shop/partials/order_items_display.html' with items=display_data.items total_quantity=display_data.total_quantity total_price=display_data.total_price edit_mode=True %} @@ -67,6 +57,24 @@
+
+
Adresse de livraison (dans la mesure du possible)
+ +
+ +
+ +
+
+ + + +
+
+
+ +
+
{% if user.is_authenticated %} @@ -123,6 +131,17 @@ const subtotalAmount = document.getElementById('subtotal-amount'); const discountAmount = document.getElementById('discount-amount'); const finalTotal = document.getElementById('final-total'); + const shippingForm = document.getElementById('shipping-form'); + + // Function to collect shipping address data + function getShippingData() { + const formData = new FormData(shippingForm); + const shippingData = {}; + formData.forEach((value, key) => { + shippingData[key] = value; + }); + return shippingData; + } // Initial values const originalTotal = parseFloat('{{ display_data.total_price }}'); @@ -244,6 +263,9 @@ checkoutButton.textContent = 'Chargement...'; checkoutButton.disabled = true; + // Get shipping data + const shippingData = getShippingData(); + // Create order and get checkout session fetch('{% url "shop:create_checkout_session" %}', { method: 'POST', @@ -251,6 +273,9 @@ 'Content-Type': 'application/json', 'X-CSRFToken': '{{ csrf_token }}' }, + body: JSON.stringify({ + shipping_address: shippingData + }), credentials: 'same-origin', }) .then(function(response) { diff --git a/shop/templates/shop/checkout.html b/shop/templates/shop/checkout.html index 15098ed..ee57c96 100644 --- a/shop/templates/shop/checkout.html +++ b/shop/templates/shop/checkout.html @@ -6,18 +6,9 @@ {% block content %} - -

Validation de la commande

+{% include 'shop/partials/navigation_base.html' %} + +

Validation de la commande

diff --git a/shop/templates/shop/my_orders.html b/shop/templates/shop/my_orders.html new file mode 100644 index 0000000..304fa71 --- /dev/null +++ b/shop/templates/shop/my_orders.html @@ -0,0 +1,81 @@ +{% extends 'tournaments/base.html' %} + +{% block head_title %}Mes Commandes{% endblock %} +{% block first_title %}Padel Club{% endblock %} +{% block second_title %}Mes Commandes{% endblock %} + +{% block content %} +{% include 'shop/partials/navigation_base.html' %} + +
+
+

Mes Commandes

+
+ {% if orders %} +
{{ item.quantity }} {% for order_id in item.orders %} - Order #{{ order_id }}{% if not forloop.last %}, {% endif %} + Order #{{ order_id }}{% if not forloop.last %}, {% endif %} {% endfor %}
Order # Date CustomerShipping Address ItemsActions
Order #{{ order.id }}Order #{{ order.id }} {{ order.date_ordered|date:"Y-m-d H:i" }} {% if order.user %} @@ -71,6 +85,16 @@ Unknown {% endif %} + {% if order.shipping_address %} + {{ order.shipping_address.street_address }} + {% if order.shipping_address.apartment %}, {{ order.shipping_address.apartment }}{% endif %}
+ {{ order.shipping_address.postal_code }} {{ order.shipping_address.city }}
+ {{ order.shipping_address.state }}, {{ order.shipping_address.country }} + {% else %} + No shipping address + {% endif %} +
{% for item in order.items.all %} {{ item.quantity }}x {{ item.product.title }} @@ -79,10 +103,28 @@
{% endfor %}
+
+ {% csrf_token %} + +
+ +
+ {% csrf_token %} + +
+
No orders foundNo orders found
+ + {% for order in orders %} + + + + + + + + + {% endfor %} + +
Commande #{{ order.id }} + Détails + {{ order.date_ordered|date:"d/m/Y H:i" }} + {% if order.status == 'PENDING' %} + En attente + {% elif order.status == 'PAID' %} + + {% elif order.status == 'PREPARED' %} + En cours de préparation + {% elif order.status == 'SHIPPED' %} + Expédiée + {% elif order.status == 'DELIVERED' %} + Livrée + {% elif order.status == 'CANCELED' %} + Annulée + {% elif order.status == 'REFUNDED' %} + Remboursée + {% endif %} + + {% if order.discount_amount > 0 %} + {{ order.total_price }}€ + {{ order.get_total_after_discount }}€ + {% else %} + {{ order.total_price }}€ + {% endif %} + + {% if order.status == 'PENDING' or order.status == 'PAID' %} +
+ {% csrf_token %} + +
+ {% endif %} +
+ {% else %} +
+

Vous n'avez pas encore de commandes.

+ Découvrir la boutique +
+ {% endif %} + + + {% if messages %} +
+ {% for message in messages %} +
+ {{ message }} +
+ {% endfor %} +
+ {% endif %} + + + +{% endblock %} diff --git a/shop/templates/shop/order_detail.html b/shop/templates/shop/order_detail.html new file mode 100644 index 0000000..25f5f4b --- /dev/null +++ b/shop/templates/shop/order_detail.html @@ -0,0 +1,150 @@ +{% extends 'tournaments/base.html' %} + +{% block head_title %}Détail de commande{% endblock %} +{% block first_title %}Padel Club{% endblock %} +{% block second_title %}Détail de commande{% endblock %} + +{% block content %} +{% include 'shop/partials/navigation_base.html' %} + +
+
+

Commande #{{ order.id }}

+
+
+
+ Date: {{ order.date_ordered|date:"d/m/Y H:i" }} +
+
+ Statut: + {% if order.status == 'PENDING' %} + En attente + {% elif order.status == 'PREPARED' %} + En cours de préparation + {% elif order.status == 'PAID' %} + + {% elif order.status == 'SHIPPED' %} + Expédiée + {% elif order.status == 'DELIVERED' %} + Livrée + {% elif order.status == 'CANCELED' %} + Annulée + {% elif order.status == 'REFUNDED' %} + Remboursée + {% endif %} +
+
+ +
+

Produits

+ {% with items=order_items total_quantity=total_quantity total_price=order.total_price %} + {% include 'shop/partials/order_items_display.html' with items=items total_quantity=total_quantity total_price=total_price edit_mode=False cancel_mode=order.is_cancellable %} + {% endwith %} +
+ +
+
Adresse de livraison (dans la mesure du possible)
+ {% if order.shipping_address %} +
+

{{ order.shipping_address.street_address }}

+ {% if order.shipping_address.apartment %} +

{{ order.shipping_address.apartment }}

+ {% endif %} +

{{ order.shipping_address.postal_code }} {{ order.shipping_address.city }}, {{ order.shipping_address.country }}

+
+ {% if order.shipping_address_can_be_edited %} + + + + {% endif %} + {% else %} +

Aucune adresse de livraison renseignée

+ {% if order.shipping_address_can_be_edited %} + + + {% endif %} + {% endif %} +
+ + {% if order.discount_amount > 0 %} +
+
+ Sous-total: + {{ order.total_price }}€ +
+
+ Réduction: + -{{ order.discount_amount }}€ +
+
+ Total final: + {{ order.get_total_after_discount }}€ +
+ + {% if order.coupon %} +
+ Coupon appliqué: {{ order.coupon.code }} +
+ {% endif %} +
+ {% endif %} + +
+ {% if order.status == 'PENDING' or order.status == 'PAID' %} +
+ {% csrf_token %} + +
+ {% endif %} +
+
+
+ + {% if messages %} +
+ {% for message in messages %} +
+ {{ message }} +
+ {% endfor %} +
+ {% endif %} +
+ + + +{% endblock %} diff --git a/shop/templates/shop/partials/navigation_base.html b/shop/templates/shop/partials/navigation_base.html new file mode 100644 index 0000000..3c49543 --- /dev/null +++ b/shop/templates/shop/partials/navigation_base.html @@ -0,0 +1,9 @@ + diff --git a/shop/templates/shop/partials/order_items_display.html b/shop/templates/shop/partials/order_items_display.html index fabb5f9..125a3a3 100644 --- a/shop/templates/shop/partials/order_items_display.html +++ b/shop/templates/shop/partials/order_items_display.html @@ -1,60 +1,68 @@ - - {% for item in items %} - - - {% if item.product_description %} - - {% endif %} - - - - {% if edit_mode %} - - {% endif %} - - {% endfor %} - - - - - - - - {% if edit_mode %} - - {% endif %} - - + + {% for item in items %} + + + {% if item.product_description %} + + {% endif %} + + + + {% if edit_mode %} + + {% elif cancel_mode and items.count > 1 %} + + {% endif %} + + {% endfor %} + + + + + + + + {% if edit_mode or cancel_mode %} + + {% endif %} + +
{{ item.product_title }}{{ item.product_description }} -
-
- {{ item.color_name }} | {{ item.size_name }} -
-
- {% if edit_mode %} -
-
- {% csrf_token %} - - - {{ item.quantity }} - -
-
- {% else %} - x {{ item.quantity }} - {% endif %} -
{{ item.total_price }} € -
- {% csrf_token %} - - -
-
{{ total_quantity }} produit(s){{ total_price }} €
{{ item.product_title }}{{ item.product_description }} +
+
+ {{ item.color_name }} | {{ item.size_name }} +
+
+ {% if edit_mode %} +
+
+ {% csrf_token %} + + + {{ item.quantity }} + +
+
+ {% else %} + x {{ item.quantity }} + {% endif %} +
{{ item.total_price }} € +
+ {% csrf_token %} + + +
+
+
+ {% csrf_token %} + +
+
{{ total_quantity }} produit(s){{ total_price }} €
diff --git a/shop/templates/shop/payment.html b/shop/templates/shop/payment.html index 4517651..c16d356 100644 --- a/shop/templates/shop/payment.html +++ b/shop/templates/shop/payment.html @@ -12,21 +12,12 @@ Use test card: 4242 4242 4242 4242 with any future date and any CVC. {% endif %} - +{% include 'shop/partials/navigation_base.html' %} +
-

Résumé de votre commande

+

Résumé de votre commande

{% include 'shop/partials/order_items_display.html' with items=display_data.items total_quantity=display_data.total_quantity total_price=display_data.total_price edit_mode=False %} diff --git a/shop/templates/shop/payment_cancel.html b/shop/templates/shop/payment_cancel.html index 996d4e6..e4fc22c 100644 --- a/shop/templates/shop/payment_cancel.html +++ b/shop/templates/shop/payment_cancel.html @@ -7,21 +7,11 @@ {% block content %} - +{% include 'shop/partials/navigation_base.html' %}
-

Paiement

+

Paiement

Le paiement a été annulé

Votre commande n'a pas été finalisée car le paiement a été annulé.

diff --git a/shop/templates/shop/payment_success.html b/shop/templates/shop/payment_success.html index e8880a5..eeb681e 100644 --- a/shop/templates/shop/payment_success.html +++ b/shop/templates/shop/payment_success.html @@ -5,21 +5,11 @@ {% block second_title %}La Boutique{% endblock %} {% block content %} - +{% include 'shop/partials/navigation_base.html' %}
-

Paiement réussi

+

Paiement réussi

Merci pour votre commande !

Votre paiement a été traité avec succès.

diff --git a/shop/templates/shop/product_list.html b/shop/templates/shop/product_list.html index aaacbc4..16cc311 100644 --- a/shop/templates/shop/product_list.html +++ b/shop/templates/shop/product_list.html @@ -6,22 +6,12 @@ {% block content %} - +{% include 'shop/partials/navigation_base.html' %}

Bienvenue sur la boutique Padel Club des copains !

Photos : Les photos des vêtements n'ont pas encore tous le logo, la description indique où il situera. -

Livraison : Commandez en ligne et récupérez votre commande en main propre lors de votre prochaine session de padel au club !

+

Livraison : Commandez en ligne et récupérez votre commande en main propre lors de votre prochaine session de padel au club. La livraison peut être possible !

{% endif %} -
-
- -
- {% csrf_token %} - {{ form.as_p }} - -
+
+
+
+ +
+ {% csrf_token %} + {{ form.as_p }} + +
+
-
-
-
- -
- {% csrf_token %} - {{ password_change_form.as_p }} - -
+
+
+ +
+ {% csrf_token %} + {{ password_change_form.as_p }} + +
+
{% endblock %} diff --git a/tournaments/templates/tournaments/navigation_base.html b/tournaments/templates/tournaments/navigation_base.html index 3624765..b9e4be8 100644 --- a/tournaments/templates/tournaments/navigation_base.html +++ b/tournaments/templates/tournaments/navigation_base.html @@ -9,4 +9,5 @@ Se connecter {% endif %} Ajouter vos tournois + La Boutique