diff --git a/website_sale_aplicoop/__manifest__.py b/website_sale_aplicoop/__manifest__.py index 1b29beb..f7ac586 100644 --- a/website_sale_aplicoop/__manifest__.py +++ b/website_sale_aplicoop/__manifest__.py @@ -3,7 +3,7 @@ { # noqa: B018 "name": "Website Sale - Aplicoop", - "version": "18.0.1.10.0", + "version": "18.0.1.10.2", "category": "Website/Sale", "summary": "Modern replacement of legacy Aplicoop - Collaborative consumption group orders", "author": "Odoo Community Association (OCA), Criptomart", diff --git a/website_sale_aplicoop/controllers/website_sale.py b/website_sale_aplicoop/controllers/website_sale.py index a9d6894..315143a 100644 --- a/website_sale_aplicoop/controllers/website_sale.py +++ b/website_sale_aplicoop/controllers/website_sale.py @@ -641,6 +641,16 @@ class AplicoopWebsiteSale(WebsiteSale): def _decode_json_body(self): return _utils._decode_json_body(self, request) + def _get_installed_module_version(self): + """Return the installed version of this addon, used as a build-id + signal so the frontend can detect a deploy and force a reload.""" + module = ( + request.env["ir.module.module"] + .sudo() + .search([("name", "=", "website_sale_aplicoop")], limit=1) + ) + return module.installed_version or "" + def _build_group_order_unavailable_response(self, group_order, status=403): """Delegate building of unavailable response to utils helper.""" self._request = request @@ -827,6 +837,7 @@ class AplicoopWebsiteSale(WebsiteSale): "current_page": page, "has_next": has_next, "total_products": total_products, + "module_version": self._get_installed_module_version(), }, ) @@ -1370,6 +1381,7 @@ class AplicoopWebsiteSale(WebsiteSale): ), "cutoff_passed": cutoff_passed, "cutoff_date": cutoff_date_str, + "client_version": self._get_installed_module_version(), } return request.make_response( json.dumps(response_data), diff --git a/website_sale_aplicoop/static/src/js/website_sale.js b/website_sale_aplicoop/static/src/js/website_sale.js index 15076c0..1ed420b 100644 --- a/website_sale_aplicoop/static/src/js/website_sale.js +++ b/website_sale_aplicoop/static/src/js/website_sale.js @@ -45,6 +45,13 @@ console.log("Initializing cart for order:", this.orderId); + // Capture the build version embedded in the rendered HTML. The + // server returns its installed version in /eskaera/check-status, + // and if the two disagree we know a deploy happened and the + // current page is running stale JS — force a one-shot reload. + this._buildVersion = orderIdElement.getAttribute("data-build-version") || null; + console.log("[groupOrderShop] Build version:", this._buildVersion); + // Attach event listeners FIRST (doesn't depend on translations) this._attachEventListeners(); console.log("[groupOrderShop] Event listeners attached"); @@ -435,7 +442,18 @@ }, _loadCart: function () { + // Storage layout: + // eskaera__cart → items as plain {productId: {...}} + // (keeps the contract used by + // checkout_labels.js, home_delivery.js + // and _saveOrderDraft). + // eskaera__cart_cycle → "YYYY-MM-DD" the cart was saved at. + // Eviction only fires when we KNOW the current cycle and it + // disagrees with the stored one. If either side is missing + // (e.g. checkout page that doesn't call check-status, or XHR + // failure), we preserve the user's cart. var cartKey = "eskaera_" + this.orderId + "_cart"; + var cycleKey = "eskaera_" + this.orderId + "_cart_cycle"; var raw = localStorage.getItem(cartKey); this.cart = {}; if (!raw) { @@ -448,27 +466,32 @@ } catch (e) { console.warn("Cart load: invalid JSON in localStorage, dropping", e); localStorage.removeItem(cartKey); + localStorage.removeItem(cycleKey); return; } - // Legacy / corrupt shape (raw items object without cycle stamp) → drop. - if ( - !parsed || - typeof parsed !== "object" || - !parsed.items || - !("cutoff_date" in parsed) - ) { - console.log( - "Cart load: legacy/unstamped value, dropping localStorage[" + cartKey + "]" - ); + if (!parsed || typeof parsed !== "object") { localStorage.removeItem(cartKey); + localStorage.removeItem(cycleKey); return; } - // Cycle mismatch (or no current cycle known) → drop. This is the - // primary defence against stale carts surviving cycle rollover. - if (!this._currentCutoffDate || parsed.cutoff_date !== this._currentCutoffDate) { + // Migration: v18.0.1.10.0 wrapped the cart as {cutoff_date, items} + // in the same key, which broke checkout_labels.js / home_delivery.js + // / _saveOrderDraft (they iterated Object.keys treating them as + // productIds). Unwrap once; subsequent saves use the canonical + // split-key layout below. + var items; + var storedCycle; + if (parsed.items && "cutoff_date" in parsed) { + items = parsed.items; + storedCycle = parsed.cutoff_date; + } else { + items = parsed; + storedCycle = localStorage.getItem(cycleKey); + } + if (this._currentCutoffDate && storedCycle && storedCycle !== this._currentCutoffDate) { console.log( "Cart load: cycle mismatch (stored=" + - parsed.cutoff_date + + storedCycle + ", current=" + this._currentCutoffDate + "), dropping localStorage[" + @@ -476,14 +499,20 @@ "]" ); localStorage.removeItem(cartKey); + localStorage.removeItem(cycleKey); return; } - this.cart = parsed.items; + this.cart = items; + // Re-canonicalize storage so other readers see the plain shape. + localStorage.setItem(cartKey, JSON.stringify(items)); + if (storedCycle) { + localStorage.setItem(cycleKey, storedCycle); + } console.log( "Cart loaded from localStorage[" + cartKey + "] (cycle " + - parsed.cutoff_date + + (storedCycle || "unknown") + "):", this.cart ); @@ -491,11 +520,11 @@ _saveCart: function () { var cartKey = "eskaera_" + this.orderId + "_cart"; - var payload = JSON.stringify({ - cutoff_date: this._currentCutoffDate || null, - items: this.cart, - }); - localStorage.setItem(cartKey, payload); + var cycleKey = "eskaera_" + this.orderId + "_cart_cycle"; + localStorage.setItem(cartKey, JSON.stringify(this.cart)); + if (this._currentCutoffDate) { + localStorage.setItem(cycleKey, this._currentCutoffDate); + } console.log( "Cart saved to localStorage[" + cartKey + @@ -508,11 +537,42 @@ _clearCurrentOrderCartSilently: function () { var cartKey = "eskaera_" + this.orderId + "_cart"; + var cycleKey = "eskaera_" + this.orderId + "_cart_cycle"; localStorage.removeItem(cartKey); + localStorage.removeItem(cycleKey); this.cart = {}; console.log("[groupOrderShop] Cart cleared silently for order:", this.orderId); }, + // Avoid a reload loop if for any reason (HTML cached upstream, etc.) + // the build versions keep disagreeing after a reload. + _shouldReloadForNewBuild: function () { + try { + var raw = sessionStorage.getItem("eskaera_build_reload_at"); + if (!raw) { + return true; + } + var last = parseInt(raw, 10); + if (isNaN(last)) { + return true; + } + // If we already reloaded in the last 30s, don't try again. + return Date.now() - last > 30000; + } catch (e) { + return true; + } + }, + + _markReloadAttempt: function () { + try { + sessionStorage.setItem("eskaera_build_reload_at", String(Date.now())); + } catch (e) { + // sessionStorage unavailable — fail open; worst case we get a + // single extra reload, never a loop because the next render + // will carry the new version. + } + }, + _isClosedOrderResponse: function (xhr) { if (!xhr) { return false; @@ -555,6 +615,27 @@ } try { var data = JSON.parse(xhr.responseText || "{}"); + // Deploy-detection: server tells us the installed module + // version; if it differs from the version embedded in + // the HTML, we are running stale JS — reload once. + if ( + data && + data.client_version && + self._buildVersion && + data.client_version !== self._buildVersion && + self._shouldReloadForNewBuild() + ) { + console.warn( + "[groupOrderShop] Build mismatch (page=" + + self._buildVersion + + ", server=" + + data.client_version + + "), reloading" + ); + self._markReloadAttempt(); + window.location.reload(); + return; + } // Capture current cycle marker so _loadCart can evict a // localStorage cart that belongs to a previous cycle. self._currentCutoffDate = data && data.cutoff_date ? data.cutoff_date : null; diff --git a/website_sale_aplicoop/views/website_templates.xml b/website_sale_aplicoop/views/website_templates.xml index 66f266d..3d4d6ac 100644 --- a/website_sale_aplicoop/views/website_templates.xml +++ b/website_sale_aplicoop/views/website_templates.xml @@ -337,7 +337,7 @@ -
+

This order's cart is empty