feat: Add transaction code generation for orders and update receipt templates
This commit is contained in:
@@ -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
@@ -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
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
@@ -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}")
|
||||||
|
|||||||
Reference in New Issue
Block a user