Pricing adj
This commit is contained in:
@@ -50,7 +50,6 @@ WEEKDAY_MAP = {
|
|||||||
|
|
||||||
DAYS = [
|
DAYS = [
|
||||||
(None,''),
|
(None,''),
|
||||||
('before_after', 'Before/After'),
|
|
||||||
('monday', 'Monday'),
|
('monday', 'Monday'),
|
||||||
('tuesday', 'Tuesday'),
|
('tuesday', 'Tuesday'),
|
||||||
('wednesday', 'Wednesday'),
|
('wednesday', 'Wednesday'),
|
||||||
@@ -1666,9 +1665,11 @@ class Period(ModelSQL,ModelView):
|
|||||||
endday = fields.Selection(DAYTYPES,"End day")
|
endday = fields.Selection(DAYTYPES,"End day")
|
||||||
nbde = fields.Integer("Nb")
|
nbde = fields.Integer("Nb")
|
||||||
nbms = fields.Integer("Starting month")
|
nbms = fields.Integer("Starting month")
|
||||||
nbme = fields.Integer("Ending month")
|
nbme = fields.Integer("Ending month")
|
||||||
every = fields.Selection(DAYS,"Every")
|
every = fields.Selection(DAYS,"Every")
|
||||||
nb_quotation = fields.Integer("Nb quotation")
|
nb_quotation_before = fields.Integer("Nb quotes before")
|
||||||
|
nb_quotation_after = fields.Integer("Nb quotes after")
|
||||||
|
nb_quotation = fields.Integer("Nb quotation")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def default_nbds(cls):
|
def default_nbds(cls):
|
||||||
@@ -1683,8 +1684,16 @@ class Period(ModelSQL,ModelView):
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def default_nbme(cls):
|
def default_nbme(cls):
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def default_nb_quotation_before(cls):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def default_nb_quotation_after(cls):
|
||||||
|
return 0
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _calendar_is_quote(cal, date):
|
def _calendar_is_quote(cal, date):
|
||||||
@@ -1712,19 +1721,44 @@ class Period(ModelSQL,ModelView):
|
|||||||
dates.reverse()
|
dates.reverse()
|
||||||
return dates
|
return dates
|
||||||
|
|
||||||
|
def _quotation_counts(self):
|
||||||
|
before = self.nb_quotation_before or 0
|
||||||
|
after = self.nb_quotation_after or 0
|
||||||
|
if before or after or not self.nb_quotation:
|
||||||
|
return max(before, 0), max(after, 0)
|
||||||
|
if self.every == 'before_after':
|
||||||
|
count = abs(self.nb_quotation)
|
||||||
|
return count, count
|
||||||
|
if self.nb_quotation < 0:
|
||||||
|
return abs(self.nb_quotation), 0
|
||||||
|
return 0, self.nb_quotation
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _weekday_dates(trigger_date, weekday_target, count, step):
|
||||||
|
dates = []
|
||||||
|
current = trigger_date
|
||||||
|
while len(dates) < count:
|
||||||
|
current += datetime.timedelta(days=step)
|
||||||
|
if current.weekday() == weekday_target:
|
||||||
|
dates.append(datetime.datetime(
|
||||||
|
current.year, current.month, current.day))
|
||||||
|
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:
|
||||||
|
before_count, after_count = self._quotation_counts()
|
||||||
if self.every:
|
if self.every:
|
||||||
if t:
|
if t:
|
||||||
j = self.every
|
j = self.every
|
||||||
if j == 'before_after' and self.nb_quotation:
|
if j == 'before_after':
|
||||||
count = abs(self.nb_quotation)
|
|
||||||
dates.extend(
|
dates.extend(
|
||||||
self._available_quotation_dates(
|
self._available_quotation_dates(
|
||||||
t, count, -1, cal))
|
t, before_count, -1, cal))
|
||||||
trigger_date = self._as_datetime(t)
|
trigger_date = self._as_datetime(t)
|
||||||
if (self.include
|
if (self.include
|
||||||
and self._calendar_is_quote(
|
and self._calendar_is_quote(
|
||||||
@@ -1732,7 +1766,7 @@ class Period(ModelSQL,ModelView):
|
|||||||
dates.append(trigger_date)
|
dates.append(trigger_date)
|
||||||
dates.extend(
|
dates.extend(
|
||||||
self._available_quotation_dates(
|
self._available_quotation_dates(
|
||||||
t, count, 1, cal))
|
t, after_count, 1, cal))
|
||||||
return date_from, date_to, dates
|
return date_from, date_to, dates
|
||||||
if j not in WEEKDAY_MAP:
|
if j not in WEEKDAY_MAP:
|
||||||
raise ValueError(f"Invalid day : '{j}'")
|
raise ValueError(f"Invalid day : '{j}'")
|
||||||
@@ -1743,34 +1777,30 @@ class Period(ModelSQL,ModelView):
|
|||||||
first_day = t.replace(day=1)
|
first_day = t.replace(day=1)
|
||||||
days_to_add = (weekday_target - first_day.weekday()) % 7
|
days_to_add = (weekday_target - first_day.weekday()) % 7
|
||||||
current = first_day + datetime.timedelta(days=days_to_add)
|
current = first_day + datetime.timedelta(days=days_to_add)
|
||||||
while current.month == t.month:
|
while current.month == t.month:
|
||||||
dates.append(datetime.datetime(current.year, current.month, current.day))
|
|
||||||
current += datetime.timedelta(days=7)
|
|
||||||
elif self.nb_quotation > 0:
|
|
||||||
days_to_add = (weekday_target - t.weekday()) % 7
|
|
||||||
current = t + datetime.timedelta(days=days_to_add)
|
|
||||||
while len(dates) < self.nb_quotation:
|
|
||||||
dates.append(datetime.datetime(current.year, current.month, current.day))
|
|
||||||
current += datetime.timedelta(days=7)
|
|
||||||
elif self.nb_quotation < 0:
|
|
||||||
days_to_sub = (t.weekday() - weekday_target) % 7
|
|
||||||
current = t - datetime.timedelta(days=days_to_sub)
|
|
||||||
while len(dates) < -self.nb_quotation:
|
|
||||||
dates.append(datetime.datetime(current.year, current.month, current.day))
|
dates.append(datetime.datetime(current.year, current.month, current.day))
|
||||||
current -= datetime.timedelta(days=7)
|
current += datetime.timedelta(days=7)
|
||||||
|
elif before_count or after_count:
|
||||||
|
dates.extend(self._weekday_dates(
|
||||||
|
t, weekday_target, before_count, -1))
|
||||||
|
trigger_date = self._as_datetime(t)
|
||||||
|
if (self.include
|
||||||
|
and trigger_date.weekday() == weekday_target):
|
||||||
|
dates.append(trigger_date)
|
||||||
|
dates.extend(self._weekday_dates(
|
||||||
|
t, weekday_target, after_count, 1))
|
||||||
|
|
||||||
elif self.nb_quotation:
|
elif before_count or after_count:
|
||||||
step = 1 if self.nb_quotation > 0 else -1
|
|
||||||
dates.extend(
|
dates.extend(
|
||||||
self._available_quotation_dates(
|
self._available_quotation_dates(
|
||||||
t, abs(self.nb_quotation), step, cal))
|
t, before_count, -1, cal))
|
||||||
trigger_date = self._as_datetime(t)
|
trigger_date = self._as_datetime(t)
|
||||||
if (self.include
|
if (self.include
|
||||||
and self._calendar_is_quote(cal, trigger_date)):
|
and self._calendar_is_quote(cal, trigger_date)):
|
||||||
if self.nb_quotation > 0:
|
dates.append(trigger_date)
|
||||||
dates.insert(0, trigger_date)
|
dates.extend(
|
||||||
else:
|
self._available_quotation_dates(
|
||||||
dates.append(trigger_date)
|
t, after_count, 1, cal))
|
||||||
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))
|
||||||
|
|||||||
@@ -3346,7 +3346,7 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
|||||||
period = Period()
|
period = Period()
|
||||||
period.trigger = 'bldate'
|
period.trigger = 'bldate'
|
||||||
period.every = 'friday'
|
period.every = 'friday'
|
||||||
period.nb_quotation = 4
|
period.nb_quotation_after = 4
|
||||||
|
|
||||||
_, _, dates = period.getDates(datetime.date(2026, 4, 1))
|
_, _, dates = period.getDates(datetime.date(2026, 4, 1))
|
||||||
|
|
||||||
@@ -3359,27 +3359,27 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
|||||||
datetime.date(2026, 4, 24),
|
datetime.date(2026, 4, 24),
|
||||||
])
|
])
|
||||||
|
|
||||||
def test_pricing_period_every_negative_returns_weekdays_before_trigger(self):
|
def test_pricing_period_every_before_returns_weekdays_before_trigger(self):
|
||||||
'Every Friday and -4 returns the four Fridays before trigger date'
|
'Every Friday with before returns the four Fridays before trigger date'
|
||||||
Period = Pool().get('pricing.period')
|
Period = Pool().get('pricing.period')
|
||||||
period = Period()
|
period = Period()
|
||||||
period.trigger = 'bldate'
|
period.trigger = 'bldate'
|
||||||
period.every = 'friday'
|
period.every = 'friday'
|
||||||
period.nb_quotation = -4
|
period.nb_quotation_before = 4
|
||||||
|
|
||||||
_, _, dates = period.getDates(datetime.date(2026, 4, 30))
|
_, _, dates = period.getDates(datetime.date(2026, 4, 30))
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
[date.date() for date in dates],
|
[date.date() for date in dates],
|
||||||
[
|
[
|
||||||
datetime.date(2026, 4, 24),
|
|
||||||
datetime.date(2026, 4, 17),
|
|
||||||
datetime.date(2026, 4, 10),
|
|
||||||
datetime.date(2026, 4, 3),
|
datetime.date(2026, 4, 3),
|
||||||
|
datetime.date(2026, 4, 10),
|
||||||
|
datetime.date(2026, 4, 17),
|
||||||
|
datetime.date(2026, 4, 24),
|
||||||
])
|
])
|
||||||
|
|
||||||
def test_pricing_period_blank_every_positive_uses_available_dates_after_trigger(self):
|
def test_pricing_period_blank_every_after_uses_available_dates_after_trigger(self):
|
||||||
'Blank Every and positive Nb quotation returns dates after trigger'
|
'Blank Every and Nb quotes after returns dates after trigger'
|
||||||
Period = Pool().get('pricing.period')
|
Period = Pool().get('pricing.period')
|
||||||
calendar = Mock()
|
calendar = Mock()
|
||||||
excluded = {
|
excluded = {
|
||||||
@@ -3391,7 +3391,7 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
|||||||
period = Period()
|
period = Period()
|
||||||
period.trigger = 'bldate'
|
period.trigger = 'bldate'
|
||||||
period.every = None
|
period.every = None
|
||||||
period.nb_quotation = 4
|
period.nb_quotation_after = 4
|
||||||
|
|
||||||
_, _, dates = period.getDates(datetime.date(2026, 5, 12), calendar)
|
_, _, dates = period.getDates(datetime.date(2026, 5, 12), calendar)
|
||||||
|
|
||||||
@@ -3405,13 +3405,13 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
|||||||
])
|
])
|
||||||
|
|
||||||
def test_pricing_period_blank_every_include_counts_trigger_plus_after_dates(self):
|
def test_pricing_period_blank_every_include_counts_trigger_plus_after_dates(self):
|
||||||
'Blank Every with Inc. returns trigger date plus quotations after'
|
'Blank Every with Inc. returns trigger date plus quotes after'
|
||||||
Period = Pool().get('pricing.period')
|
Period = Pool().get('pricing.period')
|
||||||
period = Period()
|
period = Period()
|
||||||
period.trigger = 'bldate'
|
period.trigger = 'bldate'
|
||||||
period.include = True
|
period.include = True
|
||||||
period.every = None
|
period.every = None
|
||||||
period.nb_quotation = 4
|
period.nb_quotation_after = 4
|
||||||
|
|
||||||
_, _, dates = period.getDates(datetime.date(2026, 5, 12))
|
_, _, dates = period.getDates(datetime.date(2026, 5, 12))
|
||||||
|
|
||||||
@@ -3425,13 +3425,13 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
|||||||
datetime.date(2026, 5, 16),
|
datetime.date(2026, 5, 16),
|
||||||
])
|
])
|
||||||
|
|
||||||
def test_pricing_period_blank_every_negative_uses_available_dates_before_trigger(self):
|
def test_pricing_period_blank_every_before_uses_available_dates_before_trigger(self):
|
||||||
'Blank Every and negative Nb quotation also returns dates before trigger'
|
'Blank Every and Nb quotes before returns dates before trigger'
|
||||||
Period = Pool().get('pricing.period')
|
Period = Pool().get('pricing.period')
|
||||||
period = Period()
|
period = Period()
|
||||||
period.trigger = 'bldate'
|
period.trigger = 'bldate'
|
||||||
period.every = None
|
period.every = None
|
||||||
period.nb_quotation = -4
|
period.nb_quotation_before = 4
|
||||||
|
|
||||||
_, _, dates = period.getDates(datetime.date(2026, 5, 12))
|
_, _, dates = period.getDates(datetime.date(2026, 5, 12))
|
||||||
|
|
||||||
@@ -3444,34 +3444,36 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
|||||||
datetime.date(2026, 5, 11),
|
datetime.date(2026, 5, 11),
|
||||||
])
|
])
|
||||||
|
|
||||||
def test_pricing_period_before_after_returns_symmetric_dates(self):
|
def test_pricing_period_blank_every_returns_asymmetric_dates(self):
|
||||||
'Before/After returns quotations before and after trigger'
|
'Blank Every returns independent quotations before and after trigger'
|
||||||
Period = Pool().get('pricing.period')
|
Period = Pool().get('pricing.period')
|
||||||
period = Period()
|
period = Period()
|
||||||
period.trigger = 'bldate'
|
period.trigger = 'bldate'
|
||||||
period.include = False
|
period.include = False
|
||||||
period.every = 'before_after'
|
period.every = None
|
||||||
period.nb_quotation = 2
|
period.nb_quotation_before = 1
|
||||||
|
period.nb_quotation_after = 3
|
||||||
|
|
||||||
_, _, dates = period.getDates(datetime.date(2026, 5, 12))
|
_, _, dates = period.getDates(datetime.date(2026, 5, 12))
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
[date.date() for date in dates],
|
[date.date() for date in dates],
|
||||||
[
|
[
|
||||||
datetime.date(2026, 5, 10),
|
|
||||||
datetime.date(2026, 5, 11),
|
datetime.date(2026, 5, 11),
|
||||||
datetime.date(2026, 5, 13),
|
datetime.date(2026, 5, 13),
|
||||||
datetime.date(2026, 5, 14),
|
datetime.date(2026, 5, 14),
|
||||||
|
datetime.date(2026, 5, 15),
|
||||||
])
|
])
|
||||||
|
|
||||||
def test_pricing_period_before_after_include_adds_trigger_date(self):
|
def test_pricing_period_blank_every_before_and_after_include_adds_trigger_date(self):
|
||||||
'Before/After with Inc. adds trigger date between both sides'
|
'Blank Every with Inc. adds trigger date between both sides'
|
||||||
Period = Pool().get('pricing.period')
|
Period = Pool().get('pricing.period')
|
||||||
period = Period()
|
period = Period()
|
||||||
period.trigger = 'bldate'
|
period.trigger = 'bldate'
|
||||||
period.include = True
|
period.include = True
|
||||||
period.every = 'before_after'
|
period.every = None
|
||||||
period.nb_quotation = 2
|
period.nb_quotation_before = 2
|
||||||
|
period.nb_quotation_after = 2
|
||||||
|
|
||||||
_, _, dates = period.getDates(datetime.date(2026, 5, 12))
|
_, _, dates = period.getDates(datetime.date(2026, 5, 12))
|
||||||
|
|
||||||
@@ -3485,8 +3487,8 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
|||||||
datetime.date(2026, 5, 14),
|
datetime.date(2026, 5, 14),
|
||||||
])
|
])
|
||||||
|
|
||||||
def test_pricing_period_before_after_skips_calendar_exclusions(self):
|
def test_pricing_period_blank_every_before_and_after_skips_calendar_exclusions(self):
|
||||||
'Before/After uses available quotations around trigger date'
|
'Blank Every uses available quotations around trigger date'
|
||||||
Period = Pool().get('pricing.period')
|
Period = Pool().get('pricing.period')
|
||||||
calendar = Mock()
|
calendar = Mock()
|
||||||
excluded = {
|
excluded = {
|
||||||
@@ -3498,15 +3500,15 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
|||||||
period = Period()
|
period = Period()
|
||||||
period.trigger = 'bldate'
|
period.trigger = 'bldate'
|
||||||
period.include = True
|
period.include = True
|
||||||
period.every = 'before_after'
|
period.every = None
|
||||||
period.nb_quotation = 2
|
period.nb_quotation_before = 1
|
||||||
|
period.nb_quotation_after = 2
|
||||||
|
|
||||||
_, _, dates = period.getDates(datetime.date(2026, 5, 12), calendar)
|
_, _, dates = period.getDates(datetime.date(2026, 5, 12), calendar)
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
[date.date() for date in dates],
|
[date.date() for date in dates],
|
||||||
[
|
[
|
||||||
datetime.date(2026, 5, 9),
|
|
||||||
datetime.date(2026, 5, 10),
|
datetime.date(2026, 5, 10),
|
||||||
datetime.date(2026, 5, 12),
|
datetime.date(2026, 5, 12),
|
||||||
datetime.date(2026, 5, 14),
|
datetime.date(2026, 5, 14),
|
||||||
|
|||||||
@@ -6,11 +6,13 @@
|
|||||||
<field name="trigger"/>
|
<field name="trigger"/>
|
||||||
<label name="include"/>
|
<label name="include"/>
|
||||||
<field name="include"/>
|
<field name="include"/>
|
||||||
<newline/>
|
<newline/>
|
||||||
<label name="every"/>
|
<label name="every"/>
|
||||||
<field name="every"/>
|
<field name="every"/>
|
||||||
<label name="nb_quotation"/>
|
<label name="nb_quotation_before"/>
|
||||||
<field name="nb_quotation"/>
|
<field name="nb_quotation_before"/>
|
||||||
|
<label name="nb_quotation_after"/>
|
||||||
|
<field name="nb_quotation_after"/>
|
||||||
<newline/>
|
<newline/>
|
||||||
<label name="startday"/>
|
<label name="startday"/>
|
||||||
<field name="startday"/>
|
<field name="startday"/>
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
<tree>
|
<tree>
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="trigger"/>
|
<field name="trigger"/>
|
||||||
<field name="include"/>
|
<field name="include"/>
|
||||||
<field name="startday"/>
|
<field name="every"/>
|
||||||
|
<field name="nb_quotation_before"/>
|
||||||
|
<field name="nb_quotation_after"/>
|
||||||
|
<field name="startday"/>
|
||||||
<field name="nbds"/>
|
<field name="nbds"/>
|
||||||
<field name="nbms"/>
|
<field name="nbms"/>
|
||||||
<field name="endday"/>
|
<field name="endday"/>
|
||||||
|
|||||||
Reference in New Issue
Block a user