[ADD] website_sale_aplicoop: botón limpiar carrito en sidebar
Añade botón 'Clear Cart' (fa-trash) en el header y footer del sidebar del carrito en la página de lista de productos. Cambios: - views/website_templates.xml: botón clear-cart-btn en card-header y clear-cart-btn-footer en card-footer del sidebar - controllers/website_sale.py: nuevo endpoint POST /eskaera/clear-cart que cancela el sale.order borrador del usuario si existe - static/src/js/website_sale.js: método _clearCart(), listeners para ambos botones (header + footer) - models/js_translations.py: nuevas cadenas clear_cart, clear_cart_confirm, cart_cleared, draft_cancelled - i18n/es.po, i18n/eu.po: traducciones ES y EU de los nuevos labels
This commit is contained in:
parent
0eb7957a70
commit
135967019e
6 changed files with 244 additions and 7 deletions
|
|
@ -268,6 +268,13 @@ class AplicoopWebsiteSale(WebsiteSale):
|
||||||
"items": env_lang._("items"),
|
"items": env_lang._("items"),
|
||||||
"added_to_cart": env_lang._("added to cart"),
|
"added_to_cart": env_lang._("added to cart"),
|
||||||
"out_of_stock": env_lang._("Out of stock"),
|
"out_of_stock": env_lang._("Out of stock"),
|
||||||
|
# ============ CLEAR CART ============
|
||||||
|
"clear_cart": env_lang._("Clear Cart"),
|
||||||
|
"clear_cart_confirm": env_lang._(
|
||||||
|
"Are you sure you want to clear the cart? This will also cancel any saved draft order."
|
||||||
|
),
|
||||||
|
"cart_cleared": env_lang._("Cart cleared"),
|
||||||
|
"draft_cancelled": env_lang._("draft order cancelled"),
|
||||||
}
|
}
|
||||||
|
|
||||||
return labels
|
return labels
|
||||||
|
|
@ -2415,6 +2422,107 @@ class AplicoopWebsiteSale(WebsiteSale):
|
||||||
status=500,
|
status=500,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@http.route(
|
||||||
|
["/eskaera/clear-cart"],
|
||||||
|
type="http",
|
||||||
|
auth="user",
|
||||||
|
website=True,
|
||||||
|
methods=["POST"],
|
||||||
|
csrf=False,
|
||||||
|
)
|
||||||
|
def clear_cart(self, **post):
|
||||||
|
"""Clear the user's cart and cancel any existing draft sale.order.
|
||||||
|
|
||||||
|
Receives: JSON body with 'order_id'
|
||||||
|
Returns: JSON with success status and cancelled_order_id if applicable.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
data = self._decode_json_body()
|
||||||
|
except ValueError as e:
|
||||||
|
return request.make_response(
|
||||||
|
json.dumps({"error": str(e)}),
|
||||||
|
[("Content-Type", "application/json")],
|
||||||
|
status=400,
|
||||||
|
)
|
||||||
|
|
||||||
|
order_id = data.get("order_id")
|
||||||
|
if not order_id:
|
||||||
|
return request.make_response(
|
||||||
|
json.dumps({"error": "order_id is required"}),
|
||||||
|
[("Content-Type", "application/json")],
|
||||||
|
status=400,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
order_id = int(order_id)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
return request.make_response(
|
||||||
|
json.dumps({"error": f"Invalid order_id format: {order_id}"}),
|
||||||
|
[("Content-Type", "application/json")],
|
||||||
|
status=400,
|
||||||
|
)
|
||||||
|
|
||||||
|
group_order = request.env["group.order"].sudo().browse(order_id)
|
||||||
|
if not group_order.exists():
|
||||||
|
return request.make_response(
|
||||||
|
json.dumps({"error": f"Order {order_id} not found"}),
|
||||||
|
[("Content-Type", "application/json")],
|
||||||
|
status=404,
|
||||||
|
)
|
||||||
|
|
||||||
|
current_user = request.env.user
|
||||||
|
if not current_user.partner_id:
|
||||||
|
return request.make_response(
|
||||||
|
json.dumps({"error": "User has no associated partner"}),
|
||||||
|
[("Content-Type", "application/json")],
|
||||||
|
status=400,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Find and cancel any existing draft sale.order for this period
|
||||||
|
cancelled_order_id = None
|
||||||
|
try:
|
||||||
|
draft_orders = self._find_recent_draft_order(
|
||||||
|
current_user.partner_id.id, group_order
|
||||||
|
)
|
||||||
|
if draft_orders:
|
||||||
|
draft_order = draft_orders[0]
|
||||||
|
cancelled_order_id = draft_order.id
|
||||||
|
# Use action_cancel if available, otherwise unlink
|
||||||
|
if draft_order.state == "draft":
|
||||||
|
draft_order.sudo().action_cancel()
|
||||||
|
_logger.info(
|
||||||
|
"clear_cart: Cancelled draft sale.order %d for partner %d",
|
||||||
|
draft_order.id,
|
||||||
|
current_user.partner_id.id,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
_logger.info(
|
||||||
|
"clear_cart: Draft order %d already in state '%s', skipping cancel",
|
||||||
|
draft_order.id,
|
||||||
|
draft_order.state,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
_logger.warning(
|
||||||
|
"clear_cart: Error cancelling draft order for partner %d: %s",
|
||||||
|
current_user.partner_id.id,
|
||||||
|
str(e),
|
||||||
|
)
|
||||||
|
|
||||||
|
response_data = {
|
||||||
|
"success": True,
|
||||||
|
"message": request.env._("Cart cleared"),
|
||||||
|
"cancelled_order_id": cancelled_order_id,
|
||||||
|
}
|
||||||
|
_logger.info(
|
||||||
|
"clear_cart: Cart cleared for partner %d, order %d (cancelled_sale_order: %s)",
|
||||||
|
current_user.partner_id.id,
|
||||||
|
order_id,
|
||||||
|
cancelled_order_id,
|
||||||
|
)
|
||||||
|
return request.make_response(
|
||||||
|
json.dumps(response_data),
|
||||||
|
[("Content-Type", "application/json")],
|
||||||
|
)
|
||||||
|
|
||||||
@http.route(
|
@http.route(
|
||||||
["/eskaera/save-order"],
|
["/eskaera/save-order"],
|
||||||
type="http",
|
type="http",
|
||||||
|
|
|
||||||
|
|
@ -1243,12 +1243,12 @@ msgstr "Guardar como Borrador"
|
||||||
#. odoo-python
|
#. odoo-python
|
||||||
#: code:addons/website_sale_aplicoop/models/js_translations.py:0
|
#: code:addons/website_sale_aplicoop/models/js_translations.py:0
|
||||||
msgid "Save Draft"
|
msgid "Save Draft"
|
||||||
msgstr "Guardar borrador"
|
msgstr "Confirmar Pedido"
|
||||||
|
|
||||||
#. module: website_sale_aplicoop
|
#. module: website_sale_aplicoop
|
||||||
#: model_terms:ir.ui.view,arch_db:website_sale_aplicoop.eskaera_checkout
|
#: model_terms:ir.ui.view,arch_db:website_sale_aplicoop.eskaera_checkout
|
||||||
msgid "Save order as draft"
|
msgid "Save order as draft"
|
||||||
msgstr "Guardar pedido como borrador"
|
msgstr "Confirmar Pedido"
|
||||||
|
|
||||||
#. module: website_sale_aplicoop
|
#. module: website_sale_aplicoop
|
||||||
#. odoo-python
|
#. odoo-python
|
||||||
|
|
@ -2278,3 +2278,22 @@ msgstr "Semanal"
|
||||||
msgid "Whether this picking includes home delivery (from sale order)"
|
msgid "Whether this picking includes home delivery (from sale order)"
|
||||||
msgstr "Si este albarán incluye entrega a domicilio (del pedido de venta)"
|
msgstr "Si este albarán incluye entrega a domicilio (del pedido de venta)"
|
||||||
|
|
||||||
|
#. module: website_sale_aplicoop
|
||||||
|
msgid "Clear Cart"
|
||||||
|
msgstr "Vaciar carrito"
|
||||||
|
|
||||||
|
#. module: website_sale_aplicoop
|
||||||
|
msgid ""
|
||||||
|
"Are you sure you want to clear the cart? This will also cancel any saved "
|
||||||
|
"draft order."
|
||||||
|
msgstr ""
|
||||||
|
"¿Estás seguro de que quieres vaciar el carrito? Esto también cancelará "
|
||||||
|
"cualquier pedido borrador guardado."
|
||||||
|
|
||||||
|
#. module: website_sale_aplicoop
|
||||||
|
msgid "Cart cleared"
|
||||||
|
msgstr "Carrito vaciado"
|
||||||
|
|
||||||
|
#. module: website_sale_aplicoop
|
||||||
|
msgid "draft order cancelled"
|
||||||
|
msgstr "pedido borrador cancelado"
|
||||||
|
|
|
||||||
|
|
@ -1242,12 +1242,12 @@ msgstr "Zirriborro Gisa Gorde"
|
||||||
#. odoo-python
|
#. odoo-python
|
||||||
#: code:addons/website_sale_aplicoop/models/js_translations.py:0
|
#: code:addons/website_sale_aplicoop/models/js_translations.py:0
|
||||||
msgid "Save Draft"
|
msgid "Save Draft"
|
||||||
msgstr "Zirriborroa gorde"
|
msgstr "Eskaera Konfirmatu"
|
||||||
|
|
||||||
#. module: website_sale_aplicoop
|
#. module: website_sale_aplicoop
|
||||||
#: model_terms:ir.ui.view,arch_db:website_sale_aplicoop.eskaera_checkout
|
#: model_terms:ir.ui.view,arch_db:website_sale_aplicoop.eskaera_checkout
|
||||||
msgid "Save order as draft"
|
msgid "Save order as draft"
|
||||||
msgstr "Gorde enparatzea zirriborro gisa"
|
msgstr "Eskaera Zirriborro Gisa Gorde"
|
||||||
|
|
||||||
#. module: website_sale_aplicoop
|
#. module: website_sale_aplicoop
|
||||||
#. odoo-python
|
#. odoo-python
|
||||||
|
|
@ -2272,3 +2272,21 @@ msgstr ""
|
||||||
"eskaeraren)"
|
"eskaeraren)"
|
||||||
|
|
||||||
#. module: website_sale_aplicoop
|
#. module: website_sale_aplicoop
|
||||||
|
msgid "Clear Cart"
|
||||||
|
msgstr "Saskia garbitu"
|
||||||
|
|
||||||
|
#. module: website_sale_aplicoop
|
||||||
|
msgid ""
|
||||||
|
"Are you sure you want to clear the cart? This will also cancel any saved "
|
||||||
|
"draft order."
|
||||||
|
msgstr ""
|
||||||
|
"Ziur zaude saskia garbitu nahi duzula? Honek gordetako zirriborro-eskaera "
|
||||||
|
"ere ezeztatuko du."
|
||||||
|
|
||||||
|
#. module: website_sale_aplicoop
|
||||||
|
msgid "Cart cleared"
|
||||||
|
msgstr "Saskia garbituta"
|
||||||
|
|
||||||
|
#. module: website_sale_aplicoop
|
||||||
|
msgid "draft order cancelled"
|
||||||
|
msgstr "zirriborro-eskaera ezeztatu da"
|
||||||
|
|
|
||||||
|
|
@ -173,3 +173,13 @@ def _register_translations():
|
||||||
_("Friday")
|
_("Friday")
|
||||||
_("Saturday")
|
_("Saturday")
|
||||||
_("Sunday")
|
_("Sunday")
|
||||||
|
|
||||||
|
# ========================
|
||||||
|
# Clear Cart
|
||||||
|
# ========================
|
||||||
|
_("Clear Cart")
|
||||||
|
_(
|
||||||
|
"Are you sure you want to clear the cart? This will also cancel any saved draft order."
|
||||||
|
)
|
||||||
|
_("Cart cleared")
|
||||||
|
_("draft order cancelled")
|
||||||
|
|
|
||||||
|
|
@ -748,6 +748,22 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Buttons to clear cart (header + footer)
|
||||||
|
var clearCartBtns = [
|
||||||
|
document.getElementById("clear-cart-btn"),
|
||||||
|
document.getElementById("clear-cart-btn-footer"),
|
||||||
|
];
|
||||||
|
clearCartBtns.forEach(function (btn) {
|
||||||
|
if (btn) {
|
||||||
|
console.log("[_attachEventListeners] clear-cart-btn found:", btn.id);
|
||||||
|
btn.addEventListener("click", function (e) {
|
||||||
|
console.log("[CLICK] clear-cart-btn clicked");
|
||||||
|
e.preventDefault();
|
||||||
|
self._clearCart();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this._cartCheckoutListenersAttached = true;
|
this._cartCheckoutListenersAttached = true;
|
||||||
console.log("[_attachEventListeners] Checkout listeners attached (one-time)");
|
console.log("[_attachEventListeners] Checkout listeners attached (one-time)");
|
||||||
}
|
}
|
||||||
|
|
@ -1029,6 +1045,12 @@
|
||||||
draft_replace_warning: "The existing draft will be permanently deleted.",
|
draft_replace_warning: "The existing draft will be permanently deleted.",
|
||||||
draft_merge_btn: "Merge",
|
draft_merge_btn: "Merge",
|
||||||
draft_replace_btn: "Replace",
|
draft_replace_btn: "Replace",
|
||||||
|
// Clear cart labels
|
||||||
|
clear_cart: "Clear Cart",
|
||||||
|
clear_cart_confirm:
|
||||||
|
"Are you sure you want to clear the cart? This will also cancel any saved draft order.",
|
||||||
|
cart_cleared: "Cart cleared",
|
||||||
|
draft_cancelled: "draft order cancelled",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -1636,6 +1658,60 @@
|
||||||
xhr.send(JSON.stringify(orderData));
|
xhr.send(JSON.stringify(orderData));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_clearCart: function () {
|
||||||
|
var self = this;
|
||||||
|
var labels = this._getLabels();
|
||||||
|
var confirmMsg =
|
||||||
|
labels.clear_cart_confirm ||
|
||||||
|
"Are you sure you want to clear the cart? This will also cancel any saved draft order.";
|
||||||
|
|
||||||
|
if (!window.confirm(confirmMsg)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear localStorage immediately
|
||||||
|
this._clearCurrentOrderCartSilently();
|
||||||
|
this._updateCartDisplay();
|
||||||
|
|
||||||
|
// Cancel draft sale.order on the server
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.open("POST", "/eskaera/clear-cart", true);
|
||||||
|
xhr.setRequestHeader("Content-Type", "application/json");
|
||||||
|
|
||||||
|
xhr.onload = function () {
|
||||||
|
var labels2 = self._getLabels();
|
||||||
|
if (xhr.status === 200) {
|
||||||
|
try {
|
||||||
|
var data = JSON.parse(xhr.responseText);
|
||||||
|
if (data.success) {
|
||||||
|
var msg = labels2.cart_cleared || "Cart cleared";
|
||||||
|
if (data.cancelled_order_id) {
|
||||||
|
msg +=
|
||||||
|
" (" +
|
||||||
|
(labels2.draft_cancelled || "draft order cancelled") +
|
||||||
|
")";
|
||||||
|
}
|
||||||
|
self._showNotification("✓ " + msg, "success", 4000);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[_clearCart] Error parsing response:", e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn("[_clearCart] Server error:", xhr.status, xhr.responseText);
|
||||||
|
// Cart is already cleared locally, server error is non-critical
|
||||||
|
self._showNotification(labels2.cart_cleared || "Cart cleared", "success", 3000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.onerror = function () {
|
||||||
|
console.warn("[_clearCart] Network error, but cart already cleared locally");
|
||||||
|
var labels2 = self._getLabels();
|
||||||
|
self._showNotification(labels2.cart_cleared || "Cart cleared", "success", 3000);
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.send(JSON.stringify({ order_id: this.orderId }));
|
||||||
|
},
|
||||||
|
|
||||||
_saveOrderDraft: function () {
|
_saveOrderDraft: function () {
|
||||||
console.log("[_saveOrderDraft] Starting - this.orderId:", this.orderId);
|
console.log("[_saveOrderDraft] Starting - this.orderId:", this.orderId);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -283,12 +283,18 @@
|
||||||
<a t-attf-href="/eskaera/{{ group_order.id }}/checkout" class="btn btn-success cart-btn-compact" aria-label="Proceed to checkout" data-bs-title="Proceed to Checkout" data-bs-toggle="tooltip">
|
<a t-attf-href="/eskaera/{{ group_order.id }}/checkout" class="btn btn-success cart-btn-compact" aria-label="Proceed to checkout" data-bs-title="Proceed to Checkout" data-bs-toggle="tooltip">
|
||||||
<i class="fa fa-check cart-icon-size" aria-hidden="true" />
|
<i class="fa fa-check cart-icon-size" aria-hidden="true" />
|
||||||
</a>
|
</a>
|
||||||
|
<button type="button" class="btn btn-outline-danger cart-btn-compact" id="clear-cart-btn" t-attf-data-order-id="{{ group_order.id }}" data-bs-title="Clear Cart" data-bs-toggle="tooltip" aria-label="Clear Cart">
|
||||||
|
<i class="fa fa-trash cart-icon-size" aria-hidden="true" />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body cart-body-lg" id="cart-items-container" t-attf-data-order-id="{{ group_order.id }}" aria-labelledby="cart-title" aria-live="polite" aria-relevant="additions removals">
|
<div class="card-body cart-body-lg" id="cart-items-container" t-attf-data-order-id="{{ group_order.id }}" aria-labelledby="cart-title" aria-live="polite" aria-relevant="additions removals">
|
||||||
<p class="text-muted">This order's cart is empty</p>
|
<p class="text-muted">This order's cart is empty</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer bg-white text-center">
|
<div class="card-footer bg-white d-flex justify-content-between align-items-center gap-2">
|
||||||
|
<button type="button" class="btn btn-outline-danger btn-sm" id="clear-cart-btn-footer" t-attf-data-order-id="{{ group_order.id }}" data-bs-title="Clear Cart" data-bs-toggle="tooltip" aria-label="Clear Cart">
|
||||||
|
<i class="fa fa-trash me-1" aria-hidden="true" />Clear Cart
|
||||||
|
</button>
|
||||||
<a t-attf-href="/eskaera/{{ group_order.id }}/checkout" class="btn btn-success checkout-btn-lg" data-bs-title="Proceed to Checkout" data-bs-toggle="tooltip">
|
<a t-attf-href="/eskaera/{{ group_order.id }}/checkout" class="btn btn-success checkout-btn-lg" data-bs-title="Proceed to Checkout" data-bs-toggle="tooltip">
|
||||||
Proceed to Checkout
|
Proceed to Checkout
|
||||||
</a>
|
</a>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue