Compare commits
28 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
01ee421d45 | ||
![]() |
a81324f3ec | ||
![]() |
88bac83429 | ||
![]() |
95e12414bc | ||
![]() |
1f05fa43c6 | ||
![]() |
46eb9a8c23 | ||
![]() |
3ed2216cbf | ||
![]() |
f397cd593c | ||
![]() |
8a08517a44 | ||
![]() |
a6097c1022 | ||
![]() |
b59e25760d | ||
![]() |
1536e8f22f | ||
![]() |
9685670672 | ||
![]() |
e4963beee9 | ||
![]() |
4f9e6ad1a0 | ||
![]() |
b5f774003f | ||
![]() |
73a7c1d86c | ||
![]() |
353b622074 | ||
![]() |
0fda36881e | ||
![]() |
3220527fe4 | ||
![]() |
ee593bd49f | ||
![]() |
6f27beb7a6 | ||
![]() |
722b265c33 | ||
![]() |
a3cba97892 | ||
![]() |
12839d06db | ||
![]() |
b47517c4d8 | ||
![]() |
b321965515 | ||
![]() |
170f5885d2 |
17 changed files with 875 additions and 43 deletions
2
.hgignore → .gitignore
vendored
2
.hgignore → .gitignore
vendored
|
@ -1,4 +1,4 @@
|
|||
syntax: glob
|
||||
*.pyc
|
||||
.env
|
||||
build/*
|
||||
dist/*
|
34
README.rst
34
README.rst
|
@ -14,6 +14,36 @@ Requires
|
|||
Changes
|
||||
=======
|
||||
|
||||
*7.0.0 - 24.02.2024*
|
||||
*7.0.8 - 05.11.2024*
|
||||
|
||||
- init
|
||||
- updt: optimize book-now button
|
||||
|
||||
*7.0.7 - 28.09.2024*
|
||||
|
||||
- add: recurrence rule 'last day of month'
|
||||
- add: button 'execute booking now'
|
||||
- add: notify
|
||||
|
||||
*7.0.6 - 19.07.2024*
|
||||
|
||||
- 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
|
||||
|
|
|
@ -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
211
config.py
Normal 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
15
config.xml
Normal 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>
|
|
@ -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">
|
||||
|
|
6
ir.py
6
ir.py
|
@ -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')
|
||||
return result
|
||||
return result | {
|
||||
'cashbook.planner',
|
||||
'cashbook.planner.nextrun',
|
||||
}
|
||||
|
||||
# end Rule
|
||||
|
|
100
locale/de.po
100
locale/de.po
|
@ -3,6 +3,18 @@ msgid ""
|
|||
msgstr "Content-Type: text/plain; charset=utf-8\n"
|
||||
|
||||
|
||||
##############
|
||||
# ir.message #
|
||||
##############
|
||||
msgctxt "model:ir.message,text:msg_title_notify"
|
||||
msgid "Cashbook Scheduled Booking"
|
||||
msgstr "Kassenbuch geplante Buchung"
|
||||
|
||||
msgctxt "model:ir.message,text:msg_text_notify"
|
||||
msgid "The following transaction was generated: %(bname)s"
|
||||
msgstr "Die folgende Buchung wurde erzeugt: %(bname)s"
|
||||
|
||||
|
||||
###########
|
||||
# ir.cron #
|
||||
###########
|
||||
|
@ -35,6 +47,22 @@ msgid "Scheduled Bookings"
|
|||
msgstr "geplante Buchungen"
|
||||
|
||||
|
||||
###################
|
||||
# ir.model.button #
|
||||
###################
|
||||
msgctxt "model:ir.model.button,string:book_now_button2"
|
||||
msgid "Execute Booking Now"
|
||||
msgstr "Buchung jetzt ausführen"
|
||||
|
||||
msgctxt "model:ir.model.button,help:book_now_button2"
|
||||
msgid "The planned booking is brought forward and executed now. The next posting is then scheduled regularly for the following execution."
|
||||
msgstr "Die geplante Buchung wird vorgezogen und jetzt ausgeführt. Die nächste Buchung wird dann regulär für die nachfolgende Ausführung geplant."
|
||||
|
||||
msgctxt "model:ir.model.button,confirm:book_now_button2"
|
||||
msgid "The booking is now being made. Continue?"
|
||||
msgstr "Die Buchung wird nun durchgeführt. Fortsetzen?"
|
||||
|
||||
|
||||
#################
|
||||
# ir.rule.group #
|
||||
#################
|
||||
|
@ -79,6 +107,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 +159,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"
|
||||
|
@ -204,7 +256,7 @@ msgstr "Tag des Monats"
|
|||
|
||||
msgctxt "help:cashbook.planner,monthday:"
|
||||
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:"
|
||||
msgid "Interval"
|
||||
|
@ -234,7 +286,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 +370,46 @@ 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."
|
||||
|
||||
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."
|
||||
|
||||
msgctxt "field:cashbook.planner,notify_bycron:"
|
||||
msgid "Notify"
|
||||
msgstr "Benachrichtigen"
|
||||
|
||||
msgctxt "help:cashbook.planner,notify_bycron:"
|
||||
msgid "A notification will appear in the web browser when the booking has been created."
|
||||
msgstr "Es wird eine Benachrichtigung im Webbrowser angezeigt wenn die Buchung erstellt wurde."
|
||||
|
||||
|
||||
############################
|
||||
# cashbook.planner.nextrun #
|
||||
|
|
90
locale/en.po
90
locale/en.po
|
@ -2,6 +2,14 @@
|
|||
msgid ""
|
||||
msgstr "Content-Type: text/plain; charset=utf-8\n"
|
||||
|
||||
msgctxt "model:ir.message,text:msg_title_notify"
|
||||
msgid "Cashbook Scheduled Booking"
|
||||
msgstr "Cashbook Scheduled Booking"
|
||||
|
||||
msgctxt "model:ir.message,text:msg_text_notify"
|
||||
msgid "The following transaction was generated: %(bname)s"
|
||||
msgstr "The following transaction was generated: %(bname)s"
|
||||
|
||||
msgctxt "selection:ir.cron,method:"
|
||||
msgid "Execute scheduled bookings"
|
||||
msgstr "Execute scheduled bookings"
|
||||
|
@ -18,6 +26,18 @@ msgctxt "model:ir.action,name:act_planner_view"
|
|||
msgid "Scheduled Bookings"
|
||||
msgstr "Scheduled Bookings"
|
||||
|
||||
msgctxt "model:ir.model.button,string:book_now_button2"
|
||||
msgid "Execute Booking Now"
|
||||
msgstr "Execute Booking Now"
|
||||
|
||||
msgctxt "model:ir.model.button,help:book_now_button2"
|
||||
msgid "The planned booking is brought forward and executed now. The next posting is then scheduled regularly for the following execution."
|
||||
msgstr "The planned booking is brought forward and executed now. The next posting is then scheduled regularly for the following execution."
|
||||
|
||||
msgctxt "model:ir.model.button,confirm:book_now_button2"
|
||||
msgid "The booking is now being made. Continue?"
|
||||
msgstr "The booking is now being made. Continue?"
|
||||
|
||||
msgctxt "model:ir.rule.group,name:rg_planner_admin"
|
||||
msgid "Administrators: scheduled bookings read/write"
|
||||
msgstr "Administrators: scheduled bookings read/write"
|
||||
|
@ -58,6 +78,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 +123,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 +250,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 +334,46 @@ 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 "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."
|
||||
|
||||
msgctxt "field:cashbook.planner,notify_bycron:"
|
||||
msgid "Notify"
|
||||
msgstr "Notify"
|
||||
|
||||
msgctxt "help:cashbook.planner,notify_bycron:"
|
||||
msgid "A notification will appear in the web browser when the booking has been created."
|
||||
msgstr "A notification will appear in the web browser when the booking has been created."
|
||||
|
||||
msgctxt "model:cashbook.planner.nextrun,name:"
|
||||
msgid "Next Execution Date"
|
||||
msgstr "Next Execution Date"
|
||||
|
@ -330,3 +410,7 @@ msgctxt "view:cashbook.line:"
|
|||
msgid "Scheduled Bookings"
|
||||
msgstr "Scheduled Bookings"
|
||||
|
||||
msgctxt "field:cashbook.line,planners:"
|
||||
msgid "Scheduled Bookings"
|
||||
msgstr "Scheduled Bookings"
|
||||
|
||||
|
|
16
message.xml
Normal file
16
message.xml
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?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.message" id="msg_title_notify">
|
||||
<field name="text">Cashbook Scheduled Booking</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_text_notify">
|
||||
<field name="text">The following transaction was generated: %(bname)s</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</tryton>
|
239
planner.py
239
planner.py
|
@ -14,6 +14,7 @@ from trytond.pool import Pool
|
|||
from trytond.report import Report
|
||||
from trytond.i18n import gettext
|
||||
from trytond.pyson import Eval, Bool, If, And
|
||||
from trytond.bus import notify
|
||||
from trytond.modules.currency.fields import Monetary
|
||||
from trytond.modules.cashbook.book import sel_state_book
|
||||
from trytond.modules.cashbook.line import sel_bookingtype as sel_bookingtype_cb
|
||||
|
@ -34,6 +35,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):
|
||||
|
@ -86,7 +92,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.',
|
||||
|
@ -98,10 +111,16 @@ 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.')
|
||||
notify_bycron = fields.Boolean(
|
||||
string='Notify', help='A notification will appear in the web ' +
|
||||
'browser when the booking has been created.')
|
||||
|
||||
bookingtype = fields.Selection(
|
||||
string='Type', selection=sel_bookingtype, required=True,
|
||||
|
@ -159,6 +178,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(
|
||||
|
@ -170,8 +191,10 @@ class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView):
|
|||
Index(
|
||||
t,
|
||||
(t.end_date, Index.Range(order='ASC')),
|
||||
where=t.end_date != DEF_NONE),
|
||||
})
|
||||
where=t.end_date != DEF_NONE)})
|
||||
cls._buttons.update({
|
||||
'book_now': {'readonly': ~Eval('active', False)},
|
||||
})
|
||||
|
||||
def get_rec_name(self, name=None):
|
||||
""" get formatted name of record
|
||||
|
@ -189,7 +212,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 +230,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 = {
|
||||
|
@ -221,9 +276,15 @@ 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)]
|
||||
|
||||
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
|
||||
|
@ -237,6 +298,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"
|
||||
|
||||
|
@ -251,7 +324,17 @@ 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 = 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
|
||||
if x_date >= query_date:
|
||||
result.append(x_date)
|
||||
if len(result) >= count:
|
||||
break
|
||||
return result
|
||||
|
@ -302,22 +385,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', '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
|
||||
|
@ -350,7 +433,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')
|
||||
|
@ -377,31 +461,86 @@ 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.monthday is None:
|
||||
self.monthday = 1
|
||||
if self.last_day_of_month:
|
||||
self.monthday = None
|
||||
else:
|
||||
if self.monthday is None:
|
||||
self.monthday = 1
|
||||
self.setpos = None
|
||||
else:
|
||||
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
|
||||
"""
|
||||
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'
|
||||
|
@ -447,6 +586,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
|
||||
|
@ -470,6 +618,15 @@ class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView):
|
|||
IrDate = Pool().get('ir.date')
|
||||
return IrDate.today()
|
||||
|
||||
@classmethod
|
||||
def default_notify_bycron(cls):
|
||||
""" get False as default
|
||||
|
||||
Returns:
|
||||
boolean: False
|
||||
"""
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def fill_placeholder(cls, linedata):
|
||||
""" replace placeholder in description
|
||||
|
@ -515,6 +672,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 +688,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):
|
||||
|
@ -578,6 +745,20 @@ class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView):
|
|||
if to_write:
|
||||
NextRun.write(*to_write)
|
||||
|
||||
@classmethod
|
||||
@ModelView.button
|
||||
def book_now(cls, records):
|
||||
""" run planned booking now
|
||||
"""
|
||||
to_work = [x for x in records if x.active and x.nextrun_date]
|
||||
cls.run_booking(to_work)
|
||||
|
||||
for record in to_work:
|
||||
if record.active:
|
||||
cls.update_next_occurence(
|
||||
[record],
|
||||
query_date=record.nextrun_date + timedelta(days=1))
|
||||
|
||||
@classmethod
|
||||
def create(cls, vlist):
|
||||
""" update nextrun-records on create of planner-records
|
||||
|
@ -675,11 +856,27 @@ class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView):
|
|||
else:
|
||||
to_create.append(line)
|
||||
|
||||
to_notify = []
|
||||
if to_create_check:
|
||||
lines = Line.create(to_create_check)
|
||||
Line.wfcheck(lines)
|
||||
to_notify.extend([
|
||||
x for x in lines
|
||||
if x.planners[0].notify_bycron])
|
||||
|
||||
if to_create:
|
||||
Line.create(to_create)
|
||||
lines = Line.create(to_create)
|
||||
to_notify.extend([
|
||||
x for x in lines
|
||||
if x.planners[0].notify_bycron])
|
||||
|
||||
for line in to_notify:
|
||||
notify(
|
||||
title=gettext('cashbook_planner.msg_title_notify'),
|
||||
body=gettext(
|
||||
'cashbook_planner.msg_text_notify',
|
||||
bname=line.rec_name),
|
||||
user=line.cashbook.owner.id)
|
||||
|
||||
@classmethod
|
||||
def cronjob(cls):
|
||||
|
|
13
planner.xml
13
planner.xml
|
@ -157,5 +157,18 @@ full copyright notices and license terms. -->
|
|||
<field name="rule_group" ref="rg_planner_companies"/>
|
||||
</record>
|
||||
|
||||
<!-- button -->
|
||||
<record model="ir.model.button" id="book_now_button2">
|
||||
<field name="name">book_now</field>
|
||||
<field name="string">Execute Booking Now</field>
|
||||
<field name="confirm">The booking is now being made. Continue?</field>
|
||||
<field name="help">The planned booking is brought forward and executed now. The next posting is then scheduled regularly for the following execution.</field>
|
||||
<field name="model" search="[('model', '=', 'cashbook.planner')]"/>
|
||||
</record>
|
||||
<record model="ir.model.button-res.group" id="book_now_button2-group_planner">
|
||||
<field name="button" ref="book_now_button2"/>
|
||||
<field name="group" ref="group_planner"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</tryton>
|
||||
|
|
151
tests/planner.py
151
tests/planner.py
|
@ -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,64 @@ 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')
|
||||
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,
|
||||
|
@ -277,6 +388,23 @@ class PlannerTestCase(object):
|
|||
'setpos': 5, 'monthday': None, 'weekday': '2',
|
||||
'end_date': None}])
|
||||
|
||||
@with_transaction()
|
||||
def test_planner_run_booking_now(self):
|
||||
""" create job, press button 'booknow'
|
||||
"""
|
||||
Planner = Pool().get('cashbook.planner')
|
||||
|
||||
job = self.prep_create_job()
|
||||
self.assertEqual(
|
||||
job.rec_name, "Job 1|Book 1|Exp|Cat1|05/01/2022|usd0.00")
|
||||
self.assertEqual(
|
||||
job._compute_dates_by_rrule(
|
||||
count=1, query_date=date(2022, 5, 1)), [
|
||||
date(2022, 5, 1)])
|
||||
Planner.book_now([job])
|
||||
self.assertEqual(
|
||||
job.rec_name, "Job 1|Book 1|Exp|Cat1|06/01/2022|usd0.00")
|
||||
|
||||
@with_transaction()
|
||||
def test_planner_create_update_nextrun(self):
|
||||
""" create job, check nextrun-record
|
||||
|
@ -292,6 +420,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 +506,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):
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
[tryton]
|
||||
version=7.0.0
|
||||
version=7.0.8
|
||||
depends:
|
||||
cashbook
|
||||
extras_depend:
|
||||
cashbook_investment
|
||||
xml:
|
||||
message.xml
|
||||
group.xml
|
||||
planner.xml
|
||||
nextrun.xml
|
||||
config.xml
|
||||
cashbook.xml
|
||||
cron.xml
|
||||
menu.xml
|
||||
|
|
15
view/configuration_form.xml
Normal file
15
view/configuration_form.xml
Normal 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>
|
|
@ -12,8 +12,9 @@ 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="3"/>
|
||||
<field name="nextrun_date"/>
|
||||
<button colspan="2" name="book_now"/>
|
||||
|
||||
<notebook colspan="6">
|
||||
<page id="rrule" col="6" string="Recurrence Rule">
|
||||
|
@ -34,6 +35,12 @@ 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"/>
|
||||
<newline/>
|
||||
|
||||
<separator id="sep2" colspan="6" string="Result of the recurrence rule"/>
|
||||
<field name="nextdates" colspan="6"/>
|
||||
|
@ -54,12 +61,13 @@ full copyright notices and license terms. -->
|
|||
|
||||
<label name="wfcheck"/>
|
||||
<field name="wfcheck"/>
|
||||
<newline/>
|
||||
<label name="notify_bycron"/>
|
||||
<field name="notify_bycron"/>
|
||||
|
||||
<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"/>
|
||||
|
|
|
@ -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"/>
|
||||
|
|
Loading…
Reference in a new issue