From fdf855f7db54b6edcdf266628da2eb98efffaf34 Mon Sep 17 00:00:00 2001 From: laurentbarontini Date: Tue, 16 Jun 2026 11:26:23 +0200 Subject: [PATCH] Pricing adj --- modules/purchase_trade/pricing.py | 92 ++++++++++++++------- modules/purchase_trade/tests/test_module.py | 60 +++++++------- modules/purchase_trade/view/period_form.xml | 12 +-- modules/purchase_trade/view/period_tree.xml | 9 +- 4 files changed, 105 insertions(+), 68 deletions(-) diff --git a/modules/purchase_trade/pricing.py b/modules/purchase_trade/pricing.py index 067f408..4f0fc9b 100755 --- a/modules/purchase_trade/pricing.py +++ b/modules/purchase_trade/pricing.py @@ -50,7 +50,6 @@ WEEKDAY_MAP = { DAYS = [ (None,''), - ('before_after', 'Before/After'), ('monday', 'Monday'), ('tuesday', 'Tuesday'), ('wednesday', 'Wednesday'), @@ -1666,9 +1665,11 @@ class Period(ModelSQL,ModelView): endday = fields.Selection(DAYTYPES,"End day") nbde = fields.Integer("Nb") nbms = fields.Integer("Starting month") - nbme = fields.Integer("Ending month") - every = fields.Selection(DAYS,"Every") - nb_quotation = fields.Integer("Nb quotation") + nbme = fields.Integer("Ending month") + every = fields.Selection(DAYS,"Every") + nb_quotation_before = fields.Integer("Nb quotes before") + nb_quotation_after = fields.Integer("Nb quotes after") + nb_quotation = fields.Integer("Nb quotation") @classmethod def default_nbds(cls): @@ -1683,8 +1684,16 @@ class Period(ModelSQL,ModelView): return 0 @classmethod - def default_nbme(cls): - return 0 + def default_nbme(cls): + return 0 + + @classmethod + def default_nb_quotation_before(cls): + return 0 + + @classmethod + def default_nb_quotation_after(cls): + return 0 @staticmethod def _calendar_is_quote(cal, date): @@ -1712,19 +1721,44 @@ class Period(ModelSQL,ModelView): dates.reverse() 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): date_from = None date_to = None dates = [] if t: + before_count, after_count = self._quotation_counts() if self.every: if t: j = self.every - if j == 'before_after' and self.nb_quotation: - count = abs(self.nb_quotation) + if j == 'before_after': dates.extend( self._available_quotation_dates( - t, count, -1, cal)) + t, before_count, -1, cal)) trigger_date = self._as_datetime(t) if (self.include and self._calendar_is_quote( @@ -1732,7 +1766,7 @@ class Period(ModelSQL,ModelView): dates.append(trigger_date) dates.extend( self._available_quotation_dates( - t, count, 1, cal)) + t, after_count, 1, cal)) return date_from, date_to, dates if j not in WEEKDAY_MAP: raise ValueError(f"Invalid day : '{j}'") @@ -1743,34 +1777,30 @@ class Period(ModelSQL,ModelView): first_day = t.replace(day=1) days_to_add = (weekday_target - first_day.weekday()) % 7 current = first_day + datetime.timedelta(days=days_to_add) - 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: + while current.month == t.month: 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: - step = 1 if self.nb_quotation > 0 else -1 + elif before_count or after_count: dates.extend( self._available_quotation_dates( - t, abs(self.nb_quotation), step, cal)) + t, before_count, -1, 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) + dates.append(trigger_date) + dates.extend( + self._available_quotation_dates( + t, after_count, 1, cal)) else: if self.startday == 'before': date_from = t - datetime.timedelta(days=(self.nbds if self.nbds else 0)) diff --git a/modules/purchase_trade/tests/test_module.py b/modules/purchase_trade/tests/test_module.py index c2f8379..d201210 100644 --- a/modules/purchase_trade/tests/test_module.py +++ b/modules/purchase_trade/tests/test_module.py @@ -3346,7 +3346,7 @@ class PurchaseTradeTestCase(ModuleTestCase): period = Period() period.trigger = 'bldate' period.every = 'friday' - period.nb_quotation = 4 + period.nb_quotation_after = 4 _, _, dates = period.getDates(datetime.date(2026, 4, 1)) @@ -3359,27 +3359,27 @@ class PurchaseTradeTestCase(ModuleTestCase): datetime.date(2026, 4, 24), ]) - def test_pricing_period_every_negative_returns_weekdays_before_trigger(self): - 'Every Friday and -4 returns the four Fridays before trigger date' + def test_pricing_period_every_before_returns_weekdays_before_trigger(self): + 'Every Friday with before returns the four Fridays before trigger date' Period = Pool().get('pricing.period') period = Period() period.trigger = 'bldate' period.every = 'friday' - period.nb_quotation = -4 + period.nb_quotation_before = 4 _, _, dates = period.getDates(datetime.date(2026, 4, 30)) self.assertEqual( [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, 10), + datetime.date(2026, 4, 17), + datetime.date(2026, 4, 24), ]) - def test_pricing_period_blank_every_positive_uses_available_dates_after_trigger(self): - 'Blank Every and positive Nb quotation returns dates after trigger' + def test_pricing_period_blank_every_after_uses_available_dates_after_trigger(self): + 'Blank Every and Nb quotes after returns dates after trigger' Period = Pool().get('pricing.period') calendar = Mock() excluded = { @@ -3391,7 +3391,7 @@ class PurchaseTradeTestCase(ModuleTestCase): period = Period() period.trigger = 'bldate' period.every = None - period.nb_quotation = 4 + period.nb_quotation_after = 4 _, _, 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): - '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 = Period() period.trigger = 'bldate' period.include = True period.every = None - period.nb_quotation = 4 + period.nb_quotation_after = 4 _, _, dates = period.getDates(datetime.date(2026, 5, 12)) @@ -3425,13 +3425,13 @@ class PurchaseTradeTestCase(ModuleTestCase): 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' + def test_pricing_period_blank_every_before_uses_available_dates_before_trigger(self): + 'Blank Every and Nb quotes before returns dates before trigger' Period = Pool().get('pricing.period') period = Period() period.trigger = 'bldate' period.every = None - period.nb_quotation = -4 + period.nb_quotation_before = 4 _, _, dates = period.getDates(datetime.date(2026, 5, 12)) @@ -3444,34 +3444,36 @@ 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' + def test_pricing_period_blank_every_returns_asymmetric_dates(self): + 'Blank Every returns independent 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 + period.every = None + period.nb_quotation_before = 1 + period.nb_quotation_after = 3 _, _, 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), + datetime.date(2026, 5, 15), ]) - def test_pricing_period_before_after_include_adds_trigger_date(self): - 'Before/After with Inc. adds trigger date between both sides' + def test_pricing_period_blank_every_before_and_after_include_adds_trigger_date(self): + 'Blank Every 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 + period.every = None + period.nb_quotation_before = 2 + period.nb_quotation_after = 2 _, _, dates = period.getDates(datetime.date(2026, 5, 12)) @@ -3485,8 +3487,8 @@ class PurchaseTradeTestCase(ModuleTestCase): datetime.date(2026, 5, 14), ]) - def test_pricing_period_before_after_skips_calendar_exclusions(self): - 'Before/After uses available quotations around trigger date' + def test_pricing_period_blank_every_before_and_after_skips_calendar_exclusions(self): + 'Blank Every uses available quotations around trigger date' Period = Pool().get('pricing.period') calendar = Mock() excluded = { @@ -3498,15 +3500,15 @@ class PurchaseTradeTestCase(ModuleTestCase): period = Period() period.trigger = 'bldate' period.include = True - period.every = 'before_after' - period.nb_quotation = 2 + period.every = None + period.nb_quotation_before = 1 + period.nb_quotation_after = 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), diff --git a/modules/purchase_trade/view/period_form.xml b/modules/purchase_trade/view/period_form.xml index d863323..c084571 100755 --- a/modules/purchase_trade/view/period_form.xml +++ b/modules/purchase_trade/view/period_form.xml @@ -6,11 +6,13 @@