miércoles, 31 de enero de 2024

25.- Creando un sistemas de descuento o cupones para la tienda.

Muchas tiendas en línea ofrecen cupones a los clientes que pueden canjearse por descuentos en sus compras. Un cupón en línea generalmente consta de un código que se proporciona a los usuarios y es válido por un período de tiempo específico. Vamos a crear un sistema de cupones para nuestra tienda. Tus cupones serán válidos para los clientes durante un cierto período de tiempo. Los cupones no tendrán limitaciones en cuanto al número de veces que pueden ser canjeados y se aplicarán al valor total del carrito de compras.

Para esta funcionalidad, necesitarás crear un modelo para almacenar el código del cupón, un marco de tiempo válido y el descuento a aplicar. Crea una nueva aplicación dentro del proyecto "PracticaDjango" utilizando el siguiente comando:

python manage.py startapp Cupones

Como siempre que creamos una nueva aplicación tenemos que registrarla. Para ello entra en el directorio "PracticaDjango" y regístrala en el archivo settings.py:

PracticaDjango/Orders/admin.py

#...
INSTALLED_APPS = [
    # Nuestras aplicaciones
    'Proyecto_web_app.apps.ProyectoWebAppConfig',
    'Servicios.apps.ServiciosConfig',
    'Blog.apps.BlogConfig',
    'Contacto.apps.ContactoConfig',
    'Tienda.apps.TiendaConfig',
    'Carro.apps.CarroConfig',
    'Autentificacion.apps.AutentificacionConfig',
    'Orders.apps.OrdersConfig',
    'Payment.apps.PaymentConfig',
    'Cupones.apps.CuponesConfig',
#...


Construyendo el modelo para los Cupones. 

Edita el archivo models.py de esta nueva aplicación "Cupones" y añade el siguiente código:

PracticaDjango/Cupones/models.py

from django.db import models
from django.core.validators import MinValueValidator, MaxValueValidator

# Create your models here.

class Cupon(models.Model):
    codigo = models.CharField(max_length=50, unique=True)
    valido_desde = models.DateTimeField()
    valido_hasta = models.DateTimeField()
    descuento = models.IntegerField(validators=[MinValueValidator(
        0), MaxValueValidator(100)], help_text='Valor del porcentaje (0-100)')
    activo = models.BooleanField()

    def __str__(self):
        return self.codigo
Este es modelo que vamos a usar para guardar los cupones. Contiene los siguientes campos:

codigo - el código que el usuario tendrá que introducir para que se le aplique el descuento en la compra.
valido_desde - la fecha desde la que es válido el cupón.
valido_hasta - la fecha de caducidad de el cupón.
descuento - el porcentaje que aplicaremos de descuento al producto. Es un porcentaje, así que usaremos validadores para asegurarnos de que no sea inferior a cero, ni superior a 100.
activo - un campo booleano para indicar si el cupón está activo.

Como siempre que modificamos o creamos algo en models.py hay que realizar las migraciones correspondientes con:

python manage.py makemigrations
python manage.py migrate

Una vez que hemos aplicado los cambios a la base de datos vamos a añadir el modelo al panel de administración. Ejecutamos el servidor de administración con:

python manage.py runserver

vamos a la página de administración, creamos un cupón y vemos que todo funciona.


Aplicando el cupón al carro de la compra.


Lo que tenemos que implementar es una forma para que los clientes apliquen los cupones a sus compras. La funcionalidad para aplicar un cupón sería la siguiente:

1. El usuario agrega productos al carrito de compras.
2. El usuario puede ingresar un código de cupón en un formulario que se muestra en la página de detalles del carrito de compras.
3. Cuando el usuario ingresa un código de cupón y envía el formulario, buscas un cupón existente con el código proporcionado que esté actualmente válido. Debes verificar que el código del cupón coincida con el ingresado por el usuario, que el atributo activo sea True y que la fecha y hora actual esté entre los valores de valido_desde y valido_hasta.
4. Si se encuentra un cupón, lo guardas en la sesión del usuario y muestras el carrito, incluyendo el descuento aplicado y el monto total actualizado.
5. Cuando el usuario realiza un pedido, guardas el cupón en el pedido proporcionado.

Crea un nuevo archivo dentro del directorio de la aplicación "Cupones" y nómbralo forms.py. Agrega el siguiente código a él: 

PracticaDjango/Cupones/forms.py

from django import forms

class CuponFormulario(forms.Form):
    codigo = forms.CharField(max_length=50)

Este es formulario que usaremos para introducir el cupón. Edita el archivo views.py de la aplicación y añade el siguiente código en el:

PracticaDjango/Cupones/views.py

from django.shortcuts import render, redirect
from django.utils import timezone
from django.views.decorators.http import require_POST
from .models import Cupon
from .forms import CuponFormulario


@require_POST
def aplicar_cupon(request):
    now = timezone.now()
    form = CuponFormulario(request.POST)
    if form.is_valid():
        codigo = form.cleaned_data['codigo']
        try:
            cupon = Cupon.objects.get(codigo__iexact=codigo,
                                      valido_desde__lte=now,
                                      valido_hasta__gte=now,
                                      activo=True)
            request.session['cupon_id'] = cupon.id
        except Cupon.DoesNotExist:
            request.session['cupon_id'] = None
    return redirect('cart:cart_detail')
Esta vista valida el cupón y lo guarda en la sesión del usuario. Aplicamos el decorador @require_POST a esta vista para que solo acepte las peticiones POST a esta página. En la vista realizamos las siguientes tareas:

  1. Creamos una instancia del formulario "CuponFormulario" usando los datos enviados en la petición POST y comprobamos que es válido.
  2. Si el formulario es válido, conseguimos el código introducido por el usuario del diccionario cleaned_data que nos facilita el formulario. Luego intentamos obtener el cupón a través del código facilitado por el usuario. Usamos __iexact para que el código introducido sea igual exactamente al código existente, que no se distinga entre minúsculas o mayúsculas. Tiene que ser exactamente igual. Por otra parte el cupón tiene que estar activo y que sea válido en el periodo de tiempo que hemos establecido. Usamos la función de Django timezone.now() para conseguir la fecha actual de la zona en la que estamos y la comparamos con los valores que hemos establecido en valido_desde y valido_hasta. Para ello usamos __lte (menor o igual que) y __gte (mayor o igual a).
  3. Guardamos el cupón en la sesión del usuario.
  4. Redirigimos al usuario hacia la url cart_detail para que se muestre el carro con el cupón aplicado.


Necesitamos crear un patrón de URL para la vista que hemos creado. Vamos con ello. Crea un archivo nuevo llamado urls.py dentro de la aplicación Cupones y añade el siguiente código:

PracticaDjango/Cupones/urls.py

from django.urls import path
from . import views

app_name = 'cupones'

urlpatterns = [
    path('aplicar/', views.aplicar_cupon, name='aplicar'),
]
Luego edita el archivo principal urls.py de la aplicación PracticaDjango e incluye este patrón.

PracticaDjango/Cupones/urls.py

#...
urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('Proyecto_web_app.urls')),
    path('servicios/', include('Servicios.urls')),
    path('blog/', include('Blog.urls')),
    path('contacto/', include('Contacto.urls')),
    path('sitemap.xml', sitemap, {'sitemaps': sitemaps}, name='django.contrib.sitemaps.views.sitemap'),
    path('payment/', include('Payment.urls', namespace='payment')),
    path('cupones/', include('Cupones.urls', namespace='cupones')),
    path('tienda/', include('Tienda.urls')),
    path('carro/', include('Carro.urls')),
    path('cuenta/', include('Autentificacion.urls')),
    path('social-auth/', include('social_django.urls', namespace='social')),
    path('orders/', include('Orders.urls', namespace='orders')),
    path('__debug__/', include('debug_toolbar.urls')),    
]
#...

Recuerda colocarlo antes de los patrones de la tienda.

Ahora edita el archivo carro.py que está dentro de la aplicación Carro. Importa el modelo que hemos creado con la siguiente línea de código:

PracticaDjango/Carro/carro.py

# Para aplicar el cupón
from Cupones.models import Cupon
#...

Luego añade esta línea en el iniciador de la clase carro:

PracticaDjango/Carro/carro.py

#...
def __init__(self, request) -> None:
        """
        Inicializa el carro.
        """
        self.session = request.session
        # construimos un carro de la compra para esta sesión.
        carro = self.session.get(settings.CART_SESSION_ID)
        if not carro:
            # save an empty cart in the session
            carro = self.session[settings.CART_SESSION_ID] = {}
            # guardamos el carro en la sesión y va a ser un diccionario
            # con los productos que el usuario va a comprar
            # la clave es el id del producto y el valor son las características del producto
        self.carro = carro
        # guardamos el cupon a aplicar en la sesión
        self.cupon_id = self.session.get('cupon_id')


En este código, intentamos obtener el cupon_id de la sesión actual y almacenar su valor en el objeto carro. Añade los siguientes métodos resaltados al objeto carro:

PracticaDjango/Carro/carro.py

#...
class Carro:
    #...
    @property
    def cupon(self):
        if self.cupon_id:
            try:
                return Cupon.objects.get(id=self.cupon_id)
            except Cupon.DoesNotExist:
                pass
        return None

    def conseguir_descuento(self):
        if self.cupon:
            return (self.cupon.descuento / Decimal(100)) * self.get_total_price()
        return Decimal(0)

    def precio_total_despues_de_descuento(self):
        return self.get_total_price() - self.conseguir_descuento()

Estos métodos son los siguientes:

• cupon(): Defines este método como una propiedad. Si el carrito contiene un atributo cupon_id, se devuelve el objeto Cupon con el ID proporcionado.

• conseguir_descuento(): Si el carrito contiene un cupón, se recupera su tasa de descuento y se devuelve la cantidad a restar del monto total del carrito.

• precio_total_despues_de_descuento(): Devuelves el monto total del carrito después de restar la cantidad devuelta por el método conseguir_descuento().


La clase Carro está ahora preparada para manejar un cupón aplicado a la sesión actual y aplicar el descuento correspondiente.

Ahora incluyamos el sistema de cupones en la vista detallada del carrito. Edita el archivo views.py de la aplicación del carrito y agrega la siguiente importación en la parte superior del archivo y el código resaltado en la vista mostrar_carro:

PracticaDjango/Carro/carro.py

#...
# para aplicar un descuento
from Cupones.forms import CuponFormulario
#...
# vista para mostrar los productos del carro
def mostrar_carro(request):
    productos = Producto.objects.all()
    carro = Carro(request)
    formulario_cupon = CuponFormulario()
    contexto = {
        "productos": productos,
        "carro": carro,
        "formulario_cupon": formulario_cupon,
    }
    return render(request, "Tienda/carro_detalle.html", contexto)


Bien, ahora busca la plantilla html del carro que está dentro de la aplicación Tienda en el archivo Tienda/carro_detalle.html. Edítalo y localiza las siguientes líneas:

PracticaDjango/Tienda/templates/Tienda/carro_detalle.html

#...
<tr class="total">
    <td>Total</td>
    <td colspan="4"></td>
    <td class="num">{{ carro.get_total_price }} €</td>
</tr> 

Remplázalas con el siguiente código:

PracticaDjango/Tienda/templates/Tienda/carro_detalle.html

#...
 {% if carro.cupon %}
            <tr class="subtotal">
                <td>Subtotal</td>
                <td colspan="4"></td>
                <td class="num">{{ carro.get_total_price|floatformat:2 }} €</td>
            </tr>
            <tr>
                <td>
                    "{{ carro.cupon.codigo }}" cupón
                    ({{ carro.cupon.descuento}}% descuento)
                </td>
                <td colspan="4"></td>
                <td class="num neg">
                    - {{ carro.conseguir_descuento|floatformat:2 }} €
                </td>
            </tr>
            {% endif %}
            <tr class="Total">
                <td>Total</td>
                <td colspan="4"></td>
                <td class="num">
                    {{ carro.precio_total_despues_de_descuento|floatformat:2 }} €
                </td>
            </tr>

Este es el código para mostrar el cupón si existe y el descuento asociado al mismo. Si el carro contiene un cupón se muestra la primera fila, incluyendo la cantidad total del carro y el subtotal. En una segunda fila se muestra el descuento aplicado. Finalmente mostramos el precio total, incluyendo el descuento llamando al método precio_total_despues_de_descuento del objeto carro.

En este mismo archivo incluye el siguiente código, que nos servirá para poder introducir el código, después de la etiqueta html </table>:

PracticaDjango/Tienda/templates/Tienda/carro_detalle.html

#...
 </table>
    <div class="container">
        <div class="row">
            <div class="col-md-6">
                <p>¿Tienes un cupón?:</p>
                <form action="{% url 'cupones:aplicar' %}" method="post">
                    {{ formulario_cupon }}
                    <input type="submit" value="Aplicar" class="btn btn-primary">
                    {% csrf_token %}
                </form>
            </div>
            <div class="col-md-6">
                <div class="d-flex justify-content-end">
                    <p class="text-right">
                        <a href="{% url 'Tienda:tienda' %}" class="btn btn-primary">Continua comprando</a>
                        <a href="{% url 'orders:order_create' %}" class="btn btn-success ml-2">Checkout</a>
                    </p>
                </div>
            </div>
        </div>
    </div>   

Ahora abre el navegador y ve a la dirección http://127.0.0.1:8000/tienda y añade algún producto al carro. Luego pulsa en el carrito para ver su detalle. Deberías ver algo parecido a esto:


carro sin descuento aplicado

En el campo Cupón, introduce el cupón que creamos previamente a través de panel de administración.


dto en el carro de la compra


Ahora añadiremos el cupón al siguiente paso en el proceso de compra. Edita la plantilla Orders/order/create.html de la aplicación Orders y localiza las siguientes líneas:

PracticaDjango/Orders/templates/Orders/order/create.html

<ul>
    {% for item in cart %}
       <li>
          {{ item.cantidad }}x {{ item.producto.nombre }}
          <span>{{ item.precio_total }} €</span>
       </li>
    {% endfor %}                    
</ul>

Remplázalo con el siguiente código:

PracticaDjango/Orders/templates/Orders/order/create.html

<ul>
    {% for item in cart %}
        <li>
           {{ item.cantidad }}x {{ item.producto.nombre }}
           <span>{{ item.precio_total }} €</span>
        </li>
    {% endfor %}
    {% if carro.cupon %}
         <li>
            "{{ carro.cupon.codigo }}" ({{ carro.cupon.descuento }}% descuento)
            <span class="neg">- {{ carro.conseguir_descuento|floatformat:2 }} €</span>
         </li>
     {% endif %}
</ul>

El resumen del pedido debería mostrar el cupón aplicado, si hay alguno. Ahora encuentra la siguiente línea, que esta justo debajo:

 <p>Total: {{ cart.get_total_price }} €</p>

Y cámbialo por la siguiente:

<p>Total: {{ carro.precio_total_despues_de_descuento|floatformat:2 }} €</p>

Con esto el precio total también tendrá en cuenta el descuento aplicado. Si tienes algún articulo comprado en el carro, le has aplicado un cupón y vas a la página http://127.0.0.1:8000/orders/create/ deberías ver algo parecido a esto:


dto aplicado en el pedido

Con esto los usuarios ya pueden aplicar los descuentos a los productos. Sin embargo aún nos queda aplicar esto a la finalización de los pedidos y a la orden de pago.


Aplicando los cupones a los pedidos.

Vamos a guardar el cupón que se ha aplicado a cada pedido. Lo primero que necesitamos es modificar el modelo Order para guardar el objeto Cupón, si es que hay alguno.  Para ello edita el archivo models.py de la aplicación Orders y añade las siguientes importaciones:

PracticaBlog/PracticaDjango/Orders/models.py

#...
from decimal import Decimal
from django.core.validators import MinValueValidator, MaxValueValidator
from Cupones.models import Cupon

Luego añade los siguientes campos al modelo:

PracticaBlog/PracticaDjango/Orders/models.py

#...
class Order(models.Model):
    #...
    cupon = models.ForeignKey(
        Cupon, related_name='orders', null=True, blank=True, on_delete=models.SET_NULL)
    descuento = models.IntegerField(
        default=0, validators=[MinValueValidator(0), MaxValueValidator(100)])
Estos campos nos permitirán guardar un posible cupón para el pedido y el porcentaje de descuento que se aplica con el cupón. El descuento está guardado es su respectivo objeto Cupon, pero podemos incluirlo en el modelo de Pedidos para prevenir si el cupón ha sido borrado o modificado. Hemos usado para el cupón on_delete = models.SET_NULL para que si borramos el cupón, este campo se establezca como NULL, pero preservemos el descuento. Como siempre que modificamos el archivo models.py hay que hacer las correspondientes migraciones:

- python manage.py makemigrations
- python manage.py migrate

Ahora edita de nuevo el archivo models.py y añade dos nuevos métodos:

coste_total_antes_del_descuento y aplicar_descuento

y modifica get_total_cost

PracticaDjango/Orders/models.py

#...
def __str__(self):
        return f"Orden {self.id}"
    
    def coste_total_antes_del_descuento(self):
        return sum(item.get_cost() for item in self.items.all())

    def aplicar_descuento(self):
        coste_total = self.coste_total_antes_del_descuento()
        if self.descuento:
            return coste_total * (self.descuento / Decimal(100)) 

    def get_total_cost(self):
        coste_total = self.coste_total_antes_del_descuento()
        return coste_total - self.aplicar_descuento()        

    def get_stripe_url(self):
#...

Ahora get_total_cost reflejará o tendrá en cuenta el descuento que se ha aplicado si es que hay alguno.

Edita el archivo views.py de la aplicación Orders y modifica la vista order_create para grabar el cupón y el descuento al procesar una nueva orden o pedido. Añade el siguiente código resaltado en la vista:

PracticaDjango/Orders/views.py

#...
def order_create(request):
    carro = Carro(request)
    if request.method == "POST":
        form = OrderCreateForm(request.POST)
        if form.is_valid():
            order = form.save(commit=False)
            if carro.cupon:
                order.cupon = carro.cupon
                order.descuento = carro.cupon.descuento
                order.save()
            for item in carro:
                OrderItem.objects.create(
                    order=order,
                    product=item["producto"],
                    price=item["precio"],
                    quantity=item["cantidad"],
                )
            # Limpia el carro
            carro.clear()
            # launch asynchronous task
            # order_created.delay(order.id)
            # return render(request, "Orders/order/created.html", {"order": order})
            order_created.delay(order.id)
            # guarda el pedido en la sesión
            request.session['order_id'] = order.id
            # redirecciona para hacer el pago
            return redirect(reverse('payment:process'))
    else:
        form = OrderCreateForm()
    return render(request, "Orders/order/create.html", {"cart": carro, "form": form})

En el nuevo código, creas un objeto de Pedido utilizando el método save() del formulario OrderCreateForm. Evitas guardarlo en la base de datos por el momento utilizando commit=False. Si el carrito contiene un cupón, almacenas el cupón relacionado y el descuento que se aplicó. Luego, guardas el objeto de pedido en la base de datos. 

Edita la plantilla payment/process.html de la aplicación de pagos y localiza las siguientes líneas:

            <tr>
                <td>Total</td>
                <td colspan="3"></td>
                <td class="num">{{ order.get_total_cost }} €</td>
            </tr>

Remplázalo con el siguiente código. Los elementos nuevos están resaltados en azul.

PracticaDjango/Payment/templates/Payment/process.html

            {% if order.cupon %}
            <tr class="subtotal">
                <td>Subtotal</td>
                <td colspan="3"></td>
                <td class="num">
                    {{ order.coste_total_antes_del_descuento|floatformat:2 }} €
                </td>
            </tr>
            <tr>
                <td>
                    "{{ order.cupon.codigo }}" cupón
                    ({{ order.descuento }}% descuento)
                </td>
                <td colspan="3"></td>
                <td class="num neg">
                    - {{ order.aplicar_descuento|floatformat:2 }} €
                </td>
            </tr>
            {% endif %}
            <tr>
                <td>Total</td>
                <td colspan="3"></td>
                <td class="num">{{ order.get_total_cost|floatformat:2 }} €</td>
            </tr>

Con esto hemos actualizado el resumen del pedido antes de realizar el pago. Ejecuta el servidor y carga todas las instrucciones necesarias para ejecutar el pago (puedes encontrarlas si no las recuerdas en el archivo start_app.py en github). Si realizas una compra verás algo similar a esto en el resumen del pedido:


resumen del pedido con descuento aplicado

Sin embargo, si haces clic en pagar, verás que la página de la pasarela de pago, no recoge el descuento, porque no se lo hemos pasado. 

pasarela de pago no recoge el descuento



Vamos a ello.


Creación de cupones para Stripe Checkout

Stripe te permite definir cupones de descuento y vincularlos a pagos únicos. Puedes encontrar más información sobre la creación de descuentos para Stripe Checkout en https://stripe.com/docs/payments/checkout/discounts.

Editemos la vista payment_process para crear un cupón para Stripe Checkout. Edita el archivo views.py de la aplicación de pagos y agrega el siguiente código resaltado en negrita a la vista payment_process:


PracticaDjango/Payment/views.py

#...
def payment_process(request):
    order_id = request.session.get('order_id', None)
    order = get_object_or_404(Order, id=order_id)

    if request.method == 'POST':
        success_url = request.build_absolute_uri(reverse('payment:completed'))
        cancel_url = request.build_absolute_uri(reverse('payment:canceled'))

        # Stripe checkout session data
        session_data = {
            'mode': 'payment',
            'client_reference_id': order.id,
            'success_url': success_url,
            'cancel_url': cancel_url,
            'line_items': []
        }
        # add order items to the Stripe checkout session
        for item in order.items.all():
            session_data['line_items'].append({
                'price_data': {
                    'unit_amount': int(item.price * Decimal('100')),
                    'currency': 'eur',
                    'product_data': {
                        'name': item.product.nombre,
                    },
                },
                'quantity': item.quantity,
            })
        # Stripe coupon
        if order.cupon:
            stripe_coupon = stripe.Coupon.create(
                name=order.cupon.codigo, percent_off=order.descuento, duration='once')
            session_data['discounts'] = [{'coupon': stripe_coupon.id}]

        # create Stripe checkout session
        session = stripe.checkout.Session.create(**session_data)
        # redirect to Stripe payment form
        return redirect(session.url, code=303)
    else:
        return render(request, 'Payment/process.html', locals())
#...

En el nuevo código, verificas si el pedido tiene un cupón asociado. En ese caso, utilizas el SDK de Stripe para crear un cupón de Stripe utilizando stripe.Coupon.create(). Utilizas los siguientes atributos para el cupón:

- name: Se utiliza el código del cupón relacionado con el objeto de pedido.

- percent_off: Se emite el descuento del objeto de pedido.

- duration: Se utiliza el valor "once". Esto indica a Stripe que este es un cupón para un pago único.

Después de crear el cupón, su ID se agrega al diccionario session_data utilizado para crear la sesión de Stripe Checkout. Esto vincula el cupón a la sesión de pago.


Abre http://127.0.0.1:8000/ en tu navegador y completa una compra utilizando el cupón que creaste. Cuando te redirijan a la página de Stripe Checkout, verás que se aplica el cupón.


dto aplicado en la pasarela de pago

Ahora completa el pago y ve al panel de administración. Si compruebas ese pedido verás que aparece el cupón y el descuento aplicado.


cupón y descuento en el panel de admon


Hemos logrado almacenar cupones para pedidos y procesar pagos con descuentos con éxito. A continuación, agregaremos los cupones a la vista de detalle del pedido en el sitio de administración y a las facturas PDF de los pedidos.


Agregar cupones a los pedidos en el sitio de administración y a las facturas PDF

Vamos a añadir el cupón a la página de detalle del pedido en el sitio de administración. Edita la plantilla admin/orders/order/detail.html de la aplicación de pedidos y agrega el siguiente código resaltado en negrita:

PracticaDjango/Orders/templates/admin/Orders/order/detail.html

 <tbody>
            {% for item in order.items.all %}
            <tr class="row{% cycle '1' '2' %}">
                <td>{{ item.product.nombre }}</td>
                <td class="num">{{ item.price }} €</td>
                <td class="num">{{ item.quantity }}</td>
                <td class="num">{{ item.get_cost }} €</td>
            </tr>
            {% endfor %}

            {% if order.cupon %}
            <tr class="subtotal">
                <td colspan="3">Subtotal</td>
                <td class="num">
                    {{ order.coste_total_antes_del_descuento|floatformat:2 }} €
                </td>
            </tr>
            <tr>
                <td colspan="3">
                    "{{ order.cupon.codigo }}" cupón
                    ({{ order.descuento }}% descuento)
                </td>
                <td class="num neg">
                    - {{ order.aplicar_descuento|floatformat:2 }} €
                </td>
            </tr>
            {% endif %}
            <tr class="total">
                <td colspan="3">Total</td>
                <td class="num">{{ order.get_total_cost }} €</td>
            </tr>
        </tbody>        

Ve a la dirección http://127.0.0.1:8000/admin/Orders/order/ y haz clic en el link view que está al final a la derecha y que nos muestra el detalle del último pedido pedido (order detail). 

vista del detalle del pedido


En la compra realizada ahora si aparece el cupón y el descuento aplicado de forma similar a como se ve en la imagen


resumen compra realizada con descuento

Ahora modificaremos la plantilla de la factura para que también refleje el descuento. Edita la plantilla Orders/order/pdf.html y añade las líneas resaltadas en azul:

PracticaDjango/Orders/templates/admin/Orders/order/pdf.html

<tbody>
            {% for item in order.items.all %}
            <tr class="row{% cycle '1' '2' %}">
                <td>{{ item.product.nombre }}</td>
                <td class="num">{{ item.price }} €</td>
                <td class="num">{{ item.quantity }}</td>
                <td class="num">{{ item.get_cost }} €</td>
            </tr>
            {% endfor %}

            {% if order.cupon %}
            <tr class="subtotal">
                <td colspan="3">Subtotal</td>
                <td class="num">
                    {{ order.coste_total_antes_del_descuento|floatformat:2 }} €
                </td>
            </tr>
            <tr>
                <td colspan="3">
                    "{{ order.cupon.codigo }}" cupon
                    ({{ order.descuento }}% descuento)
                </td>
                <td class="num neg">
                    - {{ order.aplicar_descuento|floatformat:2 }} €
                </td>
            </tr>
            {% endif %}

            <tr class="total">
                <td colspan="3">Total</td>
                <td class="num">{{ order.get_total_cost|floatformat:2}} €</td>
            </tr>
        </tbody>
    </table>   


Ve a la dirección http://127.0.0.1:8000/admin/Orders/order/ y haz click en el link PDF que está al final a la derecha y que nos muestra la factura en PDF.


fra con descuento

Código en GITHUB del capítulo.

No hay comentarios:

Publicar un comentario