From a3eae6358777af5de66d17a94acda03b432bbeff Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 9 Apr 2026 08:51:12 +0200 Subject: [PATCH] feat: Add transaction code generation for orders and update receipt templates --- .../migrations/0003_order_transaction_code.py | 41 +++++++++++++++++++ tienda/pdf.py | 3 +- tienda/tasks.py | 10 ++++- tienda/templates/tienda/checkout_success.html | 3 ++ tienda/templates/tienda/mis_recibos.html | 2 + tienda/vars.py | 5 +++ tienda/views.py | 22 ++++++---- 7 files changed, 76 insertions(+), 10 deletions(-) create mode 100644 tienda/migrations/0003_order_transaction_code.py diff --git a/tienda/migrations/0003_order_transaction_code.py b/tienda/migrations/0003_order_transaction_code.py new file mode 100644 index 0000000..d1b91d7 --- /dev/null +++ b/tienda/migrations/0003_order_transaction_code.py @@ -0,0 +1,41 @@ +# Generated by Django 6.0.1 on 2026-04-09 + +from django.db import migrations, models +from django.utils.crypto import get_random_string + + +TRANSACTION_CODE_PREFIX = "comal-" +TRANSACTION_CODE_LENGTH = 32 +TRANSACTION_CODE_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + + +def populate_transaction_codes(apps, schema_editor): + Order = apps.get_model("tienda", "Order") + + for order in Order.objects.filter(transaction_code__isnull=True): + while True: + code = f"{TRANSACTION_CODE_PREFIX}{get_random_string(TRANSACTION_CODE_LENGTH, TRANSACTION_CODE_ALPHABET)}" + if not Order.objects.filter(transaction_code=code).exists(): + order.transaction_code = code + order.save(update_fields=["transaction_code"]) + break + + +def noop_reverse(apps, schema_editor): + pass + + +class Migration(migrations.Migration): + + dependencies = [ + ("tienda", "0002_verificationcode_code_mode_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="order", + name="transaction_code", + field=models.CharField(blank=True, db_index=True, max_length=38, null=True, unique=True), + ), + migrations.RunPython(populate_transaction_codes, noop_reverse), + ] diff --git a/tienda/pdf.py b/tienda/pdf.py index 26cfed6..c805a78 100644 --- a/tienda/pdf.py +++ b/tienda/pdf.py @@ -15,7 +15,7 @@ class Recibo(FPDF): self.set_font('Arial', 'I', 8) self.cell(0, 10, f'Pagina {self.page_no()}', align='C') -def generar_recibo(cliente: str, total: float, objetos: list, metodo_pago: str): +def generar_recibo(cliente: str, total: float, objetos: list, metodo_pago: str, transaction_code: str): pdf = Recibo() font_path = "/fonts/Roboto-Regular.ttf" pdf.add_font('Roboto', '', '/fonts/Roboto-Regular.ttf') @@ -24,6 +24,7 @@ def generar_recibo(cliente: str, total: float, objetos: list, metodo_pago: str): pdf.set_font('Roboto', size=12) pdf.cell(0, 10, f"Cliente: {cliente}", ln=True) + pdf.cell(0, 10, f"ID de transaccion: {transaction_code}", ln=True) pdf.cell(0, 10, f"") DATA = [] diff --git a/tienda/tasks.py b/tienda/tasks.py index 4d9f9d3..a547100 100644 --- a/tienda/tasks.py +++ b/tienda/tasks.py @@ -57,12 +57,18 @@ def enviar_correo_recuperacion(email: str): # Purchased items should be a list of dictionary, the dictionary must follow this tags: amount, product name, price (each) @shared_task -def process_purchase(user_id: int, purchased_items: list, payment_method: str): +def process_purchase(user_id: int, purchased_items: list, payment_method: str, transaction_code: str): user = User.objects.get(id=user_id) total = 0 for i in purchased_items: total += i["price"]*i["amount"] - pdf_data = pdf.generar_recibo(user.get_full_name(), total, purchased_items, payment_method) + pdf_data = pdf.generar_recibo( + user.get_full_name(), + total, + purchased_items, + payment_method, + transaction_code, + ) email = EmailMessage( subject="Tu recibo de compra", diff --git a/tienda/templates/tienda/checkout_success.html b/tienda/templates/tienda/checkout_success.html index c445c8e..07159ac 100644 --- a/tienda/templates/tienda/checkout_success.html +++ b/tienda/templates/tienda/checkout_success.html @@ -7,6 +7,9 @@

¡Pago completado!

Tu pedido ha sido procesado correctamente.

+ {% if order and order.transaction_code %} +

ID de transacción: {{ order.transaction_code }}

+ {% endif %} Volver a la tienda
diff --git a/tienda/templates/tienda/mis_recibos.html b/tienda/templates/tienda/mis_recibos.html index 49426d0..3c87fcb 100644 --- a/tienda/templates/tienda/mis_recibos.html +++ b/tienda/templates/tienda/mis_recibos.html @@ -34,6 +34,7 @@ Recibo # + ID Transacción Fecha Total Método @@ -44,6 +45,7 @@ {% for receipt in receipts %} {{ receipt.id }} + {{ receipt.transaction_code|default:"-" }} {{ receipt.created_at|date:"d/m/Y H:i" }} {{ receipt.total }}€ {{ receipt.get_payment_method_display }} diff --git a/tienda/vars.py b/tienda/vars.py index 3fc1273..c7e4b1f 100644 --- a/tienda/vars.py +++ b/tienda/vars.py @@ -1,5 +1,10 @@ PAGE_SIZE = 20 VAT_RATE = 0.21 # IVA 21% +STOCK_RESERVATION_MINUTES = 5 + +TRANSACTION_CODE_PREFIX = "comal-" +TRANSACTION_CODE_LENGTH = 32 +TRANSACTION_CODE_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" # Restricciones de envío SHIPPING_COUNTRY = "España" diff --git a/tienda/views.py b/tienda/views.py index 63d29d3..0f64af2 100644 --- a/tienda/views.py +++ b/tienda/views.py @@ -4,19 +4,22 @@ from django.contrib.auth import authenticate, login as auth_login, logout as aut from django.contrib.auth.decorators import login_required from django.contrib import messages -from .models import User, Product, Category, Cart, CartItem, Image, Order, OrderItem, OrderMessage, ShippingAddress, VerificationCode +from .models import User, Product, Category, Cart, CartItem, Image, Order, OrderItem, OrderMessage, ShippingAddress, StockReservation, StockReservationItem, VerificationCode from . import tasks from .vars import ( PAGE_SIZE, VAT_RATE, SHIPPING_COUNTRY, ALMERIA_POSTAL_CODE_PREFIX, - ALMERIA_MUNICIPALITIES_DISPLAY + ALMERIA_MUNICIPALITIES_DISPLAY, + STOCK_RESERVATION_MINUTES, ) from django.conf import settings from django.views.decorators.csrf import csrf_exempt from django.urls import reverse +from django.utils import timezone from decimal import Decimal, ROUND_HALF_UP +from datetime import timedelta import stripe from django.db import models, transaction from django.core.cache import cache @@ -373,7 +376,12 @@ def create_order_from_cart(request, payment_method, payment_reference="", shippi cart.items.all().delete() if request.user.is_authenticated and purchased_items: - tasks.process_purchase.delay(request.user.id, purchased_items, payment_method) + tasks.process_purchase.delay( + request.user.id, + purchased_items, + payment_method, + order.transaction_code, + ) return order @@ -785,13 +793,13 @@ def checkout_success(request: HttpRequest): payment_reference = request.session.get('stripe_session_id', "") shipping_address_id = request.session.get('selected_shipping_address_id') shipping_address = ShippingAddress.objects.filter(id=shipping_address_id, user=request.user).first() - create_order_from_cart(request, Order.PAYMENT_STRIPE, payment_reference, shipping_address) + order = create_order_from_cart(request, Order.PAYMENT_STRIPE, payment_reference, shipping_address) if 'stripe_session_id' in request.session: del request.session['stripe_session_id'] if 'selected_shipping_address_id' in request.session: del request.session['selected_shipping_address_id'] messages.success(request, "Pago realizado correctamente. ¡Gracias por tu compra!") - return render(request, "tienda/checkout_success.html", {}) + return render(request, "tienda/checkout_success.html", {"order": order}) def checkout_cancel(request: HttpRequest): @@ -957,7 +965,7 @@ def paypal_execute(request: HttpRequest): # Pago exitoso - crear pedido y limpiar el carrito shipping_address_id = request.session.get('selected_shipping_address_id') shipping_address = ShippingAddress.objects.filter(id=shipping_address_id, user=request.user).first() - create_order_from_cart(request, Order.PAYMENT_PAYPAL, payment_id, shipping_address) + order = create_order_from_cart(request, Order.PAYMENT_PAYPAL, payment_id, shipping_address) # Limpiar la sesión if 'paypal_payment_id' in request.session: @@ -966,7 +974,7 @@ def paypal_execute(request: HttpRequest): del request.session['selected_shipping_address_id'] messages.success(request, "¡Pago realizado correctamente con PayPal! Gracias por tu compra.") - return render(request, "tienda/checkout_success.html", {}) + return render(request, "tienda/checkout_success.html", {"order": order}) else: error_message = payment.error.get("message", "Error desconocido") messages.error(request, f"Error al procesar el pago: {error_message}")