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}")