diff --git a/website_sale_aplicoop/README.md b/website_sale_aplicoop/README.md
index 63b947c..8f9e2fd 100644
--- a/website_sale_aplicoop/README.md
+++ b/website_sale_aplicoop/README.md
@@ -240,6 +240,29 @@ python -m pytest website_sale_aplicoop/tests/ -v
## Changelog
+### 18.0.1.3.1 (2026-02-18)
+- **Date Calculation Fixes (Critical)**:
+ - Fixed `_compute_cutoff_date` logic: Changed `days_ahead <= 0` to `days_ahead < 0` to allow cutoff_date to be the same day as today
+ - Enabled `store=True` for `delivery_date` field to persist calculated values and enable database filtering
+ - Added constraint `_check_cutoff_before_pickup` to validate that pickup_day >= cutoff_day in weekly orders
+ - Added `@api.onchange` methods for immediate UI feedback when changing cutoff_day or pickup_day
+- **Automatic Date Updates**:
+ - Created daily cron job `_cron_update_dates` to automatically recalculate dates for active orders
+ - Ensures computed dates stay current as time passes
+- **UI Improvements**:
+ - Added "Calculated Dates" section in form view showing readonly cutoff_date, pickup_date, and delivery_date
+ - Improved visibility of automatically calculated dates for administrators
+- **Testing**:
+ - Added 6 regression tests with `@tagged('post_install', 'date_calculations')`:
+ - `test_cutoff_same_day_as_today_bug_fix`: Validates cutoff can be today
+ - `test_delivery_date_stored_correctly`: Ensures delivery_date persistence
+ - `test_constraint_cutoff_before_pickup_invalid`: Tests invalid configurations are rejected
+ - `test_constraint_cutoff_before_pickup_valid`: Tests valid configurations work
+ - `test_all_weekday_combinations_consistency`: Tests all 49 date combinations
+ - `test_cron_update_dates_executes`: Validates cron job execution
+- **Documentation**:
+ - Documented that this is a more robust fix than v18.0.1.2.0, addressing edge cases in date calculations
+
### 18.0.1.3.0 (2026-02-16)
- **Performance**: Lazy loading of products for faster page loads
- Configurable product pagination (default: 20 per page)
@@ -306,7 +329,7 @@ For issues, feature requests, or contributions:
---
-**Version:** 18.0.1.2.0
+**Version:** 18.0.1.3.1
**Odoo:** 18.0+
**License:** AGPL-3
**Maintainer:** Criptomart SL
diff --git a/website_sale_aplicoop/__manifest__.py b/website_sale_aplicoop/__manifest__.py
index 64d5662..8f1a7fe 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.1.1",
+ "version": "18.0.1.3.1",
"category": "Website/Sale",
"summary": "Modern replacement of legacy Aplicoop - Collaborative consumption group orders",
"author": "Odoo Community Association (OCA), Criptomart",
@@ -24,6 +24,8 @@
"data/groups.xml",
# Datos: Menús del website
"data/website_menus.xml",
+ # Datos: Cron jobs
+ "data/cron.xml",
# Vistas de seguridad
"security/ir.model.access.csv",
"security/record_rules.xml",
diff --git a/website_sale_aplicoop/data/cron.xml b/website_sale_aplicoop/data/cron.xml
new file mode 100644
index 0000000..7194ae1
--- /dev/null
+++ b/website_sale_aplicoop/data/cron.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+ Group Order: Update Dates Daily
+
+ code
+ model._cron_update_dates()
+ 1
+ days
+
+
+
+
diff --git a/website_sale_aplicoop/models/group_order.py b/website_sale_aplicoop/models/group_order.py
index 643fb33..05f6444 100644
--- a/website_sale_aplicoop/models/group_order.py
+++ b/website_sale_aplicoop/models/group_order.py
@@ -154,7 +154,7 @@ class GroupOrder(models.Model):
delivery_date = fields.Date(
string="Delivery Date",
compute="_compute_delivery_date",
- store=False,
+ store=True,
readonly=True,
help="Calculated delivery date (pickup date + 1 day)",
)
@@ -503,10 +503,11 @@ class GroupOrder(models.Model):
# Calculate days to NEXT occurrence of cutoff_day
days_ahead = target_weekday - current_weekday
- if days_ahead <= 0:
- # Target day already passed this week or is today
+ if days_ahead < 0:
+ # Target day already passed this week
# Jump to next week's occurrence
days_ahead += 7
+ # If days_ahead == 0, cutoff is today (allowed)
record.cutoff_date = reference_date + timedelta(days=days_ahead)
_logger.info(
@@ -534,3 +535,64 @@ class GroupOrder(models.Model):
)
else:
record.delivery_date = None
+
+ # === Constraints ===
+
+ @api.constrains("cutoff_day", "pickup_day", "period")
+ def _check_cutoff_before_pickup(self):
+ """Validate that pickup_day comes after or equals cutoff_day in weekly orders.
+
+ For weekly orders, if pickup_day < cutoff_day numerically, it means pickup
+ would be scheduled BEFORE cutoff in the same week cycle, which is illogical.
+
+ Example:
+ - cutoff_day=3 (Thursday), pickup_day=1 (Tuesday): INVALID
+ (pickup Tuesday would be before cutoff Thursday)
+ - cutoff_day=1 (Tuesday), pickup_day=5 (Saturday): VALID
+ (pickup Saturday is after cutoff Tuesday)
+ - cutoff_day=5 (Saturday), pickup_day=5 (Saturday): VALID
+ (same day allowed)
+ """
+ for record in self:
+ if record.cutoff_day and record.pickup_day and record.period == "weekly":
+ cutoff = int(record.cutoff_day)
+ pickup = int(record.pickup_day)
+ if pickup < cutoff:
+ pickup_name = dict(self._get_day_selection())[str(pickup)]
+ cutoff_name = dict(self._get_day_selection())[str(cutoff)]
+ raise ValidationError(
+ _(
+ "For weekly orders, pickup day ({pickup}) must be after or equal to "
+ "cutoff day ({cutoff}) in the same week. Current configuration would "
+ "put pickup before cutoff, which is illogical."
+ ).format(pickup=pickup_name, cutoff=cutoff_name)
+ )
+
+ # === Onchange Methods ===
+
+ @api.onchange("cutoff_day", "start_date")
+ def _onchange_cutoff_day(self):
+ """Force recompute cutoff_date on UI change for immediate feedback."""
+ self._compute_cutoff_date()
+
+ @api.onchange("pickup_day", "cutoff_day", "start_date")
+ def _onchange_pickup_day(self):
+ """Force recompute pickup_date on UI change for immediate feedback."""
+ self._compute_pickup_date()
+
+ # === Cron Methods ===
+
+ @api.model
+ def _cron_update_dates(self):
+ """Cron job to recalculate dates for active orders daily.
+
+ This ensures that computed dates stay up-to-date as time passes.
+ Only updates orders in 'draft' or 'open' states.
+ """
+ orders = self.search([("state", "in", ["draft", "open"])])
+ _logger.info("Cron: Updating dates for %d active group orders", len(orders))
+ for order in orders:
+ order._compute_cutoff_date()
+ order._compute_pickup_date()
+ order._compute_delivery_date()
+ _logger.info("Cron: Date update completed")
diff --git a/website_sale_aplicoop/tests/test_date_calculations.py b/website_sale_aplicoop/tests/test_date_calculations.py
index ec6aa1b..bc28323 100644
--- a/website_sale_aplicoop/tests/test_date_calculations.py
+++ b/website_sale_aplicoop/tests/test_date_calculations.py
@@ -1,26 +1,31 @@
# Copyright 2026 Criptomart
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
-from datetime import datetime, timedelta
+from datetime import timedelta
-from odoo.tests.common import TransactionCase
from odoo import fields
+from odoo.exceptions import ValidationError
+from odoo.tests.common import TransactionCase
+from odoo.tests.common import tagged
+@tagged("post_install", "date_calculations")
class TestDateCalculations(TransactionCase):
- '''Test suite for date calculation methods in group.order model.'''
+ """Test suite for date calculation methods in group.order model."""
def setUp(self):
super().setUp()
# Create a test group
- self.group = self.env['res.partner'].create({
- 'name': 'Test Group',
- 'is_company': True,
- 'email': 'group@test.com',
- })
+ self.group = self.env["res.partner"].create(
+ {
+ "name": "Test Group",
+ "is_company": True,
+ "email": "group@test.com",
+ }
+ )
def test_compute_pickup_date_basic(self):
- '''Test pickup_date calculation returns next occurrence of pickup day.'''
+ """Test pickup_date calculation returns next occurrence of pickup day."""
# Use today as reference and calculate next Tuesday
today = fields.Date.today()
# Find next Sunday (weekday 6) from today
@@ -29,16 +34,18 @@ class TestDateCalculations(TransactionCase):
start_date = today
else:
start_date = today + timedelta(days=days_until_sunday)
-
+
# Create order with pickup_day = Tuesday (1), starting on Sunday
# NO cutoff_day to avoid dependency on cutoff_date
- order = self.env['group.order'].create({
- 'name': 'Test Order',
- 'group_ids': [(6, 0, [self.group.id])],
- 'start_date': start_date, # Sunday
- 'pickup_day': '1', # Tuesday
- 'cutoff_day': False, # Disable to avoid cutoff_date interference
- })
+ order = self.env["group.order"].create(
+ {
+ "name": "Test Order",
+ "group_ids": [(6, 0, [self.group.id])],
+ "start_date": start_date, # Sunday
+ "pickup_day": "1", # Tuesday
+ "cutoff_day": False, # Disable to avoid cutoff_date interference
+ }
+ )
# Force computation
order._compute_pickup_date()
@@ -48,11 +55,11 @@ class TestDateCalculations(TransactionCase):
self.assertEqual(
order.pickup_date,
expected_date,
- f"Expected {expected_date}, got {order.pickup_date}"
+ f"Expected {expected_date}, got {order.pickup_date}",
)
def test_compute_pickup_date_same_day(self):
- '''Test pickup_date when start_date is same weekday as pickup_day.'''
+ """Test pickup_date when start_date is same weekday as pickup_day."""
# Find next Tuesday from today
today = fields.Date.today()
days_until_tuesday = (1 - today.weekday()) % 7
@@ -60,14 +67,16 @@ class TestDateCalculations(TransactionCase):
start_date = today
else:
start_date = today + timedelta(days=days_until_tuesday)
-
+
# Start on Tuesday, pickup also Tuesday - should return next week's Tuesday
- order = self.env['group.order'].create({
- 'name': 'Test Order Same Day',
- 'group_ids': [(6, 0, [self.group.id])],
- 'start_date': start_date, # Tuesday
- 'pickup_day': '1', # Tuesday
- })
+ order = self.env["group.order"].create(
+ {
+ "name": "Test Order Same Day",
+ "group_ids": [(6, 0, [self.group.id])],
+ "start_date": start_date, # Tuesday
+ "pickup_day": "1", # Tuesday
+ }
+ )
order._compute_pickup_date()
@@ -76,13 +85,15 @@ class TestDateCalculations(TransactionCase):
self.assertEqual(order.pickup_date, expected_date)
def test_compute_pickup_date_no_start_date(self):
- '''Test pickup_date calculation when no start_date is set.'''
- order = self.env['group.order'].create({
- 'name': 'Test Order No Start',
- 'group_ids': [(6, 0, [self.group.id])],
- 'start_date': False,
- 'pickup_day': '1', # Tuesday
- })
+ """Test pickup_date calculation when no start_date is set."""
+ order = self.env["group.order"].create(
+ {
+ "name": "Test Order No Start",
+ "group_ids": [(6, 0, [self.group.id])],
+ "start_date": False,
+ "pickup_day": "1", # Tuesday
+ }
+ )
order._compute_pickup_date()
@@ -93,32 +104,43 @@ class TestDateCalculations(TransactionCase):
self.assertEqual(order.pickup_date.weekday(), 1) # 1 = Tuesday
def test_compute_pickup_date_without_pickup_day(self):
- '''Test pickup_date is None when pickup_day is not set.'''
- order = self.env['group.order'].create({
- 'name': 'Test Order No Pickup Day',
- 'group_ids': [(6, 0, [self.group.id])],
- 'start_date': fields.Date.today(),
- 'pickup_day': False,
- })
+ """Test pickup_date is None when pickup_day is not set."""
+ order = self.env["group.order"].create(
+ {
+ "name": "Test Order No Pickup Day",
+ "group_ids": [(6, 0, [self.group.id])],
+ "start_date": fields.Date.today(),
+ "pickup_day": False,
+ }
+ )
order._compute_pickup_date()
# In Odoo, computed Date fields return False (not None) when no value
self.assertFalse(order.pickup_date)
def test_compute_pickup_date_all_weekdays(self):
- '''Test pickup_date calculation for each day of the week.'''
- base_date = fields.Date.from_string('2026-02-02') # Monday
+ """Test pickup_date calculation for each day of the week."""
+ base_date = fields.Date.from_string("2026-02-02") # Monday
for day_num in range(7):
- day_name = ['Monday', 'Tuesday', 'Wednesday', 'Thursday',
- 'Friday', 'Saturday', 'Sunday'][day_num]
+ day_name = [
+ "Monday",
+ "Tuesday",
+ "Wednesday",
+ "Thursday",
+ "Friday",
+ "Saturday",
+ "Sunday",
+ ][day_num]
- order = self.env['group.order'].create({
- 'name': f'Test Order {day_name}',
- 'group_ids': [(6, 0, [self.group.id])],
- 'start_date': base_date,
- 'pickup_day': str(day_num),
- })
+ order = self.env["group.order"].create(
+ {
+ "name": f"Test Order {day_name}",
+ "group_ids": [(6, 0, [self.group.id])],
+ "start_date": base_date,
+ "pickup_day": str(day_num),
+ }
+ )
order._compute_pickup_date()
@@ -126,14 +148,14 @@ class TestDateCalculations(TransactionCase):
self.assertEqual(
order.pickup_date.weekday(),
day_num,
- f"Pickup date weekday should be {day_num} ({day_name})"
+ f"Pickup date weekday should be {day_num} ({day_name})",
)
# Verify it's after start_date
self.assertGreater(order.pickup_date, base_date)
def test_compute_delivery_date_basic(self):
- '''Test delivery_date is pickup_date + 1 day.'''
+ """Test delivery_date is pickup_date + 1 day."""
# Find next Sunday from today
today = fields.Date.today()
days_until_sunday = (6 - today.weekday()) % 7
@@ -141,13 +163,15 @@ class TestDateCalculations(TransactionCase):
start_date = today
else:
start_date = today + timedelta(days=days_until_sunday)
-
- order = self.env['group.order'].create({
- 'name': 'Test Delivery Date',
- 'group_ids': [(6, 0, [self.group.id])],
- 'start_date': start_date, # Sunday
- 'pickup_day': '1', # Tuesday = start_date + 2 days
- })
+
+ order = self.env["group.order"].create(
+ {
+ "name": "Test Delivery Date",
+ "group_ids": [(6, 0, [self.group.id])],
+ "start_date": start_date, # Sunday
+ "pickup_day": "1", # Tuesday = start_date + 2 days
+ }
+ )
order._compute_pickup_date()
order._compute_delivery_date()
@@ -159,13 +183,15 @@ class TestDateCalculations(TransactionCase):
self.assertEqual(order.delivery_date, expected_delivery)
def test_compute_delivery_date_without_pickup(self):
- '''Test delivery_date is None when pickup_date is not set.'''
- order = self.env['group.order'].create({
- 'name': 'Test No Delivery',
- 'group_ids': [(6, 0, [self.group.id])],
- 'start_date': fields.Date.today(),
- 'pickup_day': False, # No pickup day = no pickup_date
- })
+ """Test delivery_date is None when pickup_date is not set."""
+ order = self.env["group.order"].create(
+ {
+ "name": "Test No Delivery",
+ "group_ids": [(6, 0, [self.group.id])],
+ "start_date": fields.Date.today(),
+ "pickup_day": False, # No pickup day = no pickup_date
+ }
+ )
order._compute_pickup_date()
order._compute_delivery_date()
@@ -174,15 +200,17 @@ class TestDateCalculations(TransactionCase):
self.assertFalse(order.delivery_date)
def test_compute_cutoff_date_basic(self):
- '''Test cutoff_date calculation returns next occurrence of cutoff day.'''
+ """Test cutoff_date calculation returns next occurrence of cutoff day."""
# Create order with cutoff_day = Sunday (6)
# If today is Sunday, cutoff should be today (days_ahead = 0 is allowed)
- order = self.env['group.order'].create({
- 'name': 'Test Cutoff Date',
- 'group_ids': [(6, 0, [self.group.id])],
- 'start_date': fields.Date.from_string('2026-02-01'), # Sunday
- 'cutoff_day': '6', # Sunday
- })
+ order = self.env["group.order"].create(
+ {
+ "name": "Test Cutoff Date",
+ "group_ids": [(6, 0, [self.group.id])],
+ "start_date": fields.Date.from_string("2026-02-01"), # Sunday
+ "cutoff_day": "6", # Sunday
+ }
+ )
order._compute_cutoff_date()
@@ -193,20 +221,22 @@ class TestDateCalculations(TransactionCase):
self.assertEqual(order.cutoff_date.weekday(), 6) # Sunday
def test_compute_cutoff_date_without_cutoff_day(self):
- '''Test cutoff_date is None when cutoff_day is not set.'''
- order = self.env['group.order'].create({
- 'name': 'Test No Cutoff',
- 'group_ids': [(6, 0, [self.group.id])],
- 'start_date': fields.Date.today(),
- 'cutoff_day': False,
- })
+ """Test cutoff_date is None when cutoff_day is not set."""
+ order = self.env["group.order"].create(
+ {
+ "name": "Test No Cutoff",
+ "group_ids": [(6, 0, [self.group.id])],
+ "start_date": fields.Date.today(),
+ "cutoff_day": False,
+ }
+ )
order._compute_cutoff_date()
# In Odoo, computed Date fields return False (not None) when no value
self.assertFalse(order.cutoff_date)
def test_date_dependency_chain(self):
- '''Test that changing start_date triggers recomputation of date fields.'''
+ """Test that changing start_date triggers recomputation of date fields."""
# Find next Sunday from today
today = fields.Date.today()
days_until_sunday = (6 - today.weekday()) % 7
@@ -214,14 +244,16 @@ class TestDateCalculations(TransactionCase):
start_date = today
else:
start_date = today + timedelta(days=days_until_sunday)
-
- order = self.env['group.order'].create({
- 'name': 'Test Date Chain',
- 'group_ids': [(6, 0, [self.group.id])],
- 'start_date': start_date, # Dynamic Sunday
- 'pickup_day': '1', # Tuesday
- 'cutoff_day': '6', # Sunday
- })
+
+ order = self.env["group.order"].create(
+ {
+ "name": "Test Date Chain",
+ "group_ids": [(6, 0, [self.group.id])],
+ "start_date": start_date, # Dynamic Sunday
+ "pickup_day": "1", # Tuesday
+ "cutoff_day": "6", # Sunday
+ }
+ )
# Get initial dates
initial_pickup = order.pickup_date
@@ -230,7 +262,7 @@ class TestDateCalculations(TransactionCase):
# Change start_date to a week later
new_start_date = start_date + timedelta(days=7)
- order.write({'start_date': new_start_date})
+ order.write({"start_date": new_start_date})
# Verify pickup and delivery dates changed
self.assertNotEqual(order.pickup_date, initial_pickup)
@@ -242,17 +274,17 @@ class TestDateCalculations(TransactionCase):
self.assertEqual(delta.days, 1)
def test_pickup_date_no_extra_week_bug(self):
- '''Regression test: ensure pickup_date doesn't add extra week incorrectly.
+ """Regression test: ensure pickup_date doesn't add extra week incorrectly.
Bug context: Previously when cutoff_day >= pickup_day numerically,
logic incorrectly added 7 extra days even when pickup was already
ahead in the calendar.
- '''
+ """
# Scenario: Pickup Tuesday (1)
# Start: Sunday (dynamic)
# Expected pickup: Tuesday (2 days later, NOT +9 days)
# NOTE: NO cutoff_day to avoid cutoff_date dependency
-
+
# Find next Sunday from today
today = fields.Date.today()
days_until_sunday = (6 - today.weekday()) % 7
@@ -260,14 +292,16 @@ class TestDateCalculations(TransactionCase):
start_date = today
else:
start_date = today + timedelta(days=days_until_sunday)
-
- order = self.env['group.order'].create({
- 'name': 'Regression Test Extra Week',
- 'group_ids': [(6, 0, [self.group.id])],
- 'start_date': start_date, # Sunday (dynamic)
- 'pickup_day': '1', # Tuesday (numerically < 6)
- 'cutoff_day': False, # Disable to test pure start_date logic
- })
+
+ order = self.env["group.order"].create(
+ {
+ "name": "Regression Test Extra Week",
+ "group_ids": [(6, 0, [self.group.id])],
+ "start_date": start_date, # Sunday (dynamic)
+ "pickup_day": "1", # Tuesday (numerically < 6)
+ "cutoff_day": False, # Disable to test pure start_date logic
+ }
+ )
order._compute_pickup_date()
@@ -276,30 +310,30 @@ class TestDateCalculations(TransactionCase):
self.assertEqual(
order.pickup_date,
expected,
- f"Bug detected: pickup_date should be {expected} not {order.pickup_date}"
+ f"Bug detected: pickup_date should be {expected} not {order.pickup_date}",
)
# Verify it's exactly 2 days after start_date
delta = order.pickup_date - order.start_date
self.assertEqual(
- delta.days,
- 2,
- "Pickup should be 2 days after Sunday start_date"
+ delta.days, 2, "Pickup should be 2 days after Sunday start_date"
)
def test_multiple_orders_same_pickup_day(self):
- '''Test multiple orders with same pickup day get consistent dates.'''
- start = fields.Date.from_string('2026-02-01')
- pickup_day = '1' # Tuesday
+ """Test multiple orders with same pickup day get consistent dates."""
+ start = fields.Date.from_string("2026-02-01")
+ pickup_day = "1" # Tuesday
orders = []
for i in range(3):
- order = self.env['group.order'].create({
- 'name': f'Test Order {i}',
- 'group_ids': [(6, 0, [self.group.id])],
- 'start_date': start,
- 'pickup_day': pickup_day,
- })
+ order = self.env["group.order"].create(
+ {
+ "name": f"Test Order {i}",
+ "group_ids": [(6, 0, [self.group.id])],
+ "start_date": start,
+ "pickup_day": pickup_day,
+ }
+ )
orders.append(order)
# All should have same pickup_date
@@ -307,5 +341,229 @@ class TestDateCalculations(TransactionCase):
self.assertEqual(
len(set(pickup_dates)),
1,
- "All orders with same start_date and pickup_day should have same pickup_date"
+ "All orders with same start_date and pickup_day should have same pickup_date",
)
+
+ # === NEW REGRESSION TESTS (v18.0.1.3.1) ===
+
+ def test_cutoff_same_day_as_today_bug_fix(self):
+ """Regression test: cutoff_date should allow same day as today.
+
+ Bug fixed in v18.0.1.3.1: Previously, if cutoff_day == today.weekday(),
+ the system would incorrectly add 7 days, scheduling cutoff for next week.
+ Now cutoff_date can be today if cutoff_day matches today's weekday.
+ """
+ today = fields.Date.today()
+ cutoff_day = str(today.weekday()) # Same as today
+
+ order = self.env["group.order"].create(
+ {
+ "name": "Test Cutoff Today",
+ "group_ids": [(6, 0, [self.group.id])],
+ "start_date": today,
+ "cutoff_day": cutoff_day,
+ "period": "weekly",
+ }
+ )
+
+ # cutoff_date should be TODAY, not next week
+ self.assertEqual(
+ order.cutoff_date,
+ today,
+ f"Expected cutoff_date={today} (today), got {order.cutoff_date}. "
+ "Cutoff should be allowed on the same day.",
+ )
+
+ def test_delivery_date_stored_correctly(self):
+ """Regression test: delivery_date must be stored in database.
+
+ Bug fixed in v18.0.1.3.1: delivery_date had store=False, causing
+ inconsistent values and inability to search/filter by this field.
+ Now delivery_date is stored (store=True).
+ """
+ today = fields.Date.today()
+ # Set pickup for next Monday
+ days_until_monday = (0 - today.weekday()) % 7
+ if days_until_monday == 0:
+ days_until_monday = 7
+ start_date = today + timedelta(days=days_until_monday - 1)
+
+ order = self.env["group.order"].create(
+ {
+ "name": "Test Delivery Stored",
+ "group_ids": [(6, 0, [self.group.id])],
+ "start_date": start_date,
+ "pickup_day": "0", # Monday
+ "period": "weekly",
+ }
+ )
+
+ # Force computation
+ order._compute_pickup_date()
+ order._compute_delivery_date()
+
+ expected_delivery = order.pickup_date + timedelta(days=1)
+ self.assertEqual(
+ order.delivery_date,
+ expected_delivery,
+ f"Expected delivery_date={expected_delivery}, got {order.delivery_date}",
+ )
+
+ # Verify it's stored: read from database
+ order_from_db = self.env["group.order"].browse(order.id)
+ self.assertEqual(
+ order_from_db.delivery_date,
+ expected_delivery,
+ "delivery_date should be persisted in database (store=True)",
+ )
+
+ def test_constraint_cutoff_before_pickup_invalid(self):
+ """Test constraint: pickup_day must be >= cutoff_day for weekly orders.
+
+ New constraint in v18.0.1.3.1: For weekly orders, if pickup_day < cutoff_day
+ numerically, it creates an illogical scenario where pickup would be
+ scheduled before cutoff in the same week cycle.
+ """
+ today = fields.Date.today()
+
+ # Invalid configuration: pickup (Tuesday=1) < cutoff (Thursday=3)
+ with self.assertRaises(
+ ValidationError,
+ msg="Should raise ValidationError for pickup_day < cutoff_day",
+ ):
+ self.env["group.order"].create(
+ {
+ "name": "Invalid Order",
+ "group_ids": [(6, 0, [self.group.id])],
+ "start_date": today,
+ "cutoff_day": "3", # Thursday
+ "pickup_day": "1", # Tuesday (BEFORE Thursday)
+ "period": "weekly",
+ }
+ )
+
+ def test_constraint_cutoff_before_pickup_valid(self):
+ """Test constraint allows valid configurations.
+
+ Valid scenarios:
+ - pickup_day > cutoff_day: pickup is after cutoff ✓
+ - pickup_day == cutoff_day: same day allowed ✓
+ """
+ today = fields.Date.today()
+
+ # Valid: pickup (Saturday=5) > cutoff (Tuesday=1)
+ order1 = self.env["group.order"].create(
+ {
+ "name": "Valid Order 1",
+ "group_ids": [(6, 0, [self.group.id])],
+ "start_date": today,
+ "cutoff_day": "1", # Tuesday
+ "pickup_day": "5", # Saturday (AFTER Tuesday)
+ "period": "weekly",
+ }
+ )
+ self.assertTrue(order1.id, "Valid configuration should create order")
+
+ # Valid: pickup == cutoff (same day)
+ order2 = self.env["group.order"].create(
+ {
+ "name": "Valid Order 2",
+ "group_ids": [(6, 0, [self.group.id])],
+ "start_date": today,
+ "cutoff_day": "5", # Saturday
+ "pickup_day": "5", # Saturday (SAME DAY)
+ "period": "weekly",
+ }
+ )
+ self.assertTrue(order2.id, "Same day configuration should be allowed")
+
+ def test_all_weekday_combinations_consistency(self):
+ """Test that all valid weekday combinations produce consistent results.
+
+ This regression test ensures the date calculation logic works correctly
+ for all 49 combinations of start_date × pickup_day (7 × 7).
+ """
+ today = fields.Date.today()
+ errors = []
+
+ for start_offset in range(7): # 7 possible start days
+ start_date = today + timedelta(days=start_offset)
+
+ for pickup_weekday in range(7): # 7 possible pickup days
+ order = self.env["group.order"].create(
+ {
+ "name": f"Test S{start_offset}P{pickup_weekday}",
+ "group_ids": [(6, 0, [self.group.id])],
+ "start_date": start_date,
+ "pickup_day": str(pickup_weekday),
+ "period": "weekly",
+ }
+ )
+
+ # Validate pickup_date is set
+ if not order.pickup_date:
+ errors.append(
+ f"start_offset={start_offset}, pickup_day={pickup_weekday}: "
+ f"pickup_date is None"
+ )
+ continue
+
+ # Validate pickup_date is in the future or today
+ if order.pickup_date < start_date:
+ errors.append(
+ f"start_offset={start_offset}, pickup_day={pickup_weekday}: "
+ f"pickup_date {order.pickup_date} < start_date {start_date}"
+ )
+
+ # Validate pickup_date weekday matches pickup_day
+ if order.pickup_date.weekday() != pickup_weekday:
+ errors.append(
+ f"start_offset={start_offset}, pickup_day={pickup_weekday}: "
+ f"pickup_date weekday is {order.pickup_date.weekday()}, "
+ f"expected {pickup_weekday}"
+ )
+
+ self.assertEqual(
+ len(errors),
+ 0,
+ f"Found {len(errors)} errors in weekday combinations:\n"
+ + "\n".join(errors),
+ )
+
+ def test_cron_update_dates_executes(self):
+ """Test that cron job method executes without errors.
+
+ New feature in v18.0.1.3.1: Daily cron job to recalculate dates
+ for active orders to keep them up-to-date as time passes.
+ """
+ today = fields.Date.today()
+
+ # Create multiple orders in different states
+ orders = []
+ for i, state in enumerate(["draft", "open", "closed"]):
+ order = self.env["group.order"].create(
+ {
+ "name": f"Test Cron Order {state}",
+ "group_ids": [(6, 0, [self.group.id])],
+ "start_date": today,
+ "pickup_day": str((i + 1) % 7),
+ "cutoff_day": str(i % 7),
+ "period": "weekly",
+ "state": state,
+ }
+ )
+ orders.append(order)
+
+ # Execute cron method (should not raise errors)
+ try:
+ self.env["group.order"]._cron_update_dates()
+ except Exception as e:
+ self.fail(f"Cron method raised exception: {e}")
+
+ # Verify dates are still valid for active orders
+ active_orders = [o for o in orders if o.state in ["draft", "open"]]
+ for order in active_orders:
+ self.assertIsNotNone(
+ order.pickup_date,
+ f"Pickup date should be set for active order {order.name}",
+ )
diff --git a/website_sale_aplicoop/views/group_order_views.xml b/website_sale_aplicoop/views/group_order_views.xml
index c7d9946..ed87af4 100644
--- a/website_sale_aplicoop/views/group_order_views.xml
+++ b/website_sale_aplicoop/views/group_order_views.xml
@@ -63,6 +63,15 @@
+
+
+
+
+
+
+
+
+