feat: Add user purchase and receipt management
- Implemented 'Mis Compras' and 'Mis Recibos' pages for users to view their orders and payment receipts. - Enhanced address validation in 'editar_direccion.html' to ensure cities and postal codes belong to Almería. - Added shipping address display in seller order details on 'pedidos_vendedor.html'. - Updated user portal to include links to purchases and receipts. - Introduced email verification functionality during user registration. - Refactored email sending utility for better error handling and logging. - Improved session management for checkout processes with selected shipping addresses.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
{% load static %}
|
||||
{% load cache %}
|
||||
{% load compress %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
@@ -69,6 +70,7 @@
|
||||
{% block head %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
{% cache 500 sidebar request.user.username %}
|
||||
<nav class="navbar navbar-expand-md header">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="{% url 'home' %}">
|
||||
@@ -102,7 +104,7 @@
|
||||
|
||||
{% if user.is_authenticated %}
|
||||
<a href="{% url 'mis_productos' %}" class="nav-link btn btn-outline-secondary btn-sm">Panel Vendedor</a>
|
||||
<span class="nav-text d-none d-md-inline text-white">{{ user.first_name|default:user.username }}</span>
|
||||
<a href="{% url 'portal_usuario' %}" class="nav-link btn btn-outline-light btn-sm">{{ user.first_name|default:user.username }}</a>
|
||||
<a href="{% url 'logout' %}" class="nav-link btn btn-primary btn-sm">Cerrar Sesión</a>
|
||||
{% else %}
|
||||
<a href="{% url 'login' %}" class="nav-link btn btn-primary btn-sm">Iniciar Sesión</a>
|
||||
@@ -112,6 +114,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
{% endcache %}
|
||||
|
||||
<div class="container-fluid">
|
||||
<!-- Mensajes -->
|
||||
@@ -131,30 +134,31 @@
|
||||
<!-- Contenido-->
|
||||
{% block content %}{% endblock %}
|
||||
|
||||
{% cache 500 footer %}
|
||||
<!-- Footer-->
|
||||
<div id="footer" class="row pt-2 pb-2 mt-5">
|
||||
<div class="col-md-12 grid">
|
||||
<p class="text-center">Enlace 1</p>
|
||||
<p class="text-center">Enlace 2</p>
|
||||
<p class="text-center">Enlace 3</p>
|
||||
<p class="text-center">Enlace 4</p>
|
||||
<p class="text-center">Enlace 5</p>
|
||||
<p class="text-center">Enlace 6</p>
|
||||
<p class="text-center">Enlace 7</p>
|
||||
<p class="text-center">Enlace 8</p>
|
||||
<p class="text-center">Enlace 9</p>
|
||||
<p class="text-center">Enlace 10</p>
|
||||
<p class="text-center">Enlace 11</p>
|
||||
<p class="text-center">Enlace 12</p>
|
||||
<p class="text-center">Enlace 13</p>
|
||||
<p class="text-center">Enlace 14</p>
|
||||
<p class="text-center">Enlace 15</p>
|
||||
<p class="text-center">Enlace 16</p>
|
||||
</div>
|
||||
<div class="col-md-12 grid">
|
||||
<p class="text-center">Enlace 1</p>
|
||||
<p class="text-center">Enlace 2</p>
|
||||
<p class="text-center">Enlace 3</p>
|
||||
<p class="text-center">Enlace 4</p>
|
||||
<p class="text-center">Enlace 5</p>
|
||||
<p class="text-center">Enlace 6</p>
|
||||
<p class="text-center">Enlace 7</p>
|
||||
<p class="text-center">Enlace 8</p>
|
||||
<p class="text-center">Enlace 9</p>
|
||||
<p class="text-center">Enlace 10</p>
|
||||
<p class="text-center">Enlace 11</p>
|
||||
<p class="text-center">Enlace 12</p>
|
||||
<p class="text-center">Enlace 13</p>
|
||||
<p class="text-center">Enlace 14</p>
|
||||
<p class="text-center">Enlace 15</p>
|
||||
<p class="text-center">Enlace 16</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endcache %}
|
||||
</div>
|
||||
|
||||
{% cache 500 scripts %}
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js" integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
@@ -243,5 +247,6 @@
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endcache %}
|
||||
</body>
|
||||
</html>
|
||||
@@ -49,6 +49,30 @@
|
||||
</div>
|
||||
|
||||
{% if cart_items %}
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title mb-3">1) Selecciona la dirección de envío</h5>
|
||||
{% if addresses %}
|
||||
<div class="mb-3">
|
||||
<label for="shipping-address" class="form-label">Dirección</label>
|
||||
<select id="shipping-address" class="form-select" required>
|
||||
<option value="">Selecciona una dirección...</option>
|
||||
{% for address in addresses %}
|
||||
<option value="{{ address.id }}" {% if address.is_default %}selected{% endif %}>
|
||||
{{ address.full_name }} - {{ address.address_line_1 }}, {{ address.postal_code }} {{ address.city }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-warning mb-0 d-flex justify-content-between align-items-center flex-wrap gap-2">
|
||||
<span>No tienes direcciones de envío creadas.</span>
|
||||
<a href="{% url 'crear_direccion' %}" class="btn btn-primary btn-sm">Crear dirección</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive mb-4">
|
||||
<table class="table table-striped align-middle">
|
||||
<thead>
|
||||
@@ -87,20 +111,22 @@
|
||||
</div>
|
||||
|
||||
<div class="payment-section">
|
||||
<h3>Selecciona tu método de pago</h3>
|
||||
<h3>2) Selecciona tu método de pago</h3>
|
||||
<div class="payment-methods">
|
||||
<button
|
||||
id="checkout-button"
|
||||
class="btn btn-primary payment-btn"
|
||||
data-config-url="/tienda/config/"
|
||||
data-session-url="/tienda/create-checkout-session/">
|
||||
data-session-url="/tienda/create-checkout-session/"
|
||||
{% if not addresses %}disabled{% endif %}>
|
||||
💳 Pagar con Stripe
|
||||
</button>
|
||||
|
||||
<button
|
||||
id="paypal-button"
|
||||
class="btn btn-warning payment-btn"
|
||||
data-payment-url="{% url 'create_paypal_payment' %}">
|
||||
data-payment-url="{% url 'create_paypal_payment' %}"
|
||||
{% if not addresses %}disabled{% endif %}>
|
||||
🅿️ Pagar con PayPal
|
||||
</button>
|
||||
</div>
|
||||
@@ -115,6 +141,13 @@
|
||||
// Manejo del botón de PayPal
|
||||
document.getElementById('paypal-button').addEventListener('click', async function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const shippingAddressSelect = document.getElementById('shipping-address');
|
||||
const selectedShippingAddress = shippingAddressSelect ? shippingAddressSelect.value : '';
|
||||
if (!selectedShippingAddress) {
|
||||
alert('Selecciona una dirección de envío para continuar.');
|
||||
return;
|
||||
}
|
||||
|
||||
const button = this;
|
||||
const originalText = button.innerHTML;
|
||||
@@ -138,7 +171,8 @@
|
||||
headers: {
|
||||
'X-CSRFToken': csrfToken || '',
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: JSON.stringify({ shipping_address_id: selectedShippingAddress })
|
||||
});
|
||||
|
||||
console.log('Response status:', response.status);
|
||||
|
||||
@@ -35,17 +35,27 @@
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="city" class="form-label">Ciudad *</label>
|
||||
<input type="text" class="form-control" id="city" name="city" value="{{ direccion.city|default:'' }}" required>
|
||||
<label for="city" class="form-label">Ciudad/Pueblo (Almería) *</label>
|
||||
<input type="text" class="form-control" id="city" name="city" value="{{ direccion.city|default:'' }}" list="almeria-towns" autocomplete="off" required>
|
||||
<datalist id="almeria-towns">
|
||||
{% for town in almeria_municipalities %}
|
||||
<option value="{{ town }}"></option>
|
||||
{% endfor %}
|
||||
</datalist>
|
||||
<div class="form-text">Selecciona o escribe un municipio de la provincia de Almería.</div>
|
||||
<div class="invalid-feedback" id="city-validation-message">
|
||||
El pueblo/ciudad debe pertenecer a la provincia de Almería.
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="postal_code" class="form-label">Código Postal *</label>
|
||||
<input type="text" class="form-control" id="postal_code" name="postal_code" value="{{ direccion.postal_code|default:'' }}" required>
|
||||
<input type="text" class="form-control" id="postal_code" name="postal_code" value="{{ direccion.postal_code|default:'' }}" pattern="04[0-9]{3}" maxlength="5" placeholder="04XXX" required>
|
||||
<div class="form-text">Solo aceptamos códigos postales de Almería (04xxx).</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="country" class="form-label">País *</label>
|
||||
<input type="text" class="form-control" id="country" name="country" value="{{ direccion.country|default:'España' }}" required>
|
||||
<input type="text" class="form-control" id="country" name="country" value="España" readonly>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="phone" class="form-label">Teléfono *</label>
|
||||
@@ -67,4 +77,64 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
const cityInput = document.getElementById('city');
|
||||
const cityValidationMessage = document.getElementById('city-validation-message');
|
||||
const form = cityInput ? cityInput.form : null;
|
||||
|
||||
if (!cityInput || !form) {
|
||||
return;
|
||||
}
|
||||
|
||||
const almeriaTowns = new Set([
|
||||
{% for town in almeria_municipalities %}
|
||||
"{{ town|escapejs }}",
|
||||
{% endfor %}
|
||||
].map(normalizeTown));
|
||||
|
||||
function normalizeTown(value) {
|
||||
return (value || '')
|
||||
.normalize('NFD')
|
||||
.replace(/[\u0300-\u036f]/g, '')
|
||||
.replace(/[^a-zA-Z0-9\s-]/g, '')
|
||||
.replace(/-/g, ' ')
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
.replace(/\s+/g, ' ')
|
||||
.replace(/^(la|los)\s+/, '');
|
||||
}
|
||||
|
||||
function validateTown() {
|
||||
const normalized = normalizeTown(cityInput.value);
|
||||
|
||||
if (!normalized) {
|
||||
cityInput.setCustomValidity('');
|
||||
cityInput.classList.remove('is-invalid');
|
||||
return;
|
||||
}
|
||||
|
||||
const isValid = almeriaTowns.has(normalized);
|
||||
|
||||
if (isValid) {
|
||||
cityInput.setCustomValidity('');
|
||||
cityInput.classList.remove('is-invalid');
|
||||
} else {
|
||||
cityInput.setCustomValidity('El pueblo/ciudad debe pertenecer a la provincia de Almería.');
|
||||
cityInput.classList.add('is-invalid');
|
||||
}
|
||||
|
||||
cityValidationMessage.textContent = cityInput.validationMessage || 'El pueblo/ciudad debe pertenecer a la provincia de Almería.';
|
||||
}
|
||||
|
||||
cityInput.addEventListener('input', validateTown);
|
||||
cityInput.addEventListener('blur', validateTown);
|
||||
form.addEventListener('submit', function () {
|
||||
validateTown();
|
||||
});
|
||||
|
||||
validateTown();
|
||||
})();
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
{% extends "tienda/base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<h2>Mis Compras</h2>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{% url 'portal_usuario' %}">Portal de Usuario</a></li>
|
||||
<li class="breadcrumb-item active">Compras</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-3">
|
||||
<div class="col-12">
|
||||
<div class="btn-group" role="group">
|
||||
<a href="{% url 'portal_usuario' %}" class="btn btn-outline-primary">Inicio</a>
|
||||
<a href="{% url 'mis_compras' %}" class="btn btn-primary">Compras</a>
|
||||
<a href="{% url 'mis_recibos' %}" class="btn btn-outline-primary">Recibos</a>
|
||||
<a href="{% url 'mensajes_comprador' %}" class="btn btn-outline-primary">Mensajes</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<p class="text-muted">Total de compras: <strong>{{ total_orders }}</strong></p>
|
||||
{% if orders %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Pedido #</th>
|
||||
<th>Fecha</th>
|
||||
<th>Total</th>
|
||||
<th>Estado</th>
|
||||
<th>Método</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for order in orders %}
|
||||
<tr>
|
||||
<td>{{ order.id }}</td>
|
||||
<td>{{ order.created_at|date:"d/m/Y H:i" }}</td>
|
||||
<td>{{ order.total }}€</td>
|
||||
<td><span class="badge bg-success">{{ order.get_status_display }}</span></td>
|
||||
<td>{{ order.get_payment_method_display }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-info mb-0">
|
||||
Aún no has realizado compras.
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,63 @@
|
||||
{% extends "tienda/base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<h2>Mis Recibos</h2>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{% url 'portal_usuario' %}">Portal de Usuario</a></li>
|
||||
<li class="breadcrumb-item active">Recibos</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-3">
|
||||
<div class="col-12">
|
||||
<div class="btn-group" role="group">
|
||||
<a href="{% url 'portal_usuario' %}" class="btn btn-outline-primary">Inicio</a>
|
||||
<a href="{% url 'mis_compras' %}" class="btn btn-outline-primary">Compras</a>
|
||||
<a href="{% url 'mis_recibos' %}" class="btn btn-primary">Recibos</a>
|
||||
<a href="{% url 'mensajes_comprador' %}" class="btn btn-outline-primary">Mensajes</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<p class="text-muted">Total de recibos: <strong>{{ total_receipts }}</strong></p>
|
||||
{% if receipts %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Recibo #</th>
|
||||
<th>Fecha</th>
|
||||
<th>Total</th>
|
||||
<th>Método</th>
|
||||
<th>Referencia</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for receipt in receipts %}
|
||||
<tr>
|
||||
<td>{{ receipt.id }}</td>
|
||||
<td>{{ receipt.created_at|date:"d/m/Y H:i" }}</td>
|
||||
<td>{{ receipt.total }}€</td>
|
||||
<td>{{ receipt.get_payment_method_display }}</td>
|
||||
<td>{{ receipt.payment_reference|default:"-" }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-info mb-0">
|
||||
No tienes recibos disponibles todavía.
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -43,6 +43,22 @@
|
||||
<li><strong>Precio total:</strong> {{ item.total_price }}€</li>
|
||||
<li><strong>Fecha:</strong> {{ item.created_at|date:"d/m/Y H:i" }}</li>
|
||||
</ul>
|
||||
<h6 class="mt-3">Dirección de envío</h6>
|
||||
{% if item.order.shipping_address %}
|
||||
<ul class="list-unstyled">
|
||||
<li><strong>Destinatario:</strong> {{ item.order.shipping_address.full_name }}</li>
|
||||
<li><strong>Dirección:</strong> {{ item.order.shipping_address.address_line_1 }}</li>
|
||||
{% if item.order.shipping_address.address_line_2 %}
|
||||
<li><strong>Detalle:</strong> {{ item.order.shipping_address.address_line_2 }}</li>
|
||||
{% endif %}
|
||||
<li><strong>Ciudad:</strong> {{ item.order.shipping_address.city }}</li>
|
||||
<li><strong>Código Postal:</strong> {{ item.order.shipping_address.postal_code }}</li>
|
||||
<li><strong>País:</strong> {{ item.order.shipping_address.country }}</li>
|
||||
<li><strong>Teléfono:</strong> {{ item.order.shipping_address.phone }}</li>
|
||||
</ul>
|
||||
{% else %}
|
||||
<p class="text-muted mb-0">Dirección no disponible.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h6>Cambiar Estado</h6>
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
<div class="col-12">
|
||||
<div class="btn-group" role="group">
|
||||
<a href="{% url 'portal_usuario' %}" class="btn btn-primary">Inicio</a>
|
||||
<a href="{% url 'mis_compras' %}" class="btn btn-outline-primary">Compras</a>
|
||||
<a href="{% url 'mis_recibos' %}" class="btn btn-outline-primary">Recibos</a>
|
||||
<a href="{% url 'editar_perfil' %}" class="btn btn-outline-primary">Mi Perfil</a>
|
||||
<a href="{% url 'direcciones_usuario' %}" class="btn btn-outline-primary">Direcciones</a>
|
||||
<a href="{% url 'mensajes_comprador' %}" class="btn btn-outline-primary">Mensajes</a>
|
||||
@@ -29,6 +31,7 @@
|
||||
<h5 class="card-title">📦 Mis Pedidos</h5>
|
||||
<p class="display-4">{{ total_orders }}</p>
|
||||
<p class="text-muted">pedidos realizados</p>
|
||||
<a href="{% url 'mis_compras' %}" class="btn btn-sm btn-primary">Ver compras</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -54,6 +57,18 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-2">
|
||||
<div class="col-md-4 mb-3">
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<h5 class="card-title">🧾 Recibos</h5>
|
||||
<p class="text-muted">consulta tus recibos de pago</p>
|
||||
<a href="{% url 'mis_recibos' %}" class="btn btn-sm btn-primary">Ver recibos</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pedidos recientes -->
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
|
||||
Reference in New Issue
Block a user