recurrence: add last-day-of-month

This commit is contained in:
=Frederik Jaeckel 2024-09-27 23:24:48 +02:00
parent 18f194caa5
commit 181f7405d6
5 changed files with 79 additions and 8 deletions

View file

@ -366,6 +366,14 @@ msgctxt "help:cashbook.planner,move_event:"
msgid "If the date of execution falls on a weekend or holiday, it can be moved to a business day." msgid "If the date of execution falls on a weekend or holiday, it can be moved to a business day."
msgstr "Wenn das Datum der Ausführung auf ein Wochenende oder Feiertag fällt, kann es auf einen Geschäftstag verschoben werden." msgstr "Wenn das Datum der Ausführung auf ein Wochenende oder Feiertag fällt, kann es auf einen Geschäftstag verschoben werden."
msgctxt "field:cashbook.planner,last_day_of_month:"
msgid "Last day of the month"
msgstr "letzer Tag des Monats"
msgctxt "help:cashbook.planner,last_day_of_month:"
msgid "The booking is made on the last day of the month."
msgstr "Die Buchung wird am letzten Tag des Monats ausgeführt."
############################ ############################
# cashbook.planner.nextrun # # cashbook.planner.nextrun #

View file

@ -374,3 +374,10 @@ msgctxt "view:cashbook.line:"
msgid "Scheduled Bookings" msgid "Scheduled Bookings"
msgstr "Scheduled Bookings" msgstr "Scheduled Bookings"
msgctxt "field:cashbook.planner,last_day_of_month:"
msgid "Last day of the month"
msgstr "Last day of the month"
msgctxt "help:cashbook.planner,last_day_of_month:"
msgid "The booking is made on the last day of the month."
msgstr "The booking is made on the last day of the month."

View file

@ -91,7 +91,14 @@ class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView):
[('monthday', '>=', 1), ('monthday', '<=', 31)], [('monthday', '>=', 1), ('monthday', '<=', 31)],
('monthday', '=', None))], ('monthday', '=', None))],
depends=['weekday', 'frequ'], depends=['weekday', 'frequ'],
states={'required': COND_MONTHDAY, 'invisible': ~COND_MONTHDAY}) states={
'required': And(COND_MONTHDAY, ~Eval('last_day_of_month', False)),
'invisible': ~And(
COND_MONTHDAY, ~Eval('last_day_of_month', False))})
last_day_of_month = fields.Boolean(
string='Last day of the month', depends=['weekday', 'frequ'],
help='The booking is made on the last day of the month.',
states={'invisible': ~COND_MONTHDAY})
interval = fields.Integer( interval = fields.Integer(
string='Interval', required=True, string='Interval', required=True,
help='Select an interval to run the rule on every n-th date.', help='Select an interval to run the rule on every n-th date.',
@ -263,6 +270,8 @@ class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView):
count = 5 count = 5
count = 1 if count < 1 else 100 if count > 100 else count count = 1 if count < 1 else 100 if count > 100 else count
last_day_of_month = params.get(
'last_day_of_month', self.last_day_of_month)
end_date = params.get('end_date', self.end_date) end_date = params.get('end_date', self.end_date)
frequ = pfrequ[params.get('frequ', self.frequ)] frequ = pfrequ[params.get('frequ', self.frequ)]
@ -283,6 +292,18 @@ class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView):
interval = 1 interval = 1
interval = 1 if interval < 1 else 10 if interval > 10 else interval interval = 1 if interval < 1 else 10 if interval > 10 else interval
# last-day-of-month: set date short before end of month,
# then compute move result to end of month
updt_lastday = False
if last_day_of_month and (frequ == MONTHLY) and not pweekday:
monthday = 28
updt_lastday = True
lastday_valid = last_day_of_month and (
frequ == MONTHLY) and (pweekday is None)
assert (lastday_valid or not last_day_of_month), \
('last-day-of-month can only be used with frequ=month ' +
'and weekday=99.')
assert (monthday is None) or (pweekday is None), \ assert (monthday is None) or (pweekday is None), \
"weekday and monthday cannot be used together" "weekday and monthday cannot be used together"
@ -297,7 +318,12 @@ class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView):
for x in dtrule: for x in dtrule:
if (query_date and (x.date() >= query_date)) or \ if (query_date and (x.date() >= query_date)) or \
(query_date is None): (query_date is None):
x_date = get_moved_date(x.date(), move_event) x_date = x.date()
if updt_lastday:
x_date = (
(x_date + timedelta(days=5)).replace(day=1) -
timedelta(days=1))
x_date = get_moved_date(x_date, move_event)
# if date was re-arranged backwards and we are before # if date was re-arranged backwards and we are before
# query_date - skip it # query_date - skip it
@ -368,7 +394,7 @@ class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView):
@fields.depends( @fields.depends(
'start_date', 'end_date', 'frequ', 'weekday', 'monthday', 'start_date', 'end_date', 'frequ', 'weekday', 'monthday',
'interval', 'setpos', 'move_event') 'interval', 'setpos', 'move_event', 'last_day_of_month')
def on_change_with_nextdates(self, name=None): def on_change_with_nextdates(self, name=None):
""" Calculates the next 5 appointments based on the configured rule, """ Calculates the next 5 appointments based on the configured rule,
returns a formatted date list returns a formatted date list
@ -401,7 +427,8 @@ class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView):
'weekday': self.weekday, 'weekday': self.weekday,
'monthday': self.monthday, 'monthday': self.monthday,
'interval': self.interval, 'interval': self.interval,
'setpos': self.setpos} 'setpos': self.setpos,
'last_day_of_month': self.last_day_of_month}
)]) )])
@fields.depends('cashbook', '_parent_cashbook.owner') @fields.depends('cashbook', '_parent_cashbook.owner')
@ -428,26 +455,33 @@ class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView):
elif self.bookingtype in ['mvin', 'mvout']: elif self.bookingtype in ['mvin', 'mvout']:
self.category = None self.category = None
@fields.depends('frequ', 'setpos', 'weekday', 'monthday') @fields.depends(
'frequ', 'setpos', 'weekday', 'monthday', 'last_day_of_month')
def on_change_frequ(self): def on_change_frequ(self):
""" update fields """ update fields
""" """
if self.frequ and self.frequ == 'month': if self.frequ and self.frequ == 'month':
if self.weekday: if self.weekday:
if self.weekday == '99': if self.weekday == '99':
if self.monthday is None: if self.last_day_of_month:
self.monthday = 1 self.monthday = None
else:
if self.monthday is None:
self.monthday = 1
self.setpos = None self.setpos = None
else: else:
if self.setpos is None: if self.setpos is None:
self.setpos = 1 self.setpos = 1
self.monthday = None self.monthday = None
self.last_day_of_month = False
else: else:
self.setpos = None self.setpos = None
self.monthday = None self.monthday = None
self.weekday = '99' self.weekday = '99'
self.last_day_of_month = False
@fields.depends('frequ', 'setpos', 'weekday', 'monthday') @fields.depends(
'frequ', 'setpos', 'weekday', 'monthday', 'last_day_of_month')
def on_change_weekday(self): def on_change_weekday(self):
""" clear day-of-month if weekday is used """ clear day-of-month if weekday is used
""" """
@ -546,6 +580,15 @@ class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView):
""" """
return 1 return 1
@classmethod
def default_last_day_of_month(cls):
""" get default for last-day-of-month
Returns:
boolean: False
"""
return False
@classmethod @classmethod
def default_frequ(cls): def default_frequ(cls):
""" get default for frequency """ get default for frequency

View file

@ -245,6 +245,17 @@ class PlannerTestCase(object):
[date(2022, 5, 11), date(2022, 6, 8), date(2022, 7, 13), [date(2022, 5, 11), date(2022, 6, 8), date(2022, 7, 13),
date(2022, 8, 10), date(2022, 9, 14), date(2022, 10, 12)]) date(2022, 8, 10), date(2022, 9, 14), date(2022, 10, 12)])
# last day of month
self.assertEqual(
job._compute_dates_by_rrule(
query_date=date(2022, 5, 1), count=6,
params={
'weekday': '99', 'end_date': None, 'frequ': 'month',
'interval': 1, 'setpos': None, 'monthday': None,
'last_day_of_month': True}),
[date(2022, 5, 31), date(2022, 6, 30), date(2022, 7, 31),
date(2022, 8, 31), date(2022, 9, 30), date(2022, 10, 31)])
# set up holidays # set up holidays
cfg1 = Config( cfg1 = Config(
holidays='01-01;05-01;easter:+1;easter:-2;ascension;whitsun:+1') holidays='01-01;05-01;easter:+1;easter:-2;ascension;whitsun:+1')

View file

@ -34,6 +34,8 @@ full copyright notices and license terms. -->
<field name="weekday"/> <field name="weekday"/>
<label name="monthday"/> <label name="monthday"/>
<field name="monthday"/> <field name="monthday"/>
<label name="last_day_of_month"/>
<field name="last_day_of_month"/>
<label name="move_event"/> <label name="move_event"/>
<field name="move_event"/> <field name="move_event"/>