Compare commits

...

21 commits

Author SHA1 Message Date
Frederik Jaeckel
f397cd593c Version 7.0.6 2024-07-19 16:24:54 +02:00
Frederik Jaeckel
8a08517a44 Use 'irrulecontext' to add 'user_id' to context of ir.rule 2024-07-19 16:23:32 +02:00
Frederik Jaeckel
a6097c1022 update gitignore 2024-07-19 16:23:28 +02:00
Frederik Jaeckel
b59e25760d Etikett ver 7.0.5 zum Änderungssatz f4651301338f hinzugefügt 2024-06-08 09:26:08 +02:00
Frederik Jaeckel
1536e8f22f Version 7.0.5 2024-06-08 09:25:58 +02:00
Frederik Jaeckel
9685670672 Add option to rule to move date of occurence to working day 2024-06-03 23:37:26 +02:00
Frederik Jaeckel
e4963beee9 config: new field 'holidays_info' to show generated holidays in config-form 2024-06-02 10:27:15 +02:00
Frederik Jaeckel
4f9e6ad1a0 config: compute holidays + test 2024-06-01 23:37:06 +02:00
Frederik Jaeckel
b5f774003f config: allow setting of holidays for each user 2024-06-01 13:33:45 +02:00
Frederik Jaeckel
73a7c1d86c New field 'move_event' for moving occurence-date if not a business day 2024-06-01 13:31:55 +02:00
Frederik Jaeckel
353b622074 Etikett ver 7.0.4 zum Änderungssatz 746c70d04a98 hinzugefügt 2024-03-11 12:51:52 +01:00
Frederik Jaeckel
0fda36881e Version 7.0.4 2024-03-11 12:51:42 +01:00
Frederik Jaeckel
3220527fe4 field 'nextrun_link' --> 'nextrun_date', add default-order 2024-03-11 12:50:00 +01:00
Frederik Jaeckel
ee593bd49f Etikett ver 7.0.3 zum Änderungssatz a496f77b38eb hinzugefügt 2024-03-10 22:23:19 +01:00
Frederik Jaeckel
6f27beb7a6 Version 7.0.3 2024-03-10 22:23:06 +01:00
Frederik Jaeckel
722b265c33 add placeholder 'rate' 2024-03-10 22:21:00 +01:00
Frederik Jaeckel
a3cba97892 Etikett ver 7.0.2 zum Änderungssatz 3a559e1ae70f hinzugefügt 2024-03-10 07:48:46 +01:00
Frederik Jaeckel
12839d06db Version 7.0.2 2024-03-10 07:47:04 +01:00
Frederik Jaeckel
b47517c4d8 fix permissions 2024-03-10 07:45:24 +01:00
Frederik Jaeckel
b321965515 Etikett ver 7.0.1 zum Änderungssatz 1f242ff67fe9 hinzugefügt 2024-03-09 22:39:57 +01:00
Frederik Jaeckel
170f5885d2 Version 7.0.1 2024-03-09 22:39:36 +01:00
15 changed files with 621 additions and 33 deletions

View file

@ -1,4 +1,4 @@
syntax: glob *.pyc
.env .env
build/* build/*
dist/* dist/*

View file

@ -14,6 +14,26 @@ Requires
Changes Changes
======= =======
*7.0.0 - 24.02.2024* *7.0.6 - 19.07.2024*
- init - updt: optimize check of permissions
*7.0.5 - 08.06.2024*
- add: re-arrange date of occurence to working day
*7.0.4 - 11.03.2024*
- add: default-order for table
*7.0.3 - 10.03.2024*
- new placeholder 'rate' for description of booking
*7.0.2 - 10.03.2024*
- fix permissions
*7.0.1 - 09.03.2024*
- works

View file

@ -9,10 +9,13 @@ from .planner import ScheduledBooking, ScheduledBookingCashbookRel
from .cashbook import Cashbook, CashbookLine from .cashbook import Cashbook, CashbookLine
from .cron import Cron from .cron import Cron
from .nextrun import NextRun from .nextrun import NextRun
from .config import Configuration, UserConfiguration
def register(): def register():
Pool.register( Pool.register(
Configuration,
UserConfiguration,
Rule, Rule,
ScheduledBooking, ScheduledBooking,
NextRun, NextRun,

211
config.py Normal file
View file

@ -0,0 +1,211 @@
# -*- coding: utf-8 -*-
# This file is part of the cashbook-planner from m-ds for Tryton.
# The COPYRIGHT file at the top level of this repository contains the
# full copyright notices and license terms.
from datetime import date, timedelta
from dateutil.easter import (
easter, EASTER_JULIAN, EASTER_ORTHODOX, EASTER_WESTERN)
from trytond.pool import PoolMeta, Pool
from trytond.model import fields
from trytond.transaction import Transaction
from trytond.report import Report
holidays = fields.Char(
string='Holidays', help='Semicolon separate list of dates: ' +
'yyyy-mm-dd = single date, mm-dd = annual repetition, ' +
'easter[greg|jul|orth] = Easter Sunday, ascension = Ascension Day, ' +
'whitsun = Whitsunday, offset with :+/-n e.g.: easter:+1 = Easter Monday')
class Configuration(metaclass=PoolMeta):
__name__ = 'cashbook.configuration'
holidays = fields.MultiValue(holidays)
holidays_info = fields.Function(fields.Char(
string='Holidays', readonly=True,
help='Holidays in the current year.'), 'on_change_with_holidays_info')
@fields.depends('holidays')
def on_change_with_holidays_info(self, name=None):
""" get string of generated holidays for years in
context-value 'holiday_years' or current year
Args:
name (str, optional): field. Defaults to None.
Returns:
str: formatted holidays in language of user
"""
pool = Pool()
Config = pool.get('cashbook.configuration')
context = Transaction().context
years = context.get('holiday_years', [])
cfg1 = Config.get_singleton()
if cfg1:
dates = cfg1.holiday_dates(years)
dates.sort()
return '|'.join([
Report.format_date(x) for x in dates])
def holiday_dates(self, years=[]):
""" get list of dates for list of years
Args:
years (list, optional): years to get holidays for. Defaults to [].
Returns:
list of date: holidays for requestd years
"""
pool = Pool()
IrDate = pool.get('ir.date')
Config = pool.get('cashbook.configuration')
if not years:
years = [IrDate.today().year]
cfg1 = Config.get_singleton()
if not (cfg1 and cfg1.holidays and isinstance(cfg1.holidays, str)):
return []
return Config.holiday_parseconfig(cfg1.holidays, years)['dates']
@classmethod
def holiday_parseconfig(cls, holiday_string, years=[]):
""" read holiday config, generate parsed list of defines
Args:
holiday_string (str): holiday definition string
years (list of int): years to generate dates for
Returns:
dict: {'definition': '<parsed definition string>',
'dates': [<requested dates>]}
"""
IrDate = Pool().get('ir.date')
def parse_date_offet(offset_str):
""" parse offset string
Args:
offset_str (str): '+n' or '-n'
Returns:
tuple: (int(offset), 'offset-string')
"""
# decode ':+n' or ':-n'
offset_value = 0
plus_sign = 1
if offset_str:
plus_sign = -1 if offset_str.startswith('-') else 1
date_offset = offset_str[1:]
if date_offset.isdigit():
offset_value = int(date_offset)
return (offset_value * plus_sign, '%(sign)s%(amount)d' % {
'sign': '+' if plus_sign >= 0 else '-',
'amount': offset_value})
def parse_date_definition(date_str, years):
""" parse date definition string, generate list of
dates
Args:
date_str (str): definition string
years (list of int): years to generate dates for
Returns:
_type_: _description_
"""
dates = []
date_def = ''
easter_type = {
'greg': EASTER_WESTERN, 'jul': EASTER_JULIAN,
'orth': EASTER_ORTHODOX}
date_str = date_str.lower()
# first parse easter-based dates
for dt_calc in [
{'type': 'easter', 'days': 0},
{'type': 'ascension', 'days': 39},
{'type': 'whitsun', 'days': 49}]:
if date_str.startswith(dt_calc['type']):
e_meth = date_str[len(dt_calc['type']):]
easter_meth = easter_type.get(e_meth, EASTER_WESTERN)
dates.extend([
easter(x, easter_meth) +
timedelta(days=dt_calc['days'])
for x in years])
date_def = date_str
# if not detected try date string
if not date_def:
date_fields = date_str.split('-')
try:
if len(date_fields) == 3:
dates.append(date.fromisoformat(date_str))
date_def = date_str
elif len(date_fields) == 2:
for year in years:
dates.append(date.fromisoformat(
str(year) + '-' + date_str))
date_def = date_str
except Exception:
pass
return (dates, date_def)
if not (holiday_string and isinstance(holiday_string, str)):
return {'definition': '', 'dates': []}
if not years:
years = [IrDate.today().year]
parsed_str = []
parsed_dates = []
for datedef in holiday_string.split(';'):
if not datedef:
continue
datedef = datedef.strip().split(':')
date_offset = datedef[1] if len(datedef) > 1 else ''
(date_offset, offset_str) = parse_date_offet(date_offset)
(date_lst, date_def) = parse_date_definition(datedef[0], years)
parsed_dates.extend([
x + timedelta(days=date_offset)
for x in date_lst])
if date_def:
if date_offset != 0:
date_def += ':' + offset_str
parsed_str.append(date_def)
return {'definition': ';'.join(parsed_str), 'dates': parsed_dates}
@classmethod
def multivalue_model(cls, field):
""" get model for value
"""
pool = Pool()
if field in ['holidays']:
return pool.get('cashbook.configuration_user')
return super(Configuration, cls).multivalue_model(field)
@classmethod
def default_holidays(cls, **pattern):
return cls.multivalue_model('holidays').default_holidays()
# end Configuration
class UserConfiguration(metaclass=PoolMeta):
__name__ = 'cashbook.configuration_user'
holidays = holidays
@classmethod
def default_holidays(cls):
return 'easter+1;easter-2;ascension;05-01;12-25;12-26;01-01;'
# end CashbookLine

15
config.xml Normal file
View file

@ -0,0 +1,15 @@
<?xml version="1.0"?>
<!-- This file is part of the cashbook-planner from m-ds for Tryton.
The COPYRIGHT file at the top level of this repository contains the
full copyright notices and license terms. -->
<tryton>
<data>
<record model="ir.ui.view" id="configuration_view_form">
<field name="model">cashbook.configuration</field>
<field name="inherit" ref="cashbook.configuration_view_form"/>
<field name="name">configuration_form</field>
</record>
</data>
</tryton>

View file

@ -7,6 +7,7 @@ full copyright notices and license terms. -->
<record model="res.group" id="group_planner"> <record model="res.group" id="group_planner">
<field name="name">Cashbook - Scheduled Bookings</field> <field name="name">Cashbook - Scheduled Bookings</field>
<field name="parent" ref="cashbook.group_cashbook"/>
</record> </record>
<record model="res.user-res.group" id="user_admin-group_planner"> <record model="res.user-res.group" id="user_admin-group_planner">

6
ir.py
View file

@ -14,7 +14,9 @@ class Rule(metaclass=PoolMeta):
""" list of models to add 'user_id' to context """ list of models to add 'user_id' to context
""" """
result = super(Rule, cls)._context_modelnames() result = super(Rule, cls)._context_modelnames()
result.append('cashbook.planner') return result | {
return result 'cashbook.planner',
'cashbook.planner.nextrun',
}
# end Rule # end Rule

View file

@ -79,6 +79,30 @@ msgid "User in companies"
msgstr "Benutzer im Unternehmen" msgstr "Benutzer im Unternehmen"
##########################
# cashbook.configuration #
##########################
msgctxt "view:cashbook.configuration:"
msgid "Scheduled Bookings"
msgstr "geplante Buchungen"
msgctxt "field:cashbook.configuration,holidays:"
msgid "Holidays"
msgstr "Feiertage"
msgctxt "help:cashbook.configuration,holidays:"
msgid "Semicolon separate list of dates: yyyy-mm-dd = single date, mm-dd = annual repetition, easter[greg|jul|orth] = Easter Sunday, ascension = Ascension Day, whitsun = Whitsunday, offset with :+/-n e.g.: easter:+1 = Easter Monday"
msgstr "Semikolon getrennte Liste von Datumswerten: yyyy-mm-dd = Einzeldatum, mm-dd = jährliche Wiederholung, easter[greg|jul|orth] = Ostersonntag, ascension = Christi Himmelfahrt, whitsun = Pfingstsonntag, Offset mit :+/-n z.B: easter:+1 = Ostermontag"
msgctxt "field:cashbook.configuration,holidays_info:"
msgid "Holidays"
msgstr "Feiertage"
msgctxt "help:cashbook.configuration,holidays_info:"
msgid "Holidays in the current year."
msgstr "Feiertage im aktuellen Jahr."
#################### ####################
# cashbook.planner # # cashbook.planner #
#################### ####################
@ -107,8 +131,8 @@ msgid "Booking text"
msgstr "Buchungstext" msgstr "Buchungstext"
msgctxt "view:cashbook.planner:" msgctxt "view:cashbook.planner:"
msgid "Available placeholders: ${date} ${month} ${year} ${amount} ${quantity}" msgid "Available placeholders: ${date} ${month} ${year} ${amount} ${quantity} ${rate}"
msgstr "verfügbare Platzhalter: ${date} ${month} ${year} ${amount} ${quantity}" msgstr "verfügbare Platzhalter: ${date} ${month} ${year} ${amount} ${quantity} ${rate}"
msgctxt "view:cashbook.planner:" msgctxt "view:cashbook.planner:"
msgid "Cashbook lines" msgid "Cashbook lines"
@ -204,7 +228,7 @@ msgstr "Tag des Monats"
msgctxt "help:cashbook.planner,monthday:" msgctxt "help:cashbook.planner,monthday:"
msgid "If you want the rule to run on a specific day of the month, select the day here." msgid "If you want the rule to run on a specific day of the month, select the day here."
msgstr "Wenn die Regel an einem bestimmten Tag im Monat ausgeführt soll, wählen Siehier den Tag." msgstr "Wenn die Regel an einem bestimmten Tag im Monat ausgeführt soll, wählen Sie hier den Tag."
msgctxt "field:cashbook.planner,interval:" msgctxt "field:cashbook.planner,interval:"
msgid "Interval" msgid "Interval"
@ -234,7 +258,7 @@ msgctxt "field:cashbook.planner,nextrun:"
msgid "Next Execution Date" msgid "Next Execution Date"
msgstr "Nächster Ausführungstermin" msgstr "Nächster Ausführungstermin"
msgctxt "field:cashbook.planner,nextrun_link:" msgctxt "field:cashbook.planner,nextrun_date:"
msgid "Next Execution Date" msgid "Next Execution Date"
msgstr "Nächster Ausführungstermin" msgstr "Nächster Ausführungstermin"
@ -318,6 +342,30 @@ msgctxt "help:cashbook.planner,cashbook_lines:"
msgid "This cash book lines was generated by the current scheduled booking." msgid "This cash book lines was generated by the current scheduled booking."
msgstr "Diese Kassenbuchzeilen wurden durch die aktuelle geplante Buchung generiert." msgstr "Diese Kassenbuchzeilen wurden durch die aktuelle geplante Buchung generiert."
msgctxt "field:cashbook.planner,party:"
msgid "Party"
msgstr "Partei"
msgctxt "field:cashbook.planner,move_event:"
msgid "If no business day"
msgstr "Wenn kein Werktag"
msgctxt "selection:cashbook.planner,move_event:"
msgid "unchanged"
msgstr "unverändert"
msgctxt "selection:cashbook.planner,move_event:"
msgid "Business day before original date"
msgstr "Geschäftstag vor ursprünglichem Datum"
msgctxt "selection:cashbook.planner,move_event:"
msgid "Business day after original date"
msgstr "Geschäftstag nach ursprünglichem Datum"
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."
############################ ############################
# cashbook.planner.nextrun # # cashbook.planner.nextrun #

View file

@ -58,6 +58,26 @@ msgctxt "model:ir.rule.group,name:rg_nextrun_companies"
msgid "User in companies" msgid "User in companies"
msgstr "User in companies" msgstr "User in companies"
msgctxt "view:cashbook.configuration:"
msgid "Scheduled Bookings"
msgstr "Scheduled Bookings"
msgctxt "field:cashbook.configuration,holidays:"
msgid "Holidays"
msgstr "Holidays"
msgctxt "help:cashbook.configuration,holidays:"
msgid "Semicolon separate list of dates: yyyy-mm-dd = single date, mm-dd = annual repetition, easter[greg|jul|orth] = Easter Sunday, ascension = Ascension Day, whitsun = Whitsunday, offset with :+/-n e.g.: easter:+1 = Easter Monday"
msgstr "Semicolon separate list of dates: yyyy-mm-dd = single date, mm-dd = annual repetition, easter[greg|jul|orth] = Easter Sunday, ascension = Ascension Day, whitsun = Whitsunday, offset with :+/-n e.g.: easter:+1 = Easter Monday"
msgctxt "field:cashbook.configuration,holidays_info:"
msgid "Holidays"
msgstr "Holidays"
msgctxt "help:cashbook.configuration,holidays_info:"
msgid "Holidays in the current year."
msgstr "Holidays in the current year."
msgctxt "model:cashbook.planner,name:" msgctxt "model:cashbook.planner,name:"
msgid "Scheduled Booking" msgid "Scheduled Booking"
msgstr "Scheduled Booking" msgstr "Scheduled Booking"
@ -83,8 +103,8 @@ msgid "Booking text"
msgstr "Booking text" msgstr "Booking text"
msgctxt "view:cashbook.planner:" msgctxt "view:cashbook.planner:"
msgid "Available placeholders: ${date} ${month} ${year} ${amount} ${quantity}" msgid "Available placeholders: ${date} ${month} ${year} ${amount} ${quantity} ${rate}"
msgstr "Available placeholders: ${date} ${month} ${year} ${amount} ${quantity}" msgstr "Available placeholders: ${date} ${month} ${year} ${amount} ${quantity} ${rate}"
msgctxt "view:cashbook.planner:" msgctxt "view:cashbook.planner:"
msgid "Cashbook lines" msgid "Cashbook lines"
@ -210,7 +230,7 @@ msgctxt "field:cashbook.planner,nextrun:"
msgid "Next Execution Date" msgid "Next Execution Date"
msgstr "Next Execution Date" msgstr "Next Execution Date"
msgctxt "field:cashbook.planner,nextrun_link:" msgctxt "field:cashbook.planner,nextrun_date:"
msgid "Next Execution Date" msgid "Next Execution Date"
msgstr "Next Execution Date" msgstr "Next Execution Date"
@ -294,6 +314,30 @@ msgctxt "help:cashbook.planner,cashbook_lines:"
msgid "This cash book lines was generated by the current scheduled booking." msgid "This cash book lines was generated by the current scheduled booking."
msgstr "This cash book lines was generated by the current scheduled booking." msgstr "This cash book lines was generated by the current scheduled booking."
msgctxt "field:cashbook.planner,party:"
msgid "Party"
msgstr "Party"
msgctxt "field:cashbook.planner,move_event:"
msgid "If no business day"
msgstr "If no business day"
msgctxt "selection:cashbook.planner,move_event:"
msgid "unchanged"
msgstr "unchanged"
msgctxt "selection:cashbook.planner,move_event:"
msgid "Business day before original date"
msgstr "Business day before original date"
msgctxt "selection:cashbook.planner,move_event:"
msgid "Business day after original date"
msgstr "Business day after original date"
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 "If the date of execution falls on a weekend or holiday, it can be moved to a business day."
msgctxt "model:cashbook.planner.nextrun,name:" msgctxt "model:cashbook.planner.nextrun,name:"
msgid "Next Execution Date" msgid "Next Execution Date"
msgstr "Next Execution Date" msgstr "Next Execution Date"

View file

@ -34,6 +34,11 @@ SEL_WEEKDAY = [
('0', 'Monday'), ('1', 'Tuesday'), ('2', 'Wednesday'), ('0', 'Monday'), ('1', 'Tuesday'), ('2', 'Wednesday'),
('3', 'Thursday'), ('4', 'Friday'), ('5', 'Saturday'), ('3', 'Thursday'), ('4', 'Friday'), ('5', 'Saturday'),
('6', 'Sunday')] ('6', 'Sunday')]
SEL_MOVE_EVENT = [
('nop', 'unchanged'),
('before', 'Business day before original date'),
('after', 'Business day after original date')
]
class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView): class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView):
@ -98,10 +103,13 @@ class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView):
nextrun = fields.One2Many( nextrun = fields.One2Many(
string='Next Execution Date', size=1, field='planner', string='Next Execution Date', size=1, field='planner',
model_name='cashbook.planner.nextrun') model_name='cashbook.planner.nextrun')
nextrun_link = fields.Function(fields.Many2One( nextrun_date = fields.Function(fields.Date(
string='Next Execution Date', readonly=True, string='Next Execution Date', readonly=True),
model_name='cashbook.planner.nextrun'), 'on_change_with_nextrun_date', searcher='search_nextrun_date')
'on_change_with_nextrun_link') move_event = fields.Selection(
string='If no business day', required=True, selection=SEL_MOVE_EVENT,
help='If the date of execution falls on a weekend or holiday, ' +
'it can be moved to a business day.')
bookingtype = fields.Selection( bookingtype = fields.Selection(
string='Type', selection=sel_bookingtype, required=True, string='Type', selection=sel_bookingtype, required=True,
@ -159,6 +167,8 @@ class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView):
@classmethod @classmethod
def __setup__(cls): def __setup__(cls):
super(ScheduledBooking, cls).__setup__() super(ScheduledBooking, cls).__setup__()
cls._order.insert(0, ('name', 'ASC'))
cls._order.insert(0, ('nextrun_date', 'ASC'))
t = cls.__table__() t = cls.__table__()
cls._sql_indexes.update({ cls._sql_indexes.update({
Index( Index(
@ -189,7 +199,8 @@ class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView):
self.booktransf.name self.booktransf.name
if self.booktransf if self.booktransf
else self.category.rec_name if self.category else '-', else self.category.rec_name if self.category else '-',
self.nextrun_link.rec_name if self.nextrun_link else '-', Report.format_date(self.nextrun_date, lang=None)
if self.nextrun_date else '-',
Report.format_currency( Report.format_currency(
self.amount, lang=None, currency=self.cashbook.currency) self.amount, lang=None, currency=self.cashbook.currency)
]) ])
@ -206,11 +217,42 @@ class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView):
used instead of the stored values, Defaults to {}, used instead of the stored values, Defaults to {},
allowed: frequ, weekday, start_date, allowed: frequ, weekday, start_date,
end_date (preferred over 'count'), end_date (preferred over 'count'),
monthday, interval, setpos monthday, interval, setpos, move_event
Returns: Returns:
list: date values, result of rrlue list: date values, result of rrlue
""" """
def get_moved_date(xdate, m_mode):
""" re-arrange xdate to a working day
Args:
xdate (date): date to move to a working day
move_mode (str): move mode:
nop - no operation
after/before - move date to after/before input date
Returns:
date: re-arranged date
"""
Config = Pool().get('cashbook.configuration')
config = Config.get_singleton()
assert m_mode in ['nop', 'after', 'before'], 'invalid move_mode'
if (not config) or (m_mode == 'nop'):
return xdate
holidays = config.holiday_dates([xdate.year, xdate.year + 1])
day_cnt = (
1 if m_mode == 'after'
else -1 if m_mode == 'before' else 0)
if day_cnt != 0:
while (xdate in holidays) or (xdate.weekday() in [5, 6]):
# re-arrange
xdate = xdate + timedelta(days=day_cnt)
return xdate
pfrequ = { pfrequ = {
'year': YEARLY, 'month': MONTHLY, 'week': WEEKLY, 'day': DAILY} 'year': YEARLY, 'month': MONTHLY, 'week': WEEKLY, 'day': DAILY}
pweekday = { pweekday = {
@ -224,6 +266,10 @@ class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView):
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)]
move_event = params.get('move_event', self.move_event)
if move_event not in ['nop', 'before', 'after']:
move_event = 'nop'
setpos = params.get('setpos', self.setpos) setpos = params.get('setpos', self.setpos)
if setpos is not None: if setpos is not None:
setpos = 1 if setpos < 1 else 4 if setpos > 4 else setpos setpos = 1 if setpos < 1 else 4 if setpos > 4 else setpos
@ -251,7 +297,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):
result.append(x.date()) x_date = get_moved_date(x.date(), move_event)
# if date was re-arranged backwards and we are before
# query_date - skip it
if x_date >= query_date:
result.append(x_date)
if len(result) >= count: if len(result) >= count:
break break
return result return result
@ -302,22 +353,22 @@ class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView):
return self.cashbook.currency.id return self.cashbook.currency.id
@fields.depends('nextrun') @fields.depends('nextrun')
def on_change_with_nextrun_link(self, name=None): def on_change_with_nextrun_date(self, name=None):
""" get nextrun-record if exist """ get nextrun-record if exist
Args: Args:
name (str, optional): field name. Defaults to None. name (str, optional): field name. Defaults to None.
Returns: Returns:
int: id of nextrun-record or None date: date of nextrun or None
""" """
if self.nextrun: if self.nextrun:
return self.nextrun[0].id return self.nextrun[0].date
return None return None
@fields.depends( @fields.depends(
'start_date', 'end_date', 'frequ', 'weekday', 'monthday', 'start_date', 'end_date', 'frequ', 'weekday', 'monthday',
'interval', 'setpos') 'interval', 'setpos', 'move_event')
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
@ -402,6 +453,54 @@ class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView):
""" """
self.on_change_frequ() self.on_change_frequ()
@staticmethod
def order_nextrun_date(tables):
""" get query to sort by date of next execution
Args:
tables (list): tables
Returns:
list of query: sort-query
"""
pool = Pool()
Nextrun = pool.get('cashbook.planner.nextrun')
Planner2 = pool.get('cashbook.planner')
tab_nxrun = Nextrun.__table__()
tab_plan = Planner2.__table__()
table, _ = tables[None]
query = tab_plan.join(
tab_nxrun,
condition=tab_nxrun.planner == tab_plan.id
).select(
tab_nxrun.date,
where=tab_plan.id == table.id)
return [query]
@classmethod
def search_nextrun_date(cls, name, clause):
""" get query for search on 'nextrun_date'
Args:
name (str): name of field to search on
clause (dict): search clause
Returns:
list of dict: search clause
"""
return [('nextrun.date',) + tuple(clause[1:])]
@classmethod
def default_move_event(cls):
""" 'no operation' as default for
business-day occurence
Returns:
str: nop
"""
return 'nop'
@classmethod @classmethod
def default_wfcheck(cls): def default_wfcheck(cls):
""" False as default for wf-state 'checked' """ False as default for wf-state 'checked'
@ -515,6 +614,15 @@ class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView):
quantity_txt = Report.format_number( quantity_txt = Report.format_number(
quantity, lang=None, digits=uom_digits) quantity, lang=None, digits=uom_digits)
asset_rate = '-'
if quantity and amount is not None:
asset_rate = '%(rate)s %(currency)s/%(uom)s' % {
'rate': Report.format_number(
amount / quantity, lang=None,
digits=to_book.currency.digits),
'currency': to_book.currency.symbol,
'uom': to_book.quantity_uom.symbol}
return Template(linedata.get('description')).safe_substitute({ return Template(linedata.get('description')).safe_substitute({
'date': Report.format_date(line_date, lang=None), 'date': Report.format_date(line_date, lang=None),
'month': line_date.month, 'month': line_date.month,
@ -522,7 +630,8 @@ class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView):
'amount': Report.format_currency( 'amount': Report.format_currency(
amount, lang=None, currency=from_book.currency) amount, lang=None, currency=from_book.currency)
if (amount is not None) and from_book else '-', if (amount is not None) and from_book else '-',
'quantity': quantity_txt}) 'quantity': quantity_txt,
'rate': asset_rate})
@classmethod @classmethod
def update_next_occurence(cls, records, query_date=None): def update_next_occurence(cls, records, query_date=None):

View file

@ -60,6 +60,7 @@ class PlannerTestCase(object):
self.assertEqual(job.monthday, 1) self.assertEqual(job.monthday, 1)
self.assertEqual(job.interval, 1) self.assertEqual(job.interval, 1)
self.assertEqual(job.setpos, None) self.assertEqual(job.setpos, None)
self.assertEqual(job.move_event, 'nop')
self.assertEqual( self.assertEqual(
job.nextdates, job.nextdates,
'05/01/2022 | 06/01/2022 | 07/01/2022 | 08/01/2022 |' + '05/01/2022 | 06/01/2022 | 07/01/2022 | 08/01/2022 |' +
@ -115,11 +116,63 @@ class PlannerTestCase(object):
'Depot | 0.00 usd | Open | 0.0000 u') 'Depot | 0.00 usd | Open | 0.0000 u')
return book return book
@with_transaction()
def test_func_holiday_parseconfig(self):
""" check function holiday_parseconfig()
"""
Config = Pool().get('cashbook.configuration')
company = self.prep_company()
with Transaction().set_context({'company': company.id}):
# check valid data
result = Config.holiday_parseconfig(
'2022-05-01;12-25;12-25:+1;easter;easter:-2;easterjul;' +
'ascension;whitsun;whitsun:+1;',
[2022, 2023, 2024])
self.assertEqual(result, {
'definition': '2022-05-01;12-25;12-25:+1;easter;easter:-2;' +
'easterjul;ascension;whitsun;whitsun:+1',
'dates': [
date(2022, 5, 1), date(2022, 12, 25), date(2023, 12, 25),
date(2024, 12, 25), date(2022, 12, 26), date(2023, 12, 26),
date(2024, 12, 26), date(2022, 4, 17), date(2023, 4, 9),
date(2024, 3, 31), date(2022, 4, 15), date(2023, 4, 7),
date(2024, 3, 29), date(2022, 4, 11), date(2023, 4, 3),
date(2024, 4, 22), date(2022, 5, 26), date(2023, 5, 18),
date(2024, 5, 9), date(2022, 6, 5), date(2023, 5, 28),
date(2024, 5, 19), date(2022, 6, 6), date(2023, 5, 29),
date(2024, 5, 20)]})
# check invalid data
self.assertEqual(
Config.holiday_parseconfig('not-a-value;'),
{'definition': '', 'dates': []})
# check no data
self.assertEqual(
Config.holiday_parseconfig(''),
{'definition': '', 'dates': []})
self.assertEqual(
Config.holiday_parseconfig(None),
{'definition': '', 'dates': []})
with Transaction().set_context({'holiday_years': [2022]}):
cfg1 = Config(holidays='2022-05-01;easter;whitsun')
cfg1.save()
self.assertEqual(
cfg1.holiday_dates([2022]),
[date(2022, 5, 1), date(2022, 4, 17), date(2022, 6, 5)])
self.assertEqual(
cfg1.holidays_info,
'04/17/2022|05/01/2022|06/05/2022')
@with_transaction() @with_transaction()
def test_planner_create_job(self): def test_planner_create_job(self):
""" create job, check rule + constraints """ create job, check rule + constraints
""" """
Planner = Pool().get('cashbook.planner') pool = Pool()
Planner = pool.get('cashbook.planner')
Config = pool.get('cashbook.configuration')
job = self.prep_create_job() job = self.prep_create_job()
self.assertEqual( self.assertEqual(
@ -192,6 +245,53 @@ 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)])
# set up holidays
cfg1 = Config(
holidays='01-01;05-01;easter:+1;easter:-2;ascension;whitsun:+1')
cfg1.save()
# 1st of may, should be moved to 2nd of may
self.assertEqual(
job._compute_dates_by_rrule(
query_date=date(2022, 4, 25), count=3,
params={
'end_date': None, 'start_date': date(2022, 5, 1),
'move_event': 'after', 'weekday': None,
'setpos': None, 'interval': 1, 'frequ': 'year',
'monthday': None}),
[date(2022, 5, 2), date(2023, 5, 2), date(2024, 5, 2)])
# easter of 2022, occurence-date moved to tuesday after easter'22
self.assertEqual(
job._compute_dates_by_rrule(
query_date=date(2022, 4, 10), count=3,
params={
'end_date': None, 'start_date': date(2022, 4, 17),
'move_event': 'after', 'weekday': None,
'setpos': None,
'interval': 1, 'frequ': 'month', 'monthday': None}),
[date(2022, 4, 19), date(2022, 5, 17), date(2022, 6, 17)])
# easter of 2022, monthly, occurence-date moved to
# thursday before easter'22
self.assertEqual(
job._compute_dates_by_rrule(
query_date=date(2022, 4, 10), count=3,
params={
'end_date': None, 'start_date': date(2022, 4, 17),
'move_event': 'before', 'weekday': None,
'setpos': None,
'interval': 1, 'frequ': 'month', 'monthday': None}),
[date(2022, 4, 14), date(2022, 5, 17), date(2022, 6, 17)])
# easter of 2022, monthly, check next occurence after easter
# recompute date at moved occurence-date+1
self.assertEqual(
job._compute_dates_by_rrule(
query_date=date(2022, 4, 15), count=3,
params={
'end_date': None, 'start_date': date(2022, 4, 17),
'move_event': 'before', 'weekday': None,
'setpos': None,
'interval': 1, 'frequ': 'month', 'monthday': None}),
[date(2022, 5, 17), date(2022, 6, 17), date(2022, 7, 15)])
Planner.write(*[[job], { Planner.write(*[[job], {
'frequ': 'year', 'start_date': date(2022, 5, 1), 'frequ': 'year', 'start_date': date(2022, 5, 1),
'setpos': None, 'monthday': None, 'interval': 1, 'setpos': None, 'monthday': None, 'interval': 1,
@ -292,6 +392,20 @@ class PlannerTestCase(object):
Planner.update_next_occurence([job], query_date=date(2022, 5, 25)) Planner.update_next_occurence([job], query_date=date(2022, 5, 25))
self.assertEqual(len(job.nextrun), 1) self.assertEqual(len(job.nextrun), 1)
self.assertEqual(job.nextrun[0].date, date(2022, 6, 1)) self.assertEqual(job.nextrun[0].date, date(2022, 6, 1))
self.assertEqual(job.nextrun_date, date(2022, 6, 1))
# check searcher + order
self.assertEqual(
Planner.search(
[('nextrun_date', '=', date(2022, 6, 1))],
order=[('nextrun_date', 'ASC')]),
[job])
self.assertEqual(
Planner.search_count([('nextrun_date', '=', date(2022, 6, 1))]),
1)
self.assertEqual(
Planner.search_count([('nextrun_date', '=', date(2022, 6, 2))]),
0)
Planner.update_next_occurence([job], query_date=date(2022, 5, 30)) Planner.update_next_occurence([job], query_date=date(2022, 5, 30))
self.assertEqual(len(job.nextrun), 1) self.assertEqual(len(job.nextrun), 1)
@ -364,12 +478,13 @@ class PlannerTestCase(object):
asset_book = self.prep_planner_asset_book() asset_book = self.prep_planner_asset_book()
self.assertEqual(Planner.fill_placeholder({ self.assertEqual(Planner.fill_placeholder({
'date': date(2022, 5, 2), 'date': date(2022, 5, 2),
'amount': Decimal('12.4567'), 'amount': Decimal('126.4567'),
'quantity': Decimal('32.4423'), 'quantity': Decimal('32.4423'),
'cashbook': asset_book.id, 'cashbook': asset_book.id,
'booktransf': asset_book.id,
'description': '- ${amount} - ${date} - ${month} - ' + 'description': '- ${amount} - ${date} - ${month} - ' +
'${year} - ${quantity}'}), '${year} - ${quantity} - ${rate}'}),
'- usd12.46 - 05/02/2022 - 5 - 2022 - 32.4423\xa0u') '- usd126.46 - 05/02/2022 - 5 - 2022 - 32.4423\xa0u - 3.90 usd/u')
@with_transaction() @with_transaction()
def test_planner_cronjobs_booking_with_category(self): def test_planner_cronjobs_booking_with_category(self):

View file

@ -1,5 +1,5 @@
[tryton] [tryton]
version=7.0.0 version=7.0.6
depends: depends:
cashbook cashbook
extras_depend: extras_depend:
@ -8,6 +8,7 @@ xml:
group.xml group.xml
planner.xml planner.xml
nextrun.xml nextrun.xml
config.xml
cashbook.xml cashbook.xml
cron.xml cron.xml
menu.xml menu.xml

View file

@ -0,0 +1,15 @@
<?xml version="1.0"?>
<!-- This file is part of the cashbook-planner from m-ds for Tryton.
The COPYRIGHT file at the top level of this repository contains the
full copyright notices and license terms. -->
<data>
<xpath expr="/form/separator[@id='sepcb']" position="before">
<separator id="planner" colspan="4" string="Scheduled Bookings"/>
<label name="holidays"/>
<field name="holidays" colspan="3"/>
<field name="holidays_info" colspan="4"/>
</xpath>
</data>

View file

@ -12,8 +12,8 @@ full copyright notices and license terms. -->
<label name="active"/> <label name="active"/>
<field name="active"/> <field name="active"/>
<label name="nextrun_link" colspan="5"/> <label name="nextrun_date" colspan="5"/>
<field name="nextrun_link"/> <field name="nextrun_date"/>
<notebook colspan="6"> <notebook colspan="6">
<page id="rrule" col="6" string="Recurrence Rule"> <page id="rrule" col="6" string="Recurrence Rule">
@ -35,6 +35,10 @@ full copyright notices and license terms. -->
<label name="monthday"/> <label name="monthday"/>
<field name="monthday"/> <field name="monthday"/>
<label name="move_event"/>
<field name="move_event"/>
<newline/>
<separator id="sep2" colspan="6" string="Result of the recurrence rule"/> <separator id="sep2" colspan="6" string="Result of the recurrence rule"/>
<field name="nextdates" colspan="6"/> <field name="nextdates" colspan="6"/>
</page> </page>
@ -59,7 +63,7 @@ full copyright notices and license terms. -->
<separator name="subject" colspan="4" string="Booking text"/> <separator name="subject" colspan="4" string="Booking text"/>
<field name="subject" colspan="4"/> <field name="subject" colspan="4"/>
<label id="lab1" colspan="4" xalign="0.0" <label id="lab1" colspan="4" xalign="0.0"
string="Available placeholders: ${date} ${month} ${year} ${amount} ${quantity}"/> string="Available placeholders: ${date} ${month} ${year} ${amount} ${quantity} ${rate}"/>
</page> </page>
<page name="description" col="1" string="Description"> <page name="description" col="1" string="Description">
<field name="description"/> <field name="description"/>

View file

@ -14,7 +14,7 @@ full copyright notices and license terms. -->
<field name="wfcheck" optional="1"/> <field name="wfcheck" optional="1"/>
<field name="cashbook" optional="0"/> <field name="cashbook" optional="0"/>
<field name="nextrun_link" optional="0"/> <field name="nextrun_date" optional="0"/>
<field name="amount" optional="0"/> <field name="amount" optional="0"/>
<field name="bookingtype" optional="0"/> <field name="bookingtype" optional="0"/>
<field name="booking_target" optional="0"/> <field name="booking_target" optional="0"/>