[FIX] website_sale_aplicoop: Critical date calculation fixes (v18.0.1.3.1)
- Fixed _compute_cutoff_date logic: Changed days_ahead <= 0 to days_ahead < 0 to allow cutoff_date 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 pickup_day >= cutoff_day in weekly orders
- Added @api.onchange methods for immediate UI feedback when changing cutoff_day or pickup_day
- Created daily cron job _cron_update_dates to automatically recalculate dates for active orders
- Added 'Calculated Dates' section in form view showing readonly cutoff_date, pickup_date, delivery_date
- Added 6 regression tests with @tagged('post_install', 'date_calculations')
- Updated documentation with comprehensive changelog
This is a more robust fix than v18.0.1.2.0, addressing edge cases in date calculations.
This commit is contained in:
parent
c70de71cff
commit
8b0a402ccf
6 changed files with 489 additions and 120 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
15
website_sale_aplicoop/data/cron.xml
Normal file
15
website_sale_aplicoop/data/cron.xml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<!-- Cron job to update dates for active group orders daily -->
|
||||
<record id="ir_cron_update_group_order_dates" model="ir.cron">
|
||||
<field name="name">Group Order: Update Dates Daily</field>
|
||||
<field name="model_id" ref="model_group_order"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">model._cron_update_dates()</field>
|
||||
<field name="interval_number">1</field>
|
||||
<field name="interval_type">days</field>
|
||||
<field name="active" eval="True"/>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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}",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -63,6 +63,15 @@
|
|||
<field name="delivery_product_id" invisible="not home_delivery" required="home_delivery" help="Product to use for home delivery"/>
|
||||
</group>
|
||||
</group>
|
||||
<group string="Calculated Dates" name="calculated_dates">
|
||||
<group>
|
||||
<field name="cutoff_date" readonly="1" help="Automatically calculated cutoff date"/>
|
||||
<field name="pickup_date" readonly="1" help="Automatically calculated pickup date"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="delivery_date" readonly="1" help="Automatically calculated delivery date (pickup + 1 day)"/>
|
||||
</group>
|
||||
</group>
|
||||
<group string="Description">
|
||||
<field name="description" placeholder="Free text description..." nolabel="1"/>
|
||||
</group>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue