Compare commits

...

17 commits

Author SHA1 Message Date
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
14 changed files with 615 additions and 31 deletions

View file

@ -14,6 +14,22 @@ Requires
Changes
=======
*7.0.0 - 24.02.2024*
*7.0.5 - 08.06.2024*
- init
- 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 .cron import Cron
from .nextrun import NextRun
from .config import Configuration, UserConfiguration
def register():
Pool.register(
Configuration,
UserConfiguration,
Rule,
ScheduledBooking,
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">
<field name="name">Cashbook - Scheduled Bookings</field>
<field name="parent" ref="cashbook.group_cashbook"/>
</record>
<record model="res.user-res.group" id="user_admin-group_planner">

4
ir.py
View file

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

View file

@ -79,6 +79,30 @@ msgid "User in companies"
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 #
####################
@ -107,8 +131,8 @@ msgid "Booking text"
msgstr "Buchungstext"
msgctxt "view:cashbook.planner:"
msgid "Available placeholders: ${date} ${month} ${year} ${amount} ${quantity}"
msgstr "verfügbare Platzhalter: ${date} ${month} ${year} ${amount} ${quantity}"
msgid "Available placeholders: ${date} ${month} ${year} ${amount} ${quantity} ${rate}"
msgstr "verfügbare Platzhalter: ${date} ${month} ${year} ${amount} ${quantity} ${rate}"
msgctxt "view:cashbook.planner:"
msgid "Cashbook lines"
@ -234,7 +258,7 @@ msgctxt "field:cashbook.planner,nextrun:"
msgid "Next Execution Date"
msgstr "Nächster Ausführungstermin"
msgctxt "field:cashbook.planner,nextrun_link:"
msgctxt "field:cashbook.planner,nextrun_date:"
msgid "Next Execution Date"
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."
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 #

View file

@ -58,6 +58,26 @@ msgctxt "model:ir.rule.group,name:rg_nextrun_companies"
msgid "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:"
msgid "Scheduled Booking"
msgstr "Scheduled Booking"
@ -83,8 +103,8 @@ msgid "Booking text"
msgstr "Booking text"
msgctxt "view:cashbook.planner:"
msgid "Available placeholders: ${date} ${month} ${year} ${amount} ${quantity}"
msgstr "Available placeholders: ${date} ${month} ${year} ${amount} ${quantity}"
msgid "Available placeholders: ${date} ${month} ${year} ${amount} ${quantity} ${rate}"
msgstr "Available placeholders: ${date} ${month} ${year} ${amount} ${quantity} ${rate}"
msgctxt "view:cashbook.planner:"
msgid "Cashbook lines"
@ -210,7 +230,7 @@ msgctxt "field:cashbook.planner,nextrun:"
msgid "Next Execution Date"
msgstr "Next Execution Date"
msgctxt "field:cashbook.planner,nextrun_link:"
msgctxt "field:cashbook.planner,nextrun_date:"
msgid "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."
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:"
msgid "Next Execution Date"
msgstr "Next Execution Date"

View file

@ -34,6 +34,11 @@ SEL_WEEKDAY = [
('0', 'Monday'), ('1', 'Tuesday'), ('2', 'Wednesday'),
('3', 'Thursday'), ('4', 'Friday'), ('5', 'Saturday'),
('6', 'Sunday')]
SEL_MOVE_EVENT = [
('nop', 'unchanged'),
('before', 'Business day before original date'),
('after', 'Business day after original date')
]
class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView):
@ -98,10 +103,13 @@ class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView):
nextrun = fields.One2Many(
string='Next Execution Date', size=1, field='planner',
model_name='cashbook.planner.nextrun')
nextrun_link = fields.Function(fields.Many2One(
string='Next Execution Date', readonly=True,
model_name='cashbook.planner.nextrun'),
'on_change_with_nextrun_link')
nextrun_date = fields.Function(fields.Date(
string='Next Execution Date', readonly=True),
'on_change_with_nextrun_date', searcher='search_nextrun_date')
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(
string='Type', selection=sel_bookingtype, required=True,
@ -159,6 +167,8 @@ class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView):
@classmethod
def __setup__(cls):
super(ScheduledBooking, cls).__setup__()
cls._order.insert(0, ('name', 'ASC'))
cls._order.insert(0, ('nextrun_date', 'ASC'))
t = cls.__table__()
cls._sql_indexes.update({
Index(
@ -189,7 +199,8 @@ class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView):
self.booktransf.name
if self.booktransf
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(
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 {},
allowed: frequ, weekday, start_date,
end_date (preferred over 'count'),
monthday, interval, setpos
monthday, interval, setpos, move_event
Returns:
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 = {
'year': YEARLY, 'month': MONTHLY, 'week': WEEKLY, 'day': DAILY}
pweekday = {
@ -224,6 +266,10 @@ class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView):
end_date = params.get('end_date', self.end_date)
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)
if setpos is not None:
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:
if (query_date and (x.date() >= query_date)) or \
(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:
break
return result
@ -302,22 +353,22 @@ class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView):
return self.cashbook.currency.id
@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
Args:
name (str, optional): field name. Defaults to None.
Returns:
int: id of nextrun-record or None
date: date of nextrun or None
"""
if self.nextrun:
return self.nextrun[0].id
return self.nextrun[0].date
return None
@fields.depends(
'start_date', 'end_date', 'frequ', 'weekday', 'monthday',
'interval', 'setpos')
'interval', 'setpos', 'move_event')
def on_change_with_nextdates(self, name=None):
""" Calculates the next 5 appointments based on the configured rule,
returns a formatted date list
@ -402,6 +453,54 @@ class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView):
"""
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
def default_wfcheck(cls):
""" False as default for wf-state 'checked'
@ -515,6 +614,15 @@ class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView):
quantity_txt = Report.format_number(
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({
'date': Report.format_date(line_date, lang=None),
'month': line_date.month,
@ -522,7 +630,8 @@ class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView):
'amount': Report.format_currency(
amount, lang=None, currency=from_book.currency)
if (amount is not None) and from_book else '-',
'quantity': quantity_txt})
'quantity': quantity_txt,
'rate': asset_rate})
@classmethod
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.interval, 1)
self.assertEqual(job.setpos, None)
self.assertEqual(job.move_event, 'nop')
self.assertEqual(
job.nextdates,
'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')
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()
def test_planner_create_job(self):
""" 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()
self.assertEqual(
@ -192,6 +245,53 @@ 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)])
# 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], {
'frequ': 'year', 'start_date': date(2022, 5, 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))
self.assertEqual(len(job.nextrun), 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))
self.assertEqual(len(job.nextrun), 1)
@ -364,12 +478,13 @@ class PlannerTestCase(object):
asset_book = self.prep_planner_asset_book()
self.assertEqual(Planner.fill_placeholder({
'date': date(2022, 5, 2),
'amount': Decimal('12.4567'),
'amount': Decimal('126.4567'),
'quantity': Decimal('32.4423'),
'cashbook': asset_book.id,
'booktransf': asset_book.id,
'description': '- ${amount} - ${date} - ${month} - ' +
'${year} - ${quantity}'}),
'- usd12.46 - 05/02/2022 - 5 - 2022 - 32.4423\xa0u')
'${year} - ${quantity} - ${rate}'}),
'- usd126.46 - 05/02/2022 - 5 - 2022 - 32.4423\xa0u - 3.90 usd/u')
@with_transaction()
def test_planner_cronjobs_booking_with_category(self):

View file

@ -1,5 +1,5 @@
[tryton]
version=7.0.0
version=7.0.5
depends:
cashbook
extras_depend:
@ -8,6 +8,7 @@ xml:
group.xml
planner.xml
nextrun.xml
config.xml
cashbook.xml
cron.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"/>
<field name="active"/>
<label name="nextrun_link" colspan="5"/>
<field name="nextrun_link"/>
<label name="nextrun_date" colspan="5"/>
<field name="nextrun_date"/>
<notebook colspan="6">
<page id="rrule" col="6" string="Recurrence Rule">
@ -35,6 +35,10 @@ full copyright notices and license terms. -->
<label 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"/>
<field name="nextdates" colspan="6"/>
</page>
@ -59,7 +63,7 @@ full copyright notices and license terms. -->
<separator name="subject" colspan="4" string="Booking text"/>
<field name="subject" colspan="4"/>
<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 name="description" col="1" string="Description">
<field name="description"/>

View file

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