[FIX] website_sale_aplicoop: two draft-reuse and compute-field bugs

- 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).
This commit is contained in:
GitHub Copilot 2026-08-13 12:02:33 +02:00
parent e59c706ef6
commit 158933e96e
2 changed files with 34 additions and 10 deletions

View file

@ -65,21 +65,33 @@ def _get_salesperson_for_order(self, partner):
def _find_recent_draft_order(self, partner_id, group_order, request_obj=None): 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. """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 A draft only counts as "current cycle" when it satisfies BOTH of these
depends on the order period: 7 days (weekly), 14 days (biweekly), one (neither is sufficient on its own see the regression each one guards):
month (monthly). One-time orders have a single cycle starting at
start_date (or unbounded when start_date is empty). Drafts created 1) create_date falls within the active window derived from
outside this window belong to a previous cycle and must not be reused group_order.cutoff_date and the order period (7 days weekly, 14
otherwise stale carts come back when the user re-enters the order page. 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 req = request_obj or request
from datetime import timedelta
from dateutil.relativedelta import relativedelta
if not group_order or not group_order.cutoff_date: if not group_order or not group_order.cutoff_date:
return req.env["sale.order"] return req.env["sale.order"]
from datetime import timedelta
from dateutil.relativedelta import relativedelta
period_end = group_order.cutoff_date period_end = group_order.cutoff_date
if group_order.period == "weekly": if group_order.period == "weekly":
period_start = period_end - timedelta(days=6) 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: if period_start:
domain.append(("create_date", ">=", f"{period_start} 00:00:00")) domain.append(("create_date", ">=", f"{period_start} 00:00:00"))
if group_order.pickup_date:
domain.append(("pickup_date", "=", group_order.pickup_date))
return ( return (
req.env["sale.order"].sudo().search(domain, order="create_date desc", limit=1) req.env["sale.order"].sudo().search(domain, order="create_date desc", limit=1)

View file

@ -420,6 +420,7 @@ class GroupOrder(models.Model):
taken = set() taken = set()
new_vals_list = [] new_vals_list = []
for vals in 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( slug = self._normalize_slug(vals.get("slug")) or self._generate_unique_slug(
vals.get("name"), taken=taken vals.get("name"), taken=taken
) )
@ -428,7 +429,16 @@ class GroupOrder(models.Model):
return super().create(new_vals_list) return super().create(new_vals_list)
def write(self, vals): 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: if "slug" not in vals:
return super().write(vals) return super().write(vals)
slug = self._normalize_slug(vals["slug"]) slug = self._normalize_slug(vals["slug"])