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."
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 #

View file

@ -374,3 +374,10 @@ msgctxt "view:cashbook.line:"
msgid "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', '=', None))],
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(
string='Interval', required=True,
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 = 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)
frequ = pfrequ[params.get('frequ', self.frequ)]
@ -283,6 +292,18 @@ class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView):
interval = 1
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), \
"weekday and monthday cannot be used together"
@ -297,7 +318,12 @@ class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView):
for x in dtrule:
if (query_date and (x.date() >= query_date)) or \
(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
# query_date - skip it
@ -368,7 +394,7 @@ class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView):
@fields.depends(
'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):
""" Calculates the next 5 appointments based on the configured rule,
returns a formatted date list
@ -401,7 +427,8 @@ class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView):
'weekday': self.weekday,
'monthday': self.monthday,
'interval': self.interval,
'setpos': self.setpos}
'setpos': self.setpos,
'last_day_of_month': self.last_day_of_month}
)])
@fields.depends('cashbook', '_parent_cashbook.owner')
@ -428,13 +455,17 @@ class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView):
elif self.bookingtype in ['mvin', 'mvout']:
self.category = None
@fields.depends('frequ', 'setpos', 'weekday', 'monthday')
@fields.depends(
'frequ', 'setpos', 'weekday', 'monthday', 'last_day_of_month')
def on_change_frequ(self):
""" update fields
"""
if self.frequ and self.frequ == 'month':
if self.weekday:
if self.weekday == '99':
if self.last_day_of_month:
self.monthday = None
else:
if self.monthday is None:
self.monthday = 1
self.setpos = None
@ -442,12 +473,15 @@ class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView):
if self.setpos is None:
self.setpos = 1
self.monthday = None
self.last_day_of_month = False
else:
self.setpos = None
self.monthday = None
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):
""" clear day-of-month if weekday is used
"""
@ -546,6 +580,15 @@ class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView):
"""
return 1
@classmethod
def default_last_day_of_month(cls):
""" get default for last-day-of-month
Returns:
boolean: False
"""
return False
@classmethod
def default_frequ(cls):
""" 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, 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
cfg1 = Config(
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"/>
<label name="monthday"/>
<field name="monthday"/>
<label name="last_day_of_month"/>
<field name="last_day_of_month"/>
<label name="move_event"/>
<field name="move_event"/>