[IMP] website_sale_aplicoop: automate non-weekly group order cycles
One-time, biweekly and monthly group orders now follow the same cron confirmation flow as weekly ones (confirm sale orders + batch pickings when the cycle cutoff passes): - Biweekly/monthly keep the cutoff_day/pickup_day weekday scheme on a recurrence grid anchored at start_date (creation date as fallback): cutoffs advance +14 days / +1 month snapped to cutoff_day, with catch-up after cron downtime. Previously they behaved as weekly. - One-time orders (specials/promotions) are driven by end_date (cutoff_date = end_date); once passed, the cron confirms, batches and closes the group order. - end_date keeps its "empty = permanent" meaning for recurring orders. - Website draft-cart lookup window is now period-aware instead of assuming a 6-day weekly cycle. - New cron tests for once/biweekly/monthly cycles; i18n es/eu updated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
d8cd83bdaf
commit
aba22fd230
8 changed files with 510 additions and 74 deletions
|
|
@ -59,47 +59,37 @@ class TestCronPickingBatch(TransactionCase):
|
|||
)
|
||||
|
||||
def _create_group_order(self, cutoff_in_past=False, state="open"):
|
||||
"""Create a group order with cutoff date in past or future.
|
||||
"""Create a one-time group order whose cycle ends in past or future.
|
||||
|
||||
One-time group orders derive cutoff_date from end_date, so the cycle
|
||||
is controlled here through end_date. pickup_date is computed as the
|
||||
next occurrence of pickup_day after end_date.
|
||||
|
||||
Args:
|
||||
cutoff_in_past: If True, cutoff_date will be yesterday (past).
|
||||
If False, cutoff_date will be tomorrow (future).
|
||||
cutoff_in_past: If True, end_date (= cutoff_date) is yesterday.
|
||||
If False, end_date is the day after tomorrow.
|
||||
state: State of the group order
|
||||
"""
|
||||
today = fields.Date.today()
|
||||
|
||||
# Create with basic config first
|
||||
order = self.env["group.order"].create(
|
||||
if cutoff_in_past:
|
||||
end_date = today - timedelta(days=1) # Yesterday
|
||||
else:
|
||||
end_date = today + timedelta(days=2) # Day after tomorrow
|
||||
|
||||
return self.env["group.order"].create(
|
||||
{
|
||||
"name": f"Test Group Order {'past' if cutoff_in_past else 'future'}",
|
||||
"group_ids": [
|
||||
(6, 0, [self.consumer_group_1.id, self.consumer_group_2.id])
|
||||
],
|
||||
"period": "once", # One-time order; dates are set explicitly below
|
||||
"cutoff_day": "0", # Monday
|
||||
"period": "once",
|
||||
"pickup_day": "2", # Wednesday
|
||||
"state": state,
|
||||
"end_date": end_date,
|
||||
}
|
||||
)
|
||||
|
||||
# Directly set cutoff_date and pickup_date (bypass computed logic)
|
||||
if cutoff_in_past:
|
||||
cutoff_date = today - timedelta(days=1) # Yesterday
|
||||
pickup_date = today + timedelta(days=1) # Tomorrow
|
||||
else:
|
||||
cutoff_date = today + timedelta(days=2) # Day after tomorrow
|
||||
pickup_date = today + timedelta(days=4) # 4 days from now
|
||||
|
||||
# Write directly to stored computed fields
|
||||
order.write(
|
||||
{
|
||||
"cutoff_date": cutoff_date,
|
||||
"pickup_date": pickup_date,
|
||||
}
|
||||
)
|
||||
|
||||
return order
|
||||
|
||||
def _create_sale_order(self, group_order, partner, consumer_group):
|
||||
"""Create a draft sale order linked to the group order."""
|
||||
return self.env["sale.order"].create(
|
||||
|
|
@ -451,3 +441,273 @@ class TestCronPickingBatch(TransactionCase):
|
|||
"sale",
|
||||
"The order should be confirmed when cron uses non-blocking procurement context",
|
||||
)
|
||||
|
||||
def test_once_order_closes_after_end_date(self):
|
||||
"""One-time order: full cron confirms, batches and closes it after end_date."""
|
||||
group_order = self._create_group_order(cutoff_in_past=True)
|
||||
so = self._create_sale_order(group_order, self.member_1, self.consumer_group_1)
|
||||
|
||||
self.env["group.order"]._cron_update_dates()
|
||||
|
||||
so.invalidate_recordset()
|
||||
self.assertEqual(
|
||||
so.state,
|
||||
"sale",
|
||||
"Sale order should be confirmed - end date has passed",
|
||||
)
|
||||
self.assertTrue(so.picking_ids, "Sale order should have pickings")
|
||||
self.assertTrue(
|
||||
so.picking_ids[0].batch_id,
|
||||
"Pickings of a one-time order should be batched",
|
||||
)
|
||||
self.assertEqual(
|
||||
group_order.state,
|
||||
"closed",
|
||||
"One-time group order should be closed after its cycle is confirmed",
|
||||
)
|
||||
|
||||
def test_once_order_before_end_date_stays_open(self):
|
||||
"""One-time order with a future end_date must not be confirmed nor closed."""
|
||||
group_order = self._create_group_order(cutoff_in_past=False)
|
||||
so = self._create_sale_order(group_order, self.member_1, self.consumer_group_1)
|
||||
end_date_before = group_order.end_date
|
||||
|
||||
group_order._confirm_linked_sale_orders()
|
||||
group_order._close_one_time_order_if_ended()
|
||||
|
||||
so.invalidate_recordset()
|
||||
self.assertEqual(
|
||||
so.state,
|
||||
"draft",
|
||||
"Sale order should remain draft - end date not yet passed",
|
||||
)
|
||||
self.assertEqual(group_order.state, "open", "Group order should stay open")
|
||||
self.assertEqual(
|
||||
group_order.end_date,
|
||||
end_date_before,
|
||||
"end_date must not move while the cycle is still open",
|
||||
)
|
||||
|
||||
def test_once_order_without_end_date_is_skipped(self):
|
||||
"""One-time order without end_date has no cutoff and is never auto-confirmed."""
|
||||
group_order = self.env["group.order"].create(
|
||||
{
|
||||
"name": "Test Group Order permanent once",
|
||||
"group_ids": [(6, 0, [self.consumer_group_1.id])],
|
||||
"period": "once",
|
||||
"pickup_day": "2",
|
||||
"state": "open",
|
||||
}
|
||||
)
|
||||
self.assertFalse(
|
||||
group_order.cutoff_date,
|
||||
"Non-weekly order without end_date should have no cutoff_date",
|
||||
)
|
||||
so = self._create_sale_order(group_order, self.member_1, self.consumer_group_1)
|
||||
|
||||
group_order._confirm_linked_sale_orders()
|
||||
group_order._close_one_time_order_if_ended()
|
||||
|
||||
so.invalidate_recordset()
|
||||
self.assertEqual(
|
||||
so.state,
|
||||
"draft",
|
||||
"Sale order should remain draft - no end_date to evaluate",
|
||||
)
|
||||
self.assertEqual(group_order.state, "open", "Group order should stay open")
|
||||
|
||||
def _create_recurring_group_order(self, period, start_date, cutoff_day):
|
||||
"""Create a biweekly/monthly group order anchored at start_date."""
|
||||
return self.env["group.order"].create(
|
||||
{
|
||||
"name": f"Test {period} Group Order",
|
||||
"group_ids": [(6, 0, [self.consumer_group_1.id])],
|
||||
"period": period,
|
||||
"cutoff_day": cutoff_day,
|
||||
"pickup_day": "2", # Wednesday
|
||||
"start_date": start_date,
|
||||
"state": "open",
|
||||
}
|
||||
)
|
||||
|
||||
def test_biweekly_cutoff_follows_14_day_grid(self):
|
||||
"""Biweekly cutoff lands on the cutoff_day grid anchored at start_date."""
|
||||
today = fields.Date.today()
|
||||
# Anchor 15 days ago on the same weekday as yesterday: the grid is
|
||||
# [today-15, today-1, today+13, ...] and the compute must pick the
|
||||
# first occurrence that is today or later.
|
||||
anchor = today - timedelta(days=15)
|
||||
cutoff_weekday = (today - timedelta(days=1)).weekday()
|
||||
group_order = self._create_recurring_group_order(
|
||||
"biweekly", anchor, str(cutoff_weekday)
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
group_order.cutoff_date,
|
||||
today + timedelta(days=13),
|
||||
"Biweekly cutoff must be the next 14-day grid point, not next week",
|
||||
)
|
||||
self.assertEqual(
|
||||
group_order.cutoff_date.weekday(),
|
||||
cutoff_weekday,
|
||||
"Biweekly cutoff must fall on the configured cutoff_day",
|
||||
)
|
||||
self.assertGreater(
|
||||
group_order.pickup_date,
|
||||
group_order.cutoff_date,
|
||||
"Pickup must be after the cycle cutoff",
|
||||
)
|
||||
|
||||
def test_biweekly_cycle_confirms_and_advances_on_grid(self):
|
||||
"""Biweekly: cron confirms the past cutoff, next cutoff moves +14 days."""
|
||||
today = fields.Date.today()
|
||||
anchor = today - timedelta(days=15)
|
||||
cutoff_weekday = (today - timedelta(days=1)).weekday()
|
||||
group_order = self._create_recurring_group_order(
|
||||
"biweekly", anchor, str(cutoff_weekday)
|
||||
)
|
||||
# Simulate the stored value from the previous cycle (grid point that
|
||||
# passed yesterday), as the daily cron would have left it.
|
||||
group_order.write({"cutoff_date": today - timedelta(days=1)})
|
||||
so = self._create_sale_order(group_order, self.member_1, self.consumer_group_1)
|
||||
|
||||
group_order._confirm_linked_sale_orders()
|
||||
group_order._close_one_time_order_if_ended()
|
||||
group_order._compute_cutoff_date()
|
||||
group_order._compute_pickup_date()
|
||||
group_order._compute_delivery_date()
|
||||
|
||||
so.invalidate_recordset()
|
||||
self.assertEqual(
|
||||
so.state,
|
||||
"sale",
|
||||
"Sale order should be confirmed - biweekly cutoff has passed",
|
||||
)
|
||||
self.assertTrue(so.picking_ids, "Sale order should have pickings")
|
||||
self.assertTrue(
|
||||
so.picking_ids[0].batch_id,
|
||||
"Pickings of a biweekly order should be batched",
|
||||
)
|
||||
self.assertEqual(
|
||||
group_order.state,
|
||||
"open",
|
||||
"Biweekly group order should stay open for the next cycle",
|
||||
)
|
||||
self.assertEqual(
|
||||
group_order.cutoff_date,
|
||||
today + timedelta(days=13),
|
||||
"Next biweekly cutoff must be 14 days after the confirmed one",
|
||||
)
|
||||
self.assertFalse(
|
||||
group_order.end_date,
|
||||
"end_date must stay empty (permanent recurring order)",
|
||||
)
|
||||
|
||||
def test_biweekly_grid_catches_up_after_downtime(self):
|
||||
"""Biweekly grid skips missed cycles and lands today or later."""
|
||||
today = fields.Date.today()
|
||||
# Grid anchored 43 days back: [-43, -29, -15, -1, +13, ...]
|
||||
anchor = today - timedelta(days=43)
|
||||
cutoff_weekday = (today - timedelta(days=1)).weekday()
|
||||
group_order = self._create_recurring_group_order(
|
||||
"biweekly", anchor, str(cutoff_weekday)
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
group_order.cutoff_date,
|
||||
today + timedelta(days=13),
|
||||
"Grid must catch up in 14-day steps from the anchor to today",
|
||||
)
|
||||
|
||||
def test_monthly_cutoff_on_cutoff_day_and_future(self):
|
||||
"""Monthly cutoff falls on cutoff_day, today or later, within one cycle."""
|
||||
today = fields.Date.today()
|
||||
anchor = today - timedelta(days=40)
|
||||
cutoff_weekday = today.weekday()
|
||||
group_order = self._create_recurring_group_order(
|
||||
"monthly", anchor, str(cutoff_weekday)
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
group_order.cutoff_date.weekday(),
|
||||
cutoff_weekday,
|
||||
"Monthly cutoff must fall on the configured cutoff_day",
|
||||
)
|
||||
self.assertGreaterEqual(
|
||||
group_order.cutoff_date,
|
||||
today,
|
||||
"Monthly cutoff must be today or in the future",
|
||||
)
|
||||
self.assertLess(
|
||||
group_order.cutoff_date,
|
||||
today + timedelta(days=38),
|
||||
"Monthly cutoff must stay within one monthly cycle from today",
|
||||
)
|
||||
|
||||
def test_monthly_cycle_confirms_and_advances(self):
|
||||
"""Monthly: cron confirms the past cutoff, next cutoff keeps the weekday."""
|
||||
today = fields.Date.today()
|
||||
anchor = today - timedelta(days=40)
|
||||
cutoff_weekday = (today - timedelta(days=1)).weekday()
|
||||
group_order = self._create_recurring_group_order(
|
||||
"monthly", anchor, str(cutoff_weekday)
|
||||
)
|
||||
group_order.write({"cutoff_date": today - timedelta(days=1)})
|
||||
so = self._create_sale_order(group_order, self.member_1, self.consumer_group_1)
|
||||
|
||||
group_order._confirm_linked_sale_orders()
|
||||
group_order._close_one_time_order_if_ended()
|
||||
group_order._compute_cutoff_date()
|
||||
group_order._compute_pickup_date()
|
||||
group_order._compute_delivery_date()
|
||||
|
||||
so.invalidate_recordset()
|
||||
self.assertEqual(
|
||||
so.state,
|
||||
"sale",
|
||||
"Sale order should be confirmed - monthly cutoff has passed",
|
||||
)
|
||||
self.assertEqual(
|
||||
group_order.state,
|
||||
"open",
|
||||
"Monthly group order should stay open for the next cycle",
|
||||
)
|
||||
self.assertGreaterEqual(
|
||||
group_order.cutoff_date,
|
||||
today,
|
||||
"Next monthly cutoff must be today or in the future",
|
||||
)
|
||||
self.assertEqual(
|
||||
group_order.cutoff_date.weekday(),
|
||||
cutoff_weekday,
|
||||
"Next monthly cutoff must keep the configured cutoff_day",
|
||||
)
|
||||
|
||||
def test_biweekly_without_cutoff_day_is_skipped(self):
|
||||
"""Recurring order without cutoff_day has no cutoff and is never confirmed."""
|
||||
today = fields.Date.today()
|
||||
group_order = self.env["group.order"].create(
|
||||
{
|
||||
"name": "Test biweekly without cutoff_day",
|
||||
"group_ids": [(6, 0, [self.consumer_group_1.id])],
|
||||
"period": "biweekly",
|
||||
"pickup_day": "2",
|
||||
"start_date": today - timedelta(days=15),
|
||||
"state": "open",
|
||||
}
|
||||
)
|
||||
self.assertFalse(
|
||||
group_order.cutoff_date,
|
||||
"Recurring order without cutoff_day should have no cutoff_date",
|
||||
)
|
||||
so = self._create_sale_order(group_order, self.member_1, self.consumer_group_1)
|
||||
|
||||
group_order._confirm_linked_sale_orders()
|
||||
|
||||
so.invalidate_recordset()
|
||||
self.assertEqual(
|
||||
so.state,
|
||||
"draft",
|
||||
"Sale order should remain draft - no cutoff_day to evaluate",
|
||||
)
|
||||
self.assertEqual(group_order.state, "open", "Group order should stay open")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue