From 0eb7957a7086c275dd330ea0c247405ebceb7ed0 Mon Sep 17 00:00:00 2001 From: snt Date: Tue, 7 Apr 2026 23:39:49 +0200 Subject: [PATCH 1/2] [IMP] website_sale_aplicoop: remove redundant string= from Boolean fields (W8113) --- website_sale_aplicoop/models/product_extension.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/website_sale_aplicoop/models/product_extension.py b/website_sale_aplicoop/models/product_extension.py index 1fc4f6c..9ed389e 100644 --- a/website_sale_aplicoop/models/product_extension.py +++ b/website_sale_aplicoop/models/product_extension.py @@ -20,14 +20,12 @@ class ProductProduct(models.Model): ) is_out_of_stock = fields.Boolean( - string="Is Out of Stock", compute="_compute_stock_ribbons", store=False, help="True if virtual_available <= 0", ) is_low_stock = fields.Boolean( - string="Is Low Stock", compute="_compute_stock_ribbons", store=False, help="True if 0 < virtual_available <= threshold", From 135967019e95e7f77b033a9c78f8ca0465e271f7 Mon Sep 17 00:00:00 2001 From: snt Date: Tue, 7 Apr 2026 23:50:30 +0200 Subject: [PATCH 2/2] =?UTF-8?q?[ADD]=20website=5Fsale=5Faplicoop:=20bot?= =?UTF-8?q?=C3=B3n=20limpiar=20carrito=20en=20sidebar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../controllers/website_sale.py | 108 ++++++++++++++++++ website_sale_aplicoop/i18n/es.po | 23 +++- website_sale_aplicoop/i18n/eu.po | 22 +++- .../models/js_translations.py | 10 ++ .../static/src/js/website_sale.js | 76 ++++++++++++ .../views/website_templates.xml | 12 +- 6 files changed, 244 insertions(+), 7 deletions(-) diff --git a/website_sale_aplicoop/controllers/website_sale.py b/website_sale_aplicoop/controllers/website_sale.py index a777c25..7da04ec 100644 --- a/website_sale_aplicoop/controllers/website_sale.py +++ b/website_sale_aplicoop/controllers/website_sale.py @@ -268,6 +268,13 @@ class AplicoopWebsiteSale(WebsiteSale): "items": env_lang._("items"), "added_to_cart": env_lang._("added to cart"), "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 @@ -2415,6 +2422,107 @@ class AplicoopWebsiteSale(WebsiteSale): 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( ["/eskaera/save-order"], type="http", diff --git a/website_sale_aplicoop/i18n/es.po b/website_sale_aplicoop/i18n/es.po index b9901a8..26e9264 100644 --- a/website_sale_aplicoop/i18n/es.po +++ b/website_sale_aplicoop/i18n/es.po @@ -1243,12 +1243,12 @@ msgstr "Guardar como Borrador" #. odoo-python #: code:addons/website_sale_aplicoop/models/js_translations.py:0 msgid "Save Draft" -msgstr "Guardar borrador" +msgstr "Confirmar Pedido" #. module: website_sale_aplicoop #: model_terms:ir.ui.view,arch_db:website_sale_aplicoop.eskaera_checkout msgid "Save order as draft" -msgstr "Guardar pedido como borrador" +msgstr "Confirmar Pedido" #. module: website_sale_aplicoop #. odoo-python @@ -2278,3 +2278,22 @@ msgstr "Semanal" msgid "Whether this picking includes home delivery (from sale order)" 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" diff --git a/website_sale_aplicoop/i18n/eu.po b/website_sale_aplicoop/i18n/eu.po index e6886d9..dbf1717 100644 --- a/website_sale_aplicoop/i18n/eu.po +++ b/website_sale_aplicoop/i18n/eu.po @@ -1242,12 +1242,12 @@ msgstr "Zirriborro Gisa Gorde" #. odoo-python #: code:addons/website_sale_aplicoop/models/js_translations.py:0 msgid "Save Draft" -msgstr "Zirriborroa gorde" +msgstr "Eskaera Konfirmatu" #. module: website_sale_aplicoop #: model_terms:ir.ui.view,arch_db:website_sale_aplicoop.eskaera_checkout msgid "Save order as draft" -msgstr "Gorde enparatzea zirriborro gisa" +msgstr "Eskaera Zirriborro Gisa Gorde" #. module: website_sale_aplicoop #. odoo-python @@ -2272,3 +2272,21 @@ msgstr "" "eskaeraren)" #. 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" diff --git a/website_sale_aplicoop/models/js_translations.py b/website_sale_aplicoop/models/js_translations.py index a7edf65..ee11ab2 100644 --- a/website_sale_aplicoop/models/js_translations.py +++ b/website_sale_aplicoop/models/js_translations.py @@ -173,3 +173,13 @@ def _register_translations(): _("Friday") _("Saturday") _("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") diff --git a/website_sale_aplicoop/static/src/js/website_sale.js b/website_sale_aplicoop/static/src/js/website_sale.js index cb7ad3b..545ef99 100644 --- a/website_sale_aplicoop/static/src/js/website_sale.js +++ b/website_sale_aplicoop/static/src/js/website_sale.js @@ -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; console.log("[_attachEventListeners] Checkout listeners attached (one-time)"); } @@ -1029,6 +1045,12 @@ draft_replace_warning: "The existing draft will be permanently deleted.", draft_merge_btn: "Merge", 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)); }, + _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 () { console.log("[_saveOrderDraft] Starting - this.orderId:", this.orderId); diff --git a/website_sale_aplicoop/views/website_templates.xml b/website_sale_aplicoop/views/website_templates.xml index 657fca0..9a66ea8 100644 --- a/website_sale_aplicoop/views/website_templates.xml +++ b/website_sale_aplicoop/views/website_templates.xml @@ -283,15 +283,21 @@