diff --git a/locale/de.po b/locale/de.po
index 7bb3e9b..e62fd0f 100644
--- a/locale/de.po
+++ b/locale/de.po
@@ -102,6 +102,10 @@ msgctxt "view:cashbook.planner:"
msgid "Booking"
msgstr "Buchung"
+msgctxt "view:cashbook.planner:"
+msgid "Booking text"
+msgstr "Buchungstext"
+
msgctxt "field:cashbook.planner,company:"
msgid "Company"
msgstr "Unternehmen"
@@ -238,18 +242,10 @@ msgctxt "selection:cashbook.planner,bookingtype:"
msgid "Revenue"
msgstr "Einnahme"
-msgctxt "selection:cashbook.planner,bookingtype:"
-msgid "Revenue Splitbooking"
-msgstr "Einnahme Splitbuchung"
-
msgctxt "selection:cashbook.planner,bookingtype:"
msgid "Expense"
msgstr "Ausgabe"
-msgctxt "selection:cashbook.planner,bookingtype:"
-msgid "Expense Splitbooking"
-msgstr "Ausgabe Splitbuchung"
-
msgctxt "selection:cashbook.planner,bookingtype:"
msgid "Transfer from"
msgstr "Umbuchung von"
@@ -258,6 +254,50 @@ msgctxt "selection:cashbook.planner,bookingtype:"
msgid "Transfer to"
msgstr "Umbuchung nach"
+msgctxt "field:cashbook.planner,currency_cashbook:"
+msgid "Currency"
+msgstr "Währung"
+
+msgctxt "help:cashbook.planner,currency_cashbook:"
+msgid "Currency of Cashbook"
+msgstr "Währung des Kassenbuchs"
+
+msgctxt "field:cashbook.planner,amount:"
+msgid "Amount"
+msgstr "Betrag"
+
+msgctxt "field:cashbook.planner,category:"
+msgid "Category"
+msgstr "Kategorie"
+
+msgctxt "help:cashbook.planner,category:"
+msgid "Category for the planned booking"
+msgstr "Kategorie für die geplante Buchung"
+
+msgctxt "field:cashbook.planner,booktransf:"
+msgid "Source/Dest"
+msgstr "Quelle/Ziel"
+
+msgctxt "field:cashbook.planner,owner_cashbook:"
+msgid "Owner"
+msgstr "Eigentümer"
+
+msgctxt "field:cashbook.planner,state_cashbook:"
+msgid "State of Cashbook"
+msgstr "Status des Kassenbuchs"
+
+msgctxt "field:cashbook.planner,subject:"
+msgid "Booking text"
+msgstr "Buchungstext"
+
+msgctxt "field:cashbook.planner,wfcheck:"
+msgid "Set to 'Checked'"
+msgstr "auf 'Geprüft' setzen"
+
+msgctxt "help:cashbook.planner,wfcheck:"
+msgid "Switches the booking to the 'Verified' state."
+msgstr "Schaltet die Buchung in den Zustand 'Geprüft'"
+
############################
# cashbook.planner.nextrun #
diff --git a/locale/en.po b/locale/en.po
index 6ff0974..77d1b8b 100644
--- a/locale/en.po
+++ b/locale/en.po
@@ -78,6 +78,10 @@ msgctxt "view:cashbook.planner:"
msgid "Booking"
msgstr "Booking"
+msgctxt "view:cashbook.planner:"
+msgid "Booking text"
+msgstr "Booking text"
+
msgctxt "field:cashbook.planner,company:"
msgid "Company"
msgstr "Company"
@@ -214,18 +218,10 @@ msgctxt "selection:cashbook.planner,bookingtype:"
msgid "Revenue"
msgstr "Revenue"
-msgctxt "selection:cashbook.planner,bookingtype:"
-msgid "Revenue Splitbooking"
-msgstr "Revenue Splitbooking"
-
msgctxt "selection:cashbook.planner,bookingtype:"
msgid "Expense"
msgstr "Expense"
-msgctxt "selection:cashbook.planner,bookingtype:"
-msgid "Expense Splitbooking"
-msgstr "Expense Splitbooking"
-
msgctxt "selection:cashbook.planner,bookingtype:"
msgid "Transfer from"
msgstr "Transfer from"
@@ -234,6 +230,50 @@ msgctxt "selection:cashbook.planner,bookingtype:"
msgid "Transfer to"
msgstr "Transfer to"
+msgctxt "field:cashbook.planner,currency_cashbook:"
+msgid "Currency"
+msgstr "Currency"
+
+msgctxt "help:cashbook.planner,currency_cashbook:"
+msgid "Currency of Cashbook"
+msgstr "Currency of Cashbook"
+
+msgctxt "field:cashbook.planner,amount:"
+msgid "Amount"
+msgstr "Amount"
+
+msgctxt "field:cashbook.planner,category:"
+msgid "Category"
+msgstr "Category"
+
+msgctxt "help:cashbook.planner,category:"
+msgid "Category for the planned booking"
+msgstr "Category for the planned booking"
+
+msgctxt "field:cashbook.planner,booktransf:"
+msgid "Source/Dest"
+msgstr "Source/Dest"
+
+msgctxt "field:cashbook.planner,owner_cashbook:"
+msgid "Owner"
+msgstr "Owner"
+
+msgctxt "field:cashbook.planner,state_cashbook:"
+msgid "State of Cashbook"
+msgstr "State of Cashbook"
+
+msgctxt "field:cashbook.planner,subject:"
+msgid "Booking text"
+msgstr "Booking text"
+
+msgctxt "field:cashbook.planner,wfcheck:"
+msgid "Set to 'Checked'"
+msgstr "Set to 'Checked'"
+
+msgctxt "help:cashbook.planner,wfcheck:"
+msgid "Switches the booking to the 'Verified' state."
+msgstr "Switches the booking to the 'Verified' state."
+
msgctxt "model:cashbook.planner.nextrun,name:"
msgid "Next Execution Date"
msgstr "Next Execution Date"
diff --git a/planner.py b/planner.py
index b7a34b2..2c038f1 100644
--- a/planner.py
+++ b/planner.py
@@ -3,6 +3,7 @@
# The COPYRIGHT file at the top level of this repository contains the
# full copyright notices and license terms.
+from decimal import Decimal
from datetime import date, timedelta
from dateutil.rrule import (
rrule, YEARLY, MONTHLY, WEEKLY, DAILY, MO, TU, WE, TH, FR, SA, SU)
@@ -11,9 +12,15 @@ from trytond.transaction import Transaction
from trytond.pool import Pool
from trytond.report import Report
from trytond.pyson import Eval, Bool, If, And
-from trytond.modules.cashbook.line import sel_bookingtype
+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
+sel_bookingtype = [
+ x for x in sel_bookingtype_cb if x[0]
+ in ['in', 'out', 'mvin', 'mvout']]
+
DEF_NONE = None
SEL_FREQU = [
('year', 'Yearly'),
@@ -97,6 +104,48 @@ class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView):
bookingtype = fields.Selection(
string='Type', selection=sel_bookingtype, required=True,
help='Type of Booking')
+ currency_cashbook = fields.Function(fields.Many2One(
+ string='Currency', help='Currency of Cashbook',
+ model_name='currency.currency'), 'on_change_with_currency_cashbook')
+ amount = Monetary(
+ string='Amount', currency='currency_cashbook',
+ digits='currency_cashbook', required=True)
+ category = fields.Many2One(
+ string='Category', model_name='cashbook.category',
+ help='Category for the planned booking', depends=['bookingtype'],
+ states={
+ 'required': Eval('bookingtype', '').in_(['in', 'out']),
+ 'invisible': ~Eval('bookingtype', '').in_(['in', 'out'])})
+ party = fields.Many2One(
+ string='Party', model_name='party.party', depends=['bookingtype'],
+ states={
+ 'required': Eval('bookingtype', '').in_(['in', 'out']),
+ 'invisible': ~Eval('bookingtype', '').in_(['in', 'out'])})
+ booktransf = fields.Many2One(
+ string='Source/Dest',
+ ondelete='RESTRICT', model_name='cashbook.book',
+ domain=[
+ ('owner.id', '=', Eval('owner_cashbook', -1)),
+ ('id', '!=', Eval('cashbook', -1)),
+ ('btype', '!=', None)],
+ states={
+ 'readonly': Eval('state_cashbook', '') != 'open',
+ 'invisible': ~Eval('bookingtype', '').in_(['mvin', 'mvout']),
+ 'required': Eval('bookingtype', '').in_(['mvin', 'mvout'])},
+ depends=[
+ 'state_cashbook', 'bookingtype', 'owner_cashbook', 'cashbook'])
+ owner_cashbook = fields.Function(fields.Many2One(
+ string='Owner', readonly=True,
+ states={'invisible': True}, model_name='res.user'),
+ 'on_change_with_owner_cashbook')
+ state_cashbook = fields.Function(fields.Selection(
+ string='State of Cashbook',
+ readonly=True, states={'invisible': True}, selection=sel_state_book),
+ 'on_change_with_state_cashbook')
+ subject = fields.Text(string='Booking text', required=True)
+ wfcheck = fields.Boolean(
+ string="Set to 'Checked'",
+ help="Switches the booking to the 'Verified' state.")
@classmethod
def __setup__(cls):
@@ -177,6 +226,19 @@ class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView):
break
return result
+ @fields.depends('cashbook', '_parent_cashbook.currency')
+ def on_change_with_currency_cashbook(self, name=None):
+ """ get currency of selected cashbook
+
+ Args:
+ name (str, optional): name of field. Defaults to None.
+
+ Returns:
+ int: id of cashbook currency
+ """
+ if self.cashbook:
+ return self.cashbook.currency.id
+
@fields.depends('nextrun')
def on_change_with_nextrun_link(self, name=None):
""" get nextrun-record if exist
@@ -229,6 +291,30 @@ class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView):
'setpos': self.setpos}
)])
+ @fields.depends('cashbook', '_parent_cashbook.owner')
+ def on_change_with_owner_cashbook(self, name=None):
+ """ get current owner
+ """
+ if self.cashbook:
+ return self.cashbook.owner.id
+
+ @fields.depends('cashbook', '_parent_cashbook.state')
+ def on_change_with_state_cashbook(self, name=None):
+ """ get state of cashbook
+ """
+ if self.cashbook:
+ return self.cashbook.state
+
+ @fields.depends('bookingtype', 'category', 'booktransf')
+ def on_change_bookingtype(self):
+ """ reset category/booktransf on change of bookingtype
+ """
+ if self.bookingtype:
+ if self.bookingtype in ['in', 'out']:
+ self.booktransf = None
+ elif self.bookingtype in ['mvin', 'mvout']:
+ self.category = None
+
@fields.depends('frequ', 'setpos', 'weekday', 'monthday')
def on_change_frequ(self):
""" update fields
@@ -254,6 +340,24 @@ class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView):
"""
self.on_change_frequ()
+ @classmethod
+ def default_wfcheck(cls):
+ """ False as default for wf-state 'checked'
+
+ Returns:
+ bool: False
+ """
+ return False
+
+ @classmethod
+ def default_amount(cls):
+ """ default for amount
+
+ Returns:
+ Decimal: 0.00
+ """
+ return Decimal('0.0')
+
@classmethod
def default_interval(cls):
""" get default for interval
@@ -387,8 +491,40 @@ class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView):
def run_booking(cls, records):
""" do prepared booking
"""
+ pool = Pool()
+ IrDate = pool.get('ir.date')
+ Line = pool.get('cashbook.line')
+
+ to_create = []
+ to_create_check = []
for record in records:
- print('-- booking:', record.rec_name)
+ line = {
+ 'cashbook': record.cashbook.id,
+ 'bookingtype': record.bookingtype,
+ 'date': IrDate.today(),
+ 'amount': record.amount,
+ 'description': record.subject}
+
+ if record.bookingtype in ['in', 'out']:
+ if record.category:
+ line['category'] = record.category.id
+ if record.party:
+ line['party'] = record.party.id
+ elif record.bookingtype in ['mvin', 'mvout']:
+ if record.booktransf:
+ line['booktransf'] = record.booktransf.id
+
+ if record.wfcheck:
+ to_create_check.append(line)
+ else:
+ to_create.append(line)
+
+ if to_create_check:
+ lines = Line.create(to_create_check)
+ Line.wfcheck(lines)
+
+ if to_create:
+ Line.create(to_create)
@classmethod
def cronjob(cls):
diff --git a/tests/planner.py b/tests/planner.py
index 842c398..0d34dc2 100644
--- a/tests/planner.py
+++ b/tests/planner.py
@@ -3,6 +3,7 @@
# The COPYRIGHT file at the top level of this repository contains the
# full copyright notices and license terms.
+from decimal import Decimal
from unittest.mock import MagicMock
from trytond.tests.test_tryton import with_transaction
from trytond.pool import Pool
@@ -19,6 +20,7 @@ class PlannerTestCase(object):
pool = Pool()
Book = pool.get('cashbook.book')
Planner = pool.get('cashbook.planner')
+ Party = pool.get('party.party')
types = self.prep_type()
company = self.prep_company()
@@ -36,11 +38,18 @@ class PlannerTestCase(object):
}])
self.assertEqual(book.rec_name, 'Book 1 | 0.00 usd | Open')
+ category = self.prep_category()
+ party, = Party.create([{
+ 'name': 'Party',
+ 'addresses': [('create', [{}])]}])
job, = Planner.create([{
'cashbook': book.id,
'name': name,
'start_date': date(2022, 5, 1),
- 'bookingtype': 'out'}])
+ 'bookingtype': 'out',
+ 'category': category.id,
+ 'party': party.id,
+ 'subject': 'Booking text'}])
# check applied defaults
self.assertEqual(job.rec_name, 'Job 1')
self.assertEqual(job.start_date, date(2022, 5, 1))
@@ -286,4 +295,111 @@ class PlannerTestCase(object):
IrDate.today = MagicMock(return_value=date.today())
+ @with_transaction()
+ def test_planner_cronjobs_booking_with_category(self):
+ """ create job, configure booking with category, run job
+ """
+ pool = Pool()
+ Planner = pool.get('cashbook.planner')
+ IrDate = pool.get('ir.date')
+ Category = pool.get('cashbook.category')
+ Cashbook = pool.get('cashbook.book')
+
+ job = self.prep_create_job()
+ self.assertEqual(
+ job._compute_dates_by_rrule(
+ count=1, query_date=date(2022, 5, 1)), [
+ date(2022, 5, 1)])
+
+ IrDate.today = MagicMock(return_value=date(2022, 5, 24))
+
+ category, = Category.search([('name', '=', 'Cat1')])
+ Planner.write(*[
+ [job],
+ {
+ 'name': 'Booking to category',
+ 'amount': Decimal('10.0'),
+ 'bookingtype': 'out',
+ 'category': category.id,
+ 'subject': 'booking ${month}/${year}, ${date}',
+ 'wfcheck': True,
+ }])
+ self.assertEqual(job.rec_name, 'Booking to category')
+ self.assertEqual(job.cashbook.rec_name, 'Book 1 | 0.00 usd | Open')
+ self.assertEqual(len(job.cashbook.lines), 0)
+
+ job, = Planner.search([])
+ self.assertEqual(job.nextrun[0].date, date(2022, 6, 1))
+ IrDate.today = MagicMock(return_value=date(2022, 6, 1))
+ Planner.cronjob()
+ self.assertEqual(job.nextrun[0].date, date(2022, 7, 1))
+
+ # check cashbook
+ self.assertEqual(len(job.cashbook.lines), 1)
+ self.assertEqual(
+ job.cashbook.lines[0].rec_name,
+ "06/01/2022|Exp|-10.00 usd|booking " +
+ "${month}/${year}, ${date} [Cat1]")
+ self.assertEqual(job.cashbook.lines[0].state, 'check')
+
+ with Transaction().set_context({'date': date(2022, 6, 1)}):
+ cashbook, = Cashbook.browse([job.cashbook])
+ self.assertEqual(cashbook.rec_name, 'Book 1 | -10.00 usd | Open')
+
+ IrDate.today = MagicMock(return_value=date.today())
+
+ @with_transaction()
+ def test_planner_cronjobs_booking_transfer_nonasset_eur(self):
+ """ create job, configure transfer-booking to non-asset-cashbook,
+ run job
+ """
+ pool = Pool()
+ Planner = pool.get('cashbook.planner')
+ IrDate = pool.get('ir.date')
+ Category = pool.get('cashbook.category')
+ Cashbook = pool.get('cashbook.book')
+
+ job = self.prep_create_job()
+ self.assertEqual(
+ job._compute_dates_by_rrule(
+ count=1, query_date=date(2022, 5, 1)), [
+ date(2022, 5, 1)])
+
+ IrDate.today = MagicMock(return_value=date(2022, 5, 24))
+
+ category, = Category.search([('name', '=', 'Cat1')])
+ Planner.write(*[
+ [job],
+ {
+ 'name': 'Booking to category',
+ 'amount': Decimal('10.0'),
+ 'bookingtype': 'out',
+ 'category': category.id,
+ 'subject': 'booking ${month}/${year}, ${date}',
+ 'wfcheck': True,
+ }])
+ self.assertEqual(job.rec_name, 'Booking to category')
+ self.assertEqual(job.cashbook.rec_name, 'Book 1 | 0.00 usd | Open')
+ self.assertEqual(len(job.cashbook.lines), 0)
+
+ job, = Planner.search([])
+ self.assertEqual(job.nextrun[0].date, date(2022, 6, 1))
+ IrDate.today = MagicMock(return_value=date(2022, 6, 1))
+ Planner.cronjob()
+ self.assertEqual(job.nextrun[0].date, date(2022, 7, 1))
+
+ # check cashbook
+ self.assertEqual(len(job.cashbook.lines), 1)
+ self.assertEqual(
+ job.cashbook.lines[0].rec_name,
+ "06/01/2022|Exp|-10.00 usd|booking " +
+ "${month}/${year}, ${date} [Cat1]")
+ self.assertEqual(job.cashbook.lines[0].state, 'check')
+
+ with Transaction().set_context({'date': date(2022, 6, 1)}):
+ cashbook, = Cashbook.browse([job.cashbook])
+ self.assertEqual(cashbook.rec_name, 'Book 1 | -10.00 usd | Open')
+
+ IrDate.today = MagicMock(return_value=date.today())
+
# end PlannerTestCase
diff --git a/view/planner_form.xml b/view/planner_form.xml
index ca4f209..cc1ebbc 100644
--- a/view/planner_form.xml
+++ b/view/planner_form.xml
@@ -41,6 +41,23 @@ full copyright notices and license terms. -->
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/view/planner_list.xml b/view/planner_list.xml
index 3da7230..e351a37 100644
--- a/view/planner_list.xml
+++ b/view/planner_list.xml
@@ -8,4 +8,8 @@ full copyright notices and license terms. -->
+
+
+
+