Pricing Before/After

This commit is contained in:
2026-06-15 08:28:59 +02:00
parent 667b66487d
commit d705d86e80
2 changed files with 140 additions and 18 deletions

View File

@@ -48,11 +48,12 @@ WEEKDAY_MAP = {
'sunday': 6
}
DAYS = [
(None,''),
('monday', 'Monday'),
('tuesday', 'Tuesday'),
('wednesday', 'Wednesday'),
DAYS = [
(None,''),
('before_after', 'Before/After'),
('monday', 'Monday'),
('tuesday', 'Tuesday'),
('wednesday', 'Wednesday'),
('thursday', 'Thursday'),
('friday', 'Friday'),
('saturday', 'Saturday'),
@@ -1695,16 +1696,46 @@ class Period(ModelSQL,ModelView):
result = cal.IsQuote(date)
return True if result is None else bool(result)
@staticmethod
def _as_datetime(date):
return datetime.datetime(date.year, date.month, date.day)
def _available_quotation_dates(self, trigger_date, count, step, cal=None):
dates = []
current = trigger_date + datetime.timedelta(days=step)
while len(dates) < count:
date = self._as_datetime(current)
if self._calendar_is_quote(cal, date):
dates.append(date)
current += datetime.timedelta(days=step)
if step < 0:
dates.reverse()
return dates
def getDates(self,t, cal=None):
date_from = None
date_to = None
dates = []
if t:
if self.every:
if t:
j = self.every
if j not in WEEKDAY_MAP:
raise ValueError(f"Invalid day : '{j}'")
if t:
j = self.every
if j == 'before_after' and self.nb_quotation:
count = abs(self.nb_quotation)
dates.extend(
self._available_quotation_dates(
t, count, -1, cal))
trigger_date = self._as_datetime(t)
if (self.include
and self._calendar_is_quote(
cal, trigger_date)):
dates.append(trigger_date)
dates.extend(
self._available_quotation_dates(
t, count, 1, cal))
return date_from, date_to, dates
if j not in WEEKDAY_MAP:
raise ValueError(f"Invalid day : '{j}'")
weekday_target = WEEKDAY_MAP[j]
@@ -1730,15 +1761,16 @@ class Period(ModelSQL,ModelView):
elif self.nb_quotation:
step = 1 if self.nb_quotation > 0 else -1
current = t + datetime.timedelta(days=step)
while len(dates) < abs(self.nb_quotation):
date = datetime.datetime(
current.year, current.month, current.day)
if self._calendar_is_quote(cal, date):
dates.append(date)
current += datetime.timedelta(days=step)
if self.nb_quotation < 0:
dates.reverse()
dates.extend(
self._available_quotation_dates(
t, abs(self.nb_quotation), step, cal))
trigger_date = self._as_datetime(t)
if (self.include
and self._calendar_is_quote(cal, trigger_date)):
if self.nb_quotation > 0:
dates.insert(0, trigger_date)
else:
dates.append(trigger_date)
else:
if self.startday == 'before':
date_from = t - datetime.timedelta(days=(self.nbds if self.nbds else 0))

View File

@@ -3385,6 +3385,27 @@ class PurchaseTradeTestCase(ModuleTestCase):
datetime.date(2026, 5, 18),
])
def test_pricing_period_blank_every_include_counts_trigger_plus_after_dates(self):
'Blank Every with Inc. returns trigger date plus quotations after'
Period = Pool().get('pricing.period')
period = Period()
period.trigger = 'bldate'
period.include = True
period.every = None
period.nb_quotation = 4
_, _, dates = period.getDates(datetime.date(2026, 5, 12))
self.assertEqual(
[date.date() for date in dates],
[
datetime.date(2026, 5, 12),
datetime.date(2026, 5, 13),
datetime.date(2026, 5, 14),
datetime.date(2026, 5, 15),
datetime.date(2026, 5, 16),
])
def test_pricing_period_blank_every_negative_uses_available_dates_before_trigger(self):
'Blank Every and negative Nb quotation also returns dates before trigger'
Period = Pool().get('pricing.period')
@@ -3404,6 +3425,75 @@ class PurchaseTradeTestCase(ModuleTestCase):
datetime.date(2026, 5, 11),
])
def test_pricing_period_before_after_returns_symmetric_dates(self):
'Before/After returns quotations before and after trigger'
Period = Pool().get('pricing.period')
period = Period()
period.trigger = 'bldate'
period.include = False
period.every = 'before_after'
period.nb_quotation = 2
_, _, dates = period.getDates(datetime.date(2026, 5, 12))
self.assertEqual(
[date.date() for date in dates],
[
datetime.date(2026, 5, 10),
datetime.date(2026, 5, 11),
datetime.date(2026, 5, 13),
datetime.date(2026, 5, 14),
])
def test_pricing_period_before_after_include_adds_trigger_date(self):
'Before/After with Inc. adds trigger date between both sides'
Period = Pool().get('pricing.period')
period = Period()
period.trigger = 'bldate'
period.include = True
period.every = 'before_after'
period.nb_quotation = 2
_, _, dates = period.getDates(datetime.date(2026, 5, 12))
self.assertEqual(
[date.date() for date in dates],
[
datetime.date(2026, 5, 10),
datetime.date(2026, 5, 11),
datetime.date(2026, 5, 12),
datetime.date(2026, 5, 13),
datetime.date(2026, 5, 14),
])
def test_pricing_period_before_after_skips_calendar_exclusions(self):
'Before/After uses available quotations around trigger date'
Period = Pool().get('pricing.period')
calendar = Mock()
excluded = {
datetime.date(2026, 5, 11),
datetime.date(2026, 5, 13),
}
calendar.IsQuote = Mock(
side_effect=lambda date: date.date() not in excluded)
period = Period()
period.trigger = 'bldate'
period.include = True
period.every = 'before_after'
period.nb_quotation = 2
_, _, dates = period.getDates(datetime.date(2026, 5, 12), calendar)
self.assertEqual(
[date.date() for date in dates],
[
datetime.date(2026, 5, 9),
datetime.date(2026, 5, 10),
datetime.date(2026, 5, 12),
datetime.date(2026, 5, 14),
datetime.date(2026, 5, 15),
])
def test_pricing_trigger_application_period_defaults_to_pricing_period(self):
'application period defaults to pricing period without being cleared'
Trigger = Pool().get('pricing.trigger')