diff --git a/stock_release_channel/models/stock_release_channel.py b/stock_release_channel/models/stock_release_channel.py index 938db4d9187..808510665d8 100644 --- a/stock_release_channel/models/stock_release_channel.py +++ b/stock_release_channel/models/stock_release_channel.py @@ -6,6 +6,7 @@ import logging from collections import defaultdict from copy import deepcopy +from datetime import timedelta from operator import itemgetter import pytz @@ -21,6 +22,9 @@ time as safe_time, ) +# This limit is arbitrary but we expect to have a delivery slot within this limit +DELIVERY_DATE_COMPUTATION_LIMIT_DAYS = 120 # in days + _logger = logging.getLogger(__name__) @@ -949,10 +953,12 @@ def _get_earliest_delivery_date(self, partner, order_dt): """ self.ensure_one() best_dt = order_dt + limit_dt = order_dt + timedelta(days=DELIVERY_DATE_COMPUTATION_LIMIT_DAYS) for step in self._delivery_date_steps: funcs = self._delivery_date_generators.get(step) if not funcs: continue + _logger.debug(f"Compute earliest delivery date for {step}") generators = [] best_generators = [] start_dt = best_dt @@ -968,14 +974,23 @@ def _get_earliest_delivery_date(self, partner, order_dt): best_generators.append(gen) # loop until all generators return the same last date while len(generators) != len(best_generators): + if best_dt > limit_dt: + _logger.debug( + "Computation for earliest delivery date reached the limit" + ) + best_dt = None + break for gen in generators: if gen in best_generators: continue best_dt = gen.send(previous_dt := best_dt) if best_dt != previous_dt: + _logger.debug(f"New {step} date {best_dt} from {gen}") best_generators = [gen] else: best_generators.append(gen) for gen in generators: gen.close() + if best_dt is None: + break return best_dt diff --git a/stock_release_channel/tests/models/generator_test_impossible.py b/stock_release_channel/tests/models/generator_test_impossible.py new file mode 100644 index 00000000000..50d6a500197 --- /dev/null +++ b/stock_release_channel/tests/models/generator_test_impossible.py @@ -0,0 +1,33 @@ +# Copyright 2025 Jacques-Etienne Baudoux (BCIM) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) + +from datetime import timedelta + +from odoo import models + + +class StockReleaseChannel(models.Model): + _inherit = ( # pylint: disable=consider-merging-classes-inherited + "stock.release.channel" + ) + + @property + def _delivery_date_generators(self): + d = {} + d["preparation"] = [ + self._next_delivery_date_one_day, + self._next_delivery_date_one_year, + ] + return d + + def _next_delivery_date_one_day(self, delivery_date, partner=None): + """Get a next valid delivery date after 1 day""" + later = delivery_date + timedelta(days=1) + while True: + delivery_date = yield max(delivery_date, later) + + def _next_delivery_date_one_year(self, delivery_date, partner=None): + """Get a next valid delivery outside the limit""" + later = delivery_date + timedelta(days=365) + while True: + delivery_date = yield max(delivery_date, later) diff --git a/stock_release_channel/tests/test_release_channel_delivery_date.py b/stock_release_channel/tests/test_release_channel_delivery_date.py index 48c527341ea..42a1a8bc80b 100644 --- a/stock_release_channel/tests/test_release_channel_delivery_date.py +++ b/stock_release_channel/tests/test_release_channel_delivery_date.py @@ -5,7 +5,12 @@ from odoo import fields -from .common import ReleaseChannelCase, StockReleaseChannelDeliveryDateCommon +from .common import ( + FakeModelLoader, + ReleaseChannelCase, + StockReleaseChannelDeliveryDateCommon, + TransactionCase, +) to_datetime = fields.Datetime.to_datetime @@ -19,6 +24,32 @@ def test_delivery_date(self): self.assertEqual(dt, to_datetime("2025-01-04 10:00:00")) +class TestReleaseChannelDeliveryDateImpossible(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.loader = FakeModelLoader(cls.env, cls.__module__) + cls.loader.backup_registry() + from .models.generator_test_impossible import StockReleaseChannel + + cls.loader.update_registry((StockReleaseChannel,)) + + cls.partner = cls.env.ref("base.main_partner") + cls.channel = cls.env.ref("stock_release_channel.stock_release_channel_default") + + @classmethod + def tearDownClass(cls): + cls.loader.restore_registry() + return super().tearDownClass() + + @freeze_time("2025-01-02 10:00:00") + def test_delivery_date(self): + """Test generator on channel object""" + now = fields.Datetime.now() + dt = self.channel._get_earliest_delivery_date(self.partner, now) + self.assertEqual(dt, None) + + class TestReleaseChannelDeliveryDate(ReleaseChannelCase): def test_compute_delivery_date(self): """Test delivery date computes with registered generators diff --git a/stock_release_channel_plan_shipment_lead_time/models/stock_release_channel.py b/stock_release_channel_plan_shipment_lead_time/models/stock_release_channel.py index 780f5c7af6f..dc395f09cbc 100644 --- a/stock_release_channel_plan_shipment_lead_time/models/stock_release_channel.py +++ b/stock_release_channel_plan_shipment_lead_time/models/stock_release_channel.py @@ -24,7 +24,9 @@ class StockReleaseChannel(models.Model): store=True, ) - @api.depends("delivery_weekday_ids", "shipment_lead_time") + @api.depends( + "delivery_weekday_ids", "shipment_lead_time", "warehouse_id.calendar_id" + ) def _compute_preparation_weekday_ids(self): """Preparation weekdays are delivery weekdays - lead time""" for channel in self: