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 @@ + + + + + + + + +