From 9484f8c3496e0377032fe8546ec3e15e4baf37da Mon Sep 17 00:00:00 2001 From: GitHub Copilot Date: Sat, 6 Jun 2026 15:37:55 +0200 Subject: [PATCH] [FIX] website_sale_aplicoop: split cart and cycle stamp in localStorage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 965c5c2 stored the cart as {cutoff_date, items: {...}} in eskaera__cart. That broke every other reader of the same key: - checkout_labels.js iterates Object.keys() expecting product IDs and rendered "cutoff_date" / "items" as ghost rows → users on the checkout page saw their cart as empty. - home_delivery.js read/wrote the cart in place; the in-place mutation destroyed the wrapper. - _saveOrderDraft serialised the same object straight to the server, POSTing "cutoff_date" and "items" as productIds. Split the schema: eskaera__cart keeps the plain {productId: {...}} shape every other JS file already relies on; the cycle marker moves to eskaera__cart_cycle. _loadCart migrates browsers still holding the v18.0.1.10.0 wrapped value on the next read. Also make eviction strictly opt-in: drop the cart only when we know the current cycle AND the stored cycle disagrees. Missing data on either side (check-status didn't run, XHR failed) → preserve. This restores the checkout page where check-status isn't called. Version bump triggers the in-tab auto-reload added in 6e6d1e5 for clients already on that build. Co-Authored-By: Claude Opus 4.7 --- website_sale_aplicoop/__manifest__.py | 2 +- .../static/src/js/website_sale.js | 66 +++++++++++++------ 2 files changed, 46 insertions(+), 22 deletions(-) diff --git a/website_sale_aplicoop/__manifest__.py b/website_sale_aplicoop/__manifest__.py index 9bc2aaf..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.1", + "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/static/src/js/website_sale.js b/website_sale_aplicoop/static/src/js/website_sale.js index a51ead4..1ed420b 100644 --- a/website_sale_aplicoop/static/src/js/website_sale.js +++ b/website_sale_aplicoop/static/src/js/website_sale.js @@ -442,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) { @@ -455,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[" + @@ -483,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 ); @@ -498,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 + @@ -515,7 +537,9 @@ _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); },