feat: Add transaction code generation for orders and update receipt templates

This commit is contained in:
2026-04-09 08:51:12 +02:00
parent a570c542c2
commit a3eae63587
7 changed files with 76 additions and 10 deletions
@@ -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),
]
+2 -1
View File
@@ -15,7 +15,7 @@ class Recibo(FPDF):
self.set_font('Arial', 'I', 8) self.set_font('Arial', 'I', 8)
self.cell(0, 10, f'Pagina {self.page_no()}', align='C') 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() pdf = Recibo()
font_path = "/fonts/Roboto-Regular.ttf" font_path = "/fonts/Roboto-Regular.ttf"
pdf.add_font('Roboto', '', '/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.set_font('Roboto', size=12)
pdf.cell(0, 10, f"Cliente: {cliente}", ln=True) 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"") pdf.cell(0, 10, f"")
DATA = [] DATA = []
+8 -2
View File
@@ -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) # Purchased items should be a list of dictionary, the dictionary must follow this tags: amount, product name, price (each)
@shared_task @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) user = User.objects.get(id=user_id)
total = 0 total = 0
for i in purchased_items: for i in purchased_items:
total += i["price"]*i["amount"] 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( email = EmailMessage(
subject="Tu recibo de compra", subject="Tu recibo de compra",
@@ -7,6 +7,9 @@
<div class="alert alert-success p-5"> <div class="alert alert-success p-5">
<h2 class="mb-3">¡Pago completado!</h2> <h2 class="mb-3">¡Pago completado!</h2>
<p class="mb-4">Tu pedido ha sido procesado correctamente.</p> <p class="mb-4">Tu pedido ha sido procesado correctamente.</p>
{% if order and order.transaction_code %}
<p class="mb-4"><strong>ID de transacción:</strong> {{ order.transaction_code }}</p>
{% endif %}
<a href="{% url 'index' %}" class="btn btn-primary">Volver a la tienda</a> <a href="{% url 'index' %}" class="btn btn-primary">Volver a la tienda</a>
</div> </div>
</div> </div>
+2
View File
@@ -34,6 +34,7 @@
<thead> <thead>
<tr> <tr>
<th>Recibo #</th> <th>Recibo #</th>
<th>ID Transacción</th>
<th>Fecha</th> <th>Fecha</th>
<th>Total</th> <th>Total</th>
<th>Método</th> <th>Método</th>
@@ -44,6 +45,7 @@
{% for receipt in receipts %} {% for receipt in receipts %}
<tr> <tr>
<td>{{ receipt.id }}</td> <td>{{ receipt.id }}</td>
<td>{{ receipt.transaction_code|default:"-" }}</td>
<td>{{ receipt.created_at|date:"d/m/Y H:i" }}</td> <td>{{ receipt.created_at|date:"d/m/Y H:i" }}</td>
<td>{{ receipt.total }}€</td> <td>{{ receipt.total }}€</td>
<td>{{ receipt.get_payment_method_display }}</td> <td>{{ receipt.get_payment_method_display }}</td>
+5
View File
@@ -1,5 +1,10 @@
PAGE_SIZE = 20 PAGE_SIZE = 20
VAT_RATE = 0.21 # IVA 21% 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 # Restricciones de envío
SHIPPING_COUNTRY = "España" SHIPPING_COUNTRY = "España"
+15 -7
View File
@@ -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.auth.decorators import login_required
from django.contrib import messages 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 . import tasks
from .vars import ( from .vars import (
PAGE_SIZE, PAGE_SIZE,
VAT_RATE, VAT_RATE,
SHIPPING_COUNTRY, SHIPPING_COUNTRY,
ALMERIA_POSTAL_CODE_PREFIX, ALMERIA_POSTAL_CODE_PREFIX,
ALMERIA_MUNICIPALITIES_DISPLAY ALMERIA_MUNICIPALITIES_DISPLAY,
STOCK_RESERVATION_MINUTES,
) )
from django.conf import settings from django.conf import settings
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django.urls import reverse from django.urls import reverse
from django.utils import timezone
from decimal import Decimal, ROUND_HALF_UP from decimal import Decimal, ROUND_HALF_UP
from datetime import timedelta
import stripe import stripe
from django.db import models, transaction from django.db import models, transaction
from django.core.cache import cache 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() cart.items.all().delete()
if request.user.is_authenticated and purchased_items: 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 return order
@@ -785,13 +793,13 @@ def checkout_success(request: HttpRequest):
payment_reference = request.session.get('stripe_session_id', "") payment_reference = request.session.get('stripe_session_id', "")
shipping_address_id = request.session.get('selected_shipping_address_id') shipping_address_id = request.session.get('selected_shipping_address_id')
shipping_address = ShippingAddress.objects.filter(id=shipping_address_id, user=request.user).first() 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: if 'stripe_session_id' in request.session:
del request.session['stripe_session_id'] del request.session['stripe_session_id']
if 'selected_shipping_address_id' in request.session: if 'selected_shipping_address_id' in request.session:
del request.session['selected_shipping_address_id'] del request.session['selected_shipping_address_id']
messages.success(request, "Pago realizado correctamente. ¡Gracias por tu compra!") 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): def checkout_cancel(request: HttpRequest):
@@ -957,7 +965,7 @@ def paypal_execute(request: HttpRequest):
# Pago exitoso - crear pedido y limpiar el carrito # Pago exitoso - crear pedido y limpiar el carrito
shipping_address_id = request.session.get('selected_shipping_address_id') shipping_address_id = request.session.get('selected_shipping_address_id')
shipping_address = ShippingAddress.objects.filter(id=shipping_address_id, user=request.user).first() 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 # Limpiar la sesión
if 'paypal_payment_id' in request.session: if 'paypal_payment_id' in request.session:
@@ -966,7 +974,7 @@ def paypal_execute(request: HttpRequest):
del request.session['selected_shipping_address_id'] del request.session['selected_shipping_address_id']
messages.success(request, "¡Pago realizado correctamente con PayPal! Gracias por tu compra.") 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: else:
error_message = payment.error.get("message", "Error desconocido") error_message = payment.error.get("message", "Error desconocido")
messages.error(request, f"Error al procesar el pago: {error_message}") messages.error(request, f"Error al procesar el pago: {error_message}")