Pricing Before/After
This commit is contained in:
@@ -48,11 +48,12 @@ WEEKDAY_MAP = {
|
|||||||
'sunday': 6
|
'sunday': 6
|
||||||
}
|
}
|
||||||
|
|
||||||
DAYS = [
|
DAYS = [
|
||||||
(None,''),
|
(None,''),
|
||||||
('monday', 'Monday'),
|
('before_after', 'Before/After'),
|
||||||
('tuesday', 'Tuesday'),
|
('monday', 'Monday'),
|
||||||
('wednesday', 'Wednesday'),
|
('tuesday', 'Tuesday'),
|
||||||
|
('wednesday', 'Wednesday'),
|
||||||
('thursday', 'Thursday'),
|
('thursday', 'Thursday'),
|
||||||
('friday', 'Friday'),
|
('friday', 'Friday'),
|
||||||
('saturday', 'Saturday'),
|
('saturday', 'Saturday'),
|
||||||
@@ -1695,16 +1696,46 @@ class Period(ModelSQL,ModelView):
|
|||||||
result = cal.IsQuote(date)
|
result = cal.IsQuote(date)
|
||||||
return True if result is None else bool(result)
|
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):
|
def getDates(self,t, cal=None):
|
||||||
date_from = None
|
date_from = None
|
||||||
date_to = None
|
date_to = None
|
||||||
dates = []
|
dates = []
|
||||||
if t:
|
if t:
|
||||||
if self.every:
|
if self.every:
|
||||||
if t:
|
if t:
|
||||||
j = self.every
|
j = self.every
|
||||||
if j not in WEEKDAY_MAP:
|
if j == 'before_after' and self.nb_quotation:
|
||||||
raise ValueError(f"Invalid day : '{j}'")
|
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]
|
weekday_target = WEEKDAY_MAP[j]
|
||||||
|
|
||||||
@@ -1730,15 +1761,16 @@ class Period(ModelSQL,ModelView):
|
|||||||
|
|
||||||
elif self.nb_quotation:
|
elif self.nb_quotation:
|
||||||
step = 1 if self.nb_quotation > 0 else -1
|
step = 1 if self.nb_quotation > 0 else -1
|
||||||
current = t + datetime.timedelta(days=step)
|
dates.extend(
|
||||||
while len(dates) < abs(self.nb_quotation):
|
self._available_quotation_dates(
|
||||||
date = datetime.datetime(
|
t, abs(self.nb_quotation), step, cal))
|
||||||
current.year, current.month, current.day)
|
trigger_date = self._as_datetime(t)
|
||||||
if self._calendar_is_quote(cal, date):
|
if (self.include
|
||||||
dates.append(date)
|
and self._calendar_is_quote(cal, trigger_date)):
|
||||||
current += datetime.timedelta(days=step)
|
if self.nb_quotation > 0:
|
||||||
if self.nb_quotation < 0:
|
dates.insert(0, trigger_date)
|
||||||
dates.reverse()
|
else:
|
||||||
|
dates.append(trigger_date)
|
||||||
else:
|
else:
|
||||||
if self.startday == 'before':
|
if self.startday == 'before':
|
||||||
date_from = t - datetime.timedelta(days=(self.nbds if self.nbds else 0))
|
date_from = t - datetime.timedelta(days=(self.nbds if self.nbds else 0))
|
||||||
|
|||||||
@@ -3385,6 +3385,27 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
|||||||
datetime.date(2026, 5, 18),
|
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):
|
def test_pricing_period_blank_every_negative_uses_available_dates_before_trigger(self):
|
||||||
'Blank Every and negative Nb quotation also returns dates before trigger'
|
'Blank Every and negative Nb quotation also returns dates before trigger'
|
||||||
Period = Pool().get('pricing.period')
|
Period = Pool().get('pricing.period')
|
||||||
@@ -3404,6 +3425,75 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
|||||||
datetime.date(2026, 5, 11),
|
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):
|
def test_pricing_trigger_application_period_defaults_to_pricing_period(self):
|
||||||
'application period defaults to pricing period without being cleared'
|
'application period defaults to pricing period without being cleared'
|
||||||
Trigger = Pool().get('pricing.trigger')
|
Trigger = Pool().get('pricing.trigger')
|
||||||
|
|||||||
Reference in New Issue
Block a user