feat: Add stock management features including stock reservations and updates to cart and checkout processes

This commit is contained in:
2026-04-09 10:03:57 +02:00
parent 618c65accb
commit cbcdb823db
25 changed files with 279 additions and 25 deletions
+21 -2
View File
@@ -12,6 +12,17 @@
{% if cart_items %}
<div class="row mt-4">
<div class="col-md-8">
{% if stock_issues %}
<div class="alert alert-warning">
<strong>Hay productos con stock insuficiente:</strong>
<ul class="mb-0 mt-2">
{% for issue in stock_issues %}
<li>{{ issue.product_name }} - disponible: {{ issue.available }}, en carrito: {{ issue.requested }}</li>
{% endfor %}
</ul>
<div class="mt-2">Actualiza las cantidades antes de continuar al pago.</div>
</div>
{% endif %}
<div class="card">
<div class="card-body">
<table class="table">
@@ -20,6 +31,7 @@
<th>Producto</th>
<th>Precio (sin IVA)</th>
<th>Cantidad</th>
<th>Stock</th>
<th>Subtotal (con IVA)</th>
<th>Acciones</th>
</tr>
@@ -39,10 +51,17 @@
<td>
<form method="post" action="{% url 'update_cart_item' item.id %}" class="d-flex align-items-center" style="max-width: 150px;">
{% csrf_token %}
<input type="number" name="quantity" value="{{ item.quantity }}" min="1" class="form-control form-control-sm me-2" style="width: 70px;">
<input type="number" name="quantity" value="{{ item.quantity }}" min="1" max="{{ item.product.stock }}" class="form-control form-control-sm me-2" style="width: 70px;">
<button type="submit" class="btn btn-sm btn-primary">Actualizar</button>
</form>
</td>
<td>
{% if item.product.stock > 0 %}
{{ item.product.stock }}
{% else %}
<span class="text-danger">0</span>
{% endif %}
</td>
<td class="price">{{ item.get_subtotal_with_vat|format_price }} €</td>
<td>
<form method="post" action="{% url 'remove_from_cart' item.id %}" style="display: inline;">
@@ -82,7 +101,7 @@
<strong class="price">{{ cart.get_total_with_vat|format_price }} €</strong>
</div>
<div class="d-grid gap-2">
<a href="{% url 'checkout' %}" class="btn btn-primary btn-lg">Proceder al Pago</a>
<a href="{% url 'checkout' %}" class="btn btn-primary btn-lg {% if stock_issues %}disabled{% endif %}" {% if stock_issues %}aria-disabled="true"{% endif %}>Proceder al Pago</a>
<a href="{% url 'index' %}" class="btn btn-outline-secondary">Continuar Comprando</a>
<form method="post" action="{% url 'clear_cart' %}">
{% csrf_token %}
+25 -3
View File
@@ -49,6 +49,23 @@
</div>
{% if cart_items %}
{% if stock_issues %}
<div class="alert alert-warning">
<strong>No hay stock suficiente para algunos productos:</strong>
<ul class="mb-0 mt-2">
{% for issue in stock_issues %}
<li>{{ issue.product_name }} - disponible: {{ issue.available }}, en carrito: {{ issue.requested }}</li>
{% endfor %}
</ul>
<div class="mt-2">Vuelve al carrito y ajusta las cantidades antes de pagar.</div>
</div>
{% endif %}
<div class="alert alert-info">
Al pulsar en pagar se reservará tu stock durante <strong>{{ reservation_minutes }} minutos</strong>.
Si el pago no se completa en ese tiempo, la reserva se cancelará automáticamente.
</div>
<div class="card mb-4">
<div class="card-body">
<h5 class="card-title mb-3">1) Selecciona la dirección de envío</h5>
@@ -80,6 +97,7 @@
<th>Producto</th>
<th class="text-end">Precio (sin IVA)</th>
<th class="text-end">Cantidad</th>
<th class="text-end">Stock actual</th>
<th class="text-end">Subtotal (con IVA)</th>
</tr>
</thead>
@@ -89,6 +107,7 @@
<td>{{ item.product.name }}</td>
<td class="text-end">{{ item.product.price|format_price }}€</td>
<td class="text-end">{{ item.quantity }}</td>
<td class="text-end">{{ item.product.stock }}</td>
<td class="text-end">{{ item.get_subtotal_with_vat|format_price }}€</td>
</tr>
{% endfor %}
@@ -118,7 +137,7 @@
class="btn btn-primary payment-btn"
data-config-url="/tienda/config/"
data-session-url="/tienda/create-checkout-session/"
{% if not addresses %}disabled{% endif %}>
{% if not addresses or stock_issues %}disabled{% endif %}>
💳 Pagar con Stripe
</button>
@@ -126,7 +145,7 @@
id="paypal-button"
class="btn btn-warning payment-btn"
data-payment-url="{% url 'create_paypal_payment' %}"
{% if not addresses %}disabled{% endif %}>
{% if not addresses or stock_issues %}disabled{% endif %}>
🅿️ Pagar con PayPal
</button>
</div>
@@ -139,7 +158,9 @@
<script>
// Manejo del botón de PayPal
document.getElementById('paypal-button').addEventListener('click', async function(e) {
const paypalButton = document.getElementById('paypal-button');
if (paypalButton) {
paypalButton.addEventListener('click', async function(e) {
e.preventDefault();
const shippingAddressSelect = document.getElementById('shipping-address');
@@ -202,5 +223,6 @@
button.innerHTML = originalText;
}
});
}
</script>
{% endblock %}
@@ -46,6 +46,14 @@
</div>
</div>
<!-- Stock -->
<div class="mb-3">
<label for="stock" class="form-label">Stock disponible <span class="text-danger">*</span></label>
<input type="number" class="form-control" id="stock" name="stock" required
min="0" step="1" placeholder="0">
<div class="form-text">Cantidad máxima que podrán comprar los clientes.</div>
</div>
<!-- Categoría -->
<div class="mb-3">
<label for="category" class="form-label">Categoría <span class="text-danger">*</span></label>
@@ -46,6 +46,14 @@
</div>
</div>
<!-- Stock -->
<div class="mb-3">
<label for="stock" class="form-label">Stock disponible <span class="text-danger">*</span></label>
<input type="number" class="form-control" id="stock" name="stock" required
min="0" step="1" value="{{ producto.stock }}" placeholder="0">
<div class="form-text">Cantidad máxima que podrán comprar los clientes.</div>
</div>
<!-- Categoría -->
<div class="mb-3">
<label for="category" class="form-label">Categoría <span class="text-danger">*</span></label>
@@ -31,6 +31,7 @@
<th>Categoría</th>
<th class="text-end">Precio (sin IVA)</th>
<th class="text-end">Precio (con IVA)</th>
<th class="text-end">Stock</th>
<th class="text-end">Acciones</th>
</tr>
</thead>
@@ -53,6 +54,7 @@
<td>{{ producto.category.name }}</td>
<td class="text-end">{{ producto.price|format_price }}€</td>
<td class="text-end text-success"><strong>{{ producto.get_price_with_vat|format_price }}€</strong></td>
<td class="text-end">{{ producto.stock }}</td>
<td class="text-end">
<div class="d-flex justify-content-end gap-2">
<a href="{% url 'editar_producto' producto.id %}" class="btn btn-outline-primary btn-sm">Editar</a>
+9 -2
View File
@@ -39,14 +39,21 @@
<div id="descripcion">
{{ product.briefdesc }}
</div>
<div class="mt-3">
{% if product.stock > 0 %}
<span class="badge bg-success">Stock disponible: {{ product.stock }}</span>
{% else %}
<span class="badge bg-danger">Sin stock</span>
{% endif %}
</div>
<form method="post" action="{% url 'add_to_cart' product.id %}" class="mt-4">
{% csrf_token %}
<div class="mb-3">
<label for="quantity" class="form-label">Cantidad:</label>
<input type="number" name="quantity" id="quantity" value="1" min="1" class="form-control" style="max-width: 100px;">
<input type="number" name="quantity" id="quantity" value="1" min="1" max="{{ product.stock }}" class="form-control" style="max-width: 100px;" {% if product.stock == 0 %}disabled{% endif %}>
</div>
<button type="submit" class="btn btn-primary btn-lg">🛒 Agregar al Carrito</button>
<button type="submit" class="btn btn-primary btn-lg" {% if product.stock == 0 %}disabled{% endif %}>🛒 Agregar al Carrito</button>
</form>
</div>
</div>