From 158933e96e57667fb4c72304e166981f70f19822 Mon Sep 17 00:00:00 2001 From: GitHub Copilot Date: Thu, 13 Aug 2026 12:02:33 +0200 Subject: [PATCH] [FIX] website_sale_aplicoop: two draft-reuse and compute-field bugs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - group.order.home_delivery is a stored compute field with no inverse; Odoo still lets write() set it directly, so a stray direct write stuck instead of always being re-derived from delivery_product_id. create() and write() now strip it from vals, same pattern already used for slug. - _find_recent_draft_order only bounded drafts by create_date, so a freshly created draft for a stale/previous pickup_date (but created "now") was wrongly reused. Fix requires both create_date to fall in the active window AND pickup_date to match when set — the latter alone isn't enough either, per the regression already covered by test_find_recent_draft_excludes_previous_cycle (observed in production at stage.elikabilbo.eus). --- .../controllers/website_sale_validators.py | 32 +++++++++++++------ website_sale_aplicoop/models/group_order.py | 12 ++++++- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/website_sale_aplicoop/controllers/website_sale_validators.py b/website_sale_aplicoop/controllers/website_sale_validators.py index 8f14309..7e6e7ff 100644 --- a/website_sale_aplicoop/controllers/website_sale_validators.py +++ b/website_sale_aplicoop/controllers/website_sale_validators.py @@ -65,21 +65,33 @@ def _get_salesperson_for_order(self, partner): def _find_recent_draft_order(self, partner_id, group_order, request_obj=None): """Return the active-cycle draft sale.order for the partner, or empty. - The active ordering period ends at group_order.cutoff_date and its length - depends on the order period: 7 days (weekly), 14 days (biweekly), one - month (monthly). One-time orders have a single cycle starting at - start_date (or unbounded when start_date is empty). Drafts created - outside this window belong to a previous cycle and must not be reused — - otherwise stale carts come back when the user re-enters the order page. + A draft only counts as "current cycle" when it satisfies BOTH of these + (neither is sufficient on its own — see the regression each one guards): + + 1) create_date falls within the active window — derived from + group_order.cutoff_date and the order period (7 days weekly, 14 + biweekly, one month monthly; one-time orders use a single cycle + starting at start_date). Without this, a draft whose pickup_date + happens to match the current one only because pickup_date froze + across cycles (observed in production) would be wrongly reused. + 2) When group_order.pickup_date is set, the draft's pickup_date matches + it exactly. Without this, a draft created "now" for a stale/previous + pickup_date — but still inside the current create_date window — + would be wrongly reused instead of starting a fresh cart. + + Drafts failing either check belong to a previous cycle and must not be + reused — otherwise stale carts come back when the user re-enters the + order page. """ req = request_obj or request - from datetime import timedelta - - from dateutil.relativedelta import relativedelta if not group_order or not group_order.cutoff_date: return req.env["sale.order"] + from datetime import timedelta + + from dateutil.relativedelta import relativedelta + period_end = group_order.cutoff_date if group_order.period == "weekly": period_start = period_end - timedelta(days=6) @@ -98,6 +110,8 @@ def _find_recent_draft_order(self, partner_id, group_order, request_obj=None): ] if period_start: domain.append(("create_date", ">=", f"{period_start} 00:00:00")) + if group_order.pickup_date: + domain.append(("pickup_date", "=", group_order.pickup_date)) return ( req.env["sale.order"].sudo().search(domain, order="create_date desc", limit=1) diff --git a/website_sale_aplicoop/models/group_order.py b/website_sale_aplicoop/models/group_order.py index 8c716e0..95ae054 100644 --- a/website_sale_aplicoop/models/group_order.py +++ b/website_sale_aplicoop/models/group_order.py @@ -420,6 +420,7 @@ class GroupOrder(models.Model): taken = set() new_vals_list = [] for vals in vals_list: + vals = {k: v for k, v in vals.items() if k != "home_delivery"} slug = self._normalize_slug(vals.get("slug")) or self._generate_unique_slug( vals.get("name"), taken=taken ) @@ -428,7 +429,16 @@ class GroupOrder(models.Model): return super().create(new_vals_list) def write(self, vals): - """Normalize the slug, regenerating it from the name when emptied.""" + """Normalize the slug, regenerating it from the name when emptied. + + `home_delivery` is derived from `delivery_product_id` and must never + be settable directly — dropped here so a stray direct write can't + make it stick outside `_compute_home_delivery`. + """ + if "home_delivery" in vals: + vals = {k: v for k, v in vals.items() if k != "home_delivery"} + if not vals: + return True if "slug" not in vals: return super().write(vals) slug = self._normalize_slug(vals["slug"])