diff --git a/configuration.py b/configuration.py
index e0e460f..e8de3b6 100644
--- a/configuration.py
+++ b/configuration.py
@@ -15,6 +15,9 @@ field_done = fields.Boolean(string='Done',
help='Show cashbook lines in Done-state.')
field_catnamelong = fields.Boolean(string='Category: Show long name',
help='Shows the long name of the category in the Category field of a cash book line.')
+field_defbook = fields.Many2One(string='Default Cashbook',
+ help='The default cashbook is selected when you open the booking wizard.',
+ model_name='cashbook.book', ondelete='SET NULL')
class Configuration(ModelSingleton, ModelSQL, ModelView, UserMultiValueMixin):
@@ -36,6 +39,7 @@ class Configuration(ModelSingleton, ModelSQL, ModelView, UserMultiValueMixin):
checked = fields.MultiValue(field_checked)
done = fields.MultiValue(field_done)
catnamelong = fields.MultiValue(field_catnamelong)
+ defbook = fields.MultiValue(field_defbook)
@classmethod
def multivalue_model(cls, field):
@@ -44,7 +48,7 @@ class Configuration(ModelSingleton, ModelSQL, ModelView, UserMultiValueMixin):
pool = Pool()
if field in ['date_from', 'date_to', 'checked', 'done',
- 'catnamelong']:
+ 'catnamelong', 'defbook']:
return pool.get('cashbook.configuration_user')
return super(Configuration, cls).multivalue_model(field)
@@ -82,6 +86,7 @@ class UserConfiguration(ModelSQL, UserValueMixin):
checked = field_checked
done = field_done
catnamelong = field_catnamelong
+ defbook = field_defbook
@classmethod
def default_checked(cls):
diff --git a/locale/de.po b/locale/de.po
index 069bc80..71403b9 100644
--- a/locale/de.po
+++ b/locale/de.po
@@ -998,6 +998,10 @@ msgctxt "model:cashbook.configuration,name:"
msgid "Configuration"
msgstr "Konfiguration"
+msgctxt "view:cashbook.configuration:"
+msgid "Enter Booking Wizard"
+msgstr "Dialog: Buchung eingeben"
+
msgctxt "view:cashbook.configuration:"
msgid "Open Cashbook Wizard"
msgstr "Dialog: Kassenbuch öffnen"
@@ -1006,6 +1010,14 @@ msgctxt "view:cashbook.configuration:"
msgid "Cashbook"
msgstr "Kassenbuch"
+msgctxt "field:cashbook.configuration,defbook:"
+msgid "Default Cashbook"
+msgstr "Standardkassenbuch"
+
+msgctxt "help:cashbook.configuration,defbook:"
+msgid "The default cashbook is selected when you open the booking wizard."
+msgstr "Das Standardkassenbuch wird beim Öffnen des Buchungswizards ausgewählt."
+
msgctxt "field:cashbook.configuration,date_from:"
msgid "Start Date"
msgstr "Beginndatum"
@@ -1078,6 +1090,14 @@ msgctxt "help:cashbook.configuration_user,catnamelong:"
msgid "Shows the long name of the category in the Category field of a cash book line."
msgstr "Zeigt im Feld 'Kategorie' einer Kassenbuchzeile den langen Namen der Kategorie."
+msgctxt "field:cashbook.configuration_user,defbook:"
+msgid "Default Cashbook"
+msgstr "Standardkassenbuch"
+
+msgctxt "help:cashbook.configuration_user,defbook:"
+msgid "The default cashbook is selected when you open the booking wizard."
+msgstr "Das Standardkassenbuch wird beim Öffnen des Buchungswizards ausgewählt."
+
##################
# cashbook.recon #
@@ -1270,6 +1290,10 @@ msgctxt "model:cashbook.enterbooking.start,name:"
msgid "Enter Booking"
msgstr "Buchung eingeben"
+msgctxt "view:cashbook.enterbooking.start:"
+msgid "Description"
+msgstr "Beschreibung"
+
msgctxt "field:cashbook.enterbooking.start,cashbook:"
msgid "Cashbook"
msgstr "Kassenbuch"
@@ -1322,6 +1346,21 @@ msgctxt "field:cashbook.enterbooking.start,amount:"
msgid "Amount"
msgstr "Betrag"
+msgctxt "field:cashbook.enterbooking.start,owner_cashbook:"
+msgid "Owner"
+msgstr "Eigentümer"
+
+msgctxt "field:cashbook.enterbooking.start,category:"
+msgid "Category"
+msgstr "Kategorie"
+
+msgctxt "field:cashbook.enterbooking.start,booktransf:"
+msgid "Source/Dest"
+msgstr "Quelle/Ziel"
+
+msgctxt "field:cashbook.enterbooking.start,party:"
+msgid "Party"
+msgstr "Partei"
#########################
@@ -1338,3 +1377,7 @@ msgstr "Abbruch"
msgctxt "wizard_button:cashbook.enterbooking,start,save_:"
msgid "Save"
msgstr "Speichern"
+
+msgctxt "wizard_button:cashbook.enterbooking,start,savenext_:"
+msgid "Save & Next"
+msgstr "Speichern & Weiter"
diff --git a/tests/__init__.py b/tests/__init__.py
index f557851..dea659d 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -11,11 +11,14 @@ from trytond.modules.cashbook.tests.test_splitline import SplitLineTestCase
from trytond.modules.cashbook.tests.test_config import ConfigTestCase
from trytond.modules.cashbook.tests.test_category import CategoryTestCase
from trytond.modules.cashbook.tests.test_reconciliation import ReconTestCase
+from trytond.modules.cashbook.tests.test_bookingwiz import BookingWizardTestCase
+
__all__ = ['suite']
class CashbookTestCase(\
+ BookingWizardTestCase,\
ReconTestCase,\
CategoryTestCase,\
ConfigTestCase,\
diff --git a/tests/test_bookingwiz.py b/tests/test_bookingwiz.py
new file mode 100644
index 0000000..29f2665
--- /dev/null
+++ b/tests/test_bookingwiz.py
@@ -0,0 +1,179 @@
+# -*- coding: utf-8 -*-
+# This file is part of the cashbook-module from m-ds for Tryton.
+# The COPYRIGHT file at the top level of this repository contains the
+# full copyright notices and license terms.
+
+from trytond.tests.test_tryton import ModuleTestCase, with_transaction
+from trytond.pool import Pool
+from trytond.transaction import Transaction
+from trytond.exceptions import UserError
+from datetime import date
+from decimal import Decimal
+from unittest.mock import MagicMock
+
+
+class BookingWizardTestCase(ModuleTestCase):
+ 'Test cashbook booking wizard module'
+ module = 'cashbook'
+
+ @with_transaction()
+ def test_bookwiz_expense(self):
+ """ run booking-wizard to store expense
+ """
+ pool = Pool()
+ BookingWiz = pool.get('cashbook.enterbooking', type='wizard')
+ Book = pool.get('cashbook.book')
+ Category = pool.get('cashbook.category')
+ Party = pool.get('party.party')
+ IrDate = pool.get('ir.date')
+
+ company = self.prep_company()
+ with Transaction().set_context({
+ 'company': company.id,
+ }):
+ types = self.prep_type()
+ book, = Book.create([{
+ 'name': 'Cash Book',
+ 'btype': types.id,
+ 'company': company.id,
+ 'currency': company.currency.id,
+ 'number_sequ': self.prep_sequence().id,
+ 'start_date': date(2022, 1, 1),
+ 'start_balance': Decimal('0.0'),
+ }])
+
+ party, = Party.create([{
+ 'name': 'Foodshop Zehlendorf',
+ 'addresses':[('create', [{}])],
+ }])
+
+ categories = Category.create([{
+ 'name':'Income',
+ 'cattype': 'in',
+ }, {
+ 'name': 'Food',
+ 'cattype': 'out',
+ }])
+
+ (sess_id, start_state, end_state) = BookingWiz.create()
+ w_obj = BookingWiz(sess_id)
+ self.assertEqual(start_state, 'start')
+ self.assertEqual(end_state, 'end')
+
+ result = BookingWiz.execute(sess_id, {}, start_state)
+ self.assertEqual(list(result.keys()), ['view'])
+ self.assertEqual(result['view']['defaults']['bookingtype'], 'out')
+ self.assertEqual(result['view']['defaults']['cashbook'], None)
+ self.assertEqual(result['view']['defaults']['amount'], None)
+ self.assertEqual(result['view']['defaults']['party'], None)
+ self.assertEqual(result['view']['defaults']['booktransf'], None)
+ self.assertEqual(result['view']['defaults']['description'], None)
+ self.assertEqual(result['view']['defaults']['category'], None)
+
+ self.assertEqual(len(book.lines), 0)
+
+ r1 = {
+ 'amount': Decimal('10.0'),
+ 'cashbook': book.id,
+ 'party': party.id,
+ 'description': 'Test 1',
+ 'category': categories[1].id,
+ 'bookingtype': 'out',
+ }
+ for x in r1.keys():
+ setattr(w_obj.start, x, r1[x])
+
+ IrDate.today = MagicMock(return_value=date(2022, 5, 1))
+ result = BookingWiz.execute(sess_id, {'start': r1}, 'save_')
+ BookingWiz.delete(sess_id)
+ IrDate.today = MagicMock(return_value=date.today())
+
+ self.assertEqual(len(book.lines), 1)
+ self.assertEqual(book.lines[0].rec_name, '05/01/2022|Exp|-10.00 usd|Test 1 [Food]')
+
+ @with_transaction()
+ def test_bookwiz_transfer(self):
+ """ run booking-wizard to store expense
+ """
+ pool = Pool()
+ BookingWiz = pool.get('cashbook.enterbooking', type='wizard')
+ Book = pool.get('cashbook.book')
+ Category = pool.get('cashbook.category')
+ Party = pool.get('party.party')
+ IrDate = pool.get('ir.date')
+
+ company = self.prep_company()
+ with Transaction().set_context({
+ 'company': company.id,
+ }):
+ types = self.prep_type()
+ books = Book.create([{
+ 'name': 'Cash Book',
+ 'btype': types.id,
+ 'company': company.id,
+ 'currency': company.currency.id,
+ 'number_sequ': self.prep_sequence().id,
+ 'start_date': date(2022, 1, 1),
+ 'start_balance': Decimal('0.0'),
+ }, {
+ 'name': 'Bank',
+ 'btype': types.id,
+ 'company': company.id,
+ 'currency': company.currency.id,
+ 'number_sequ': self.prep_sequence().id,
+ 'start_date': date(2022, 1, 1),
+ 'start_balance': Decimal('0.0'),
+ }])
+
+ party, = Party.create([{
+ 'name': 'Foodshop Zehlendorf',
+ 'addresses':[('create', [{}])],
+ }])
+
+ categories = Category.create([{
+ 'name':'Income',
+ 'cattype': 'in',
+ }, {
+ 'name': 'Food',
+ 'cattype': 'out',
+ }])
+
+ (sess_id, start_state, end_state) = BookingWiz.create()
+ w_obj = BookingWiz(sess_id)
+ self.assertEqual(start_state, 'start')
+ self.assertEqual(end_state, 'end')
+
+ result = BookingWiz.execute(sess_id, {}, start_state)
+ self.assertEqual(list(result.keys()), ['view'])
+ self.assertEqual(result['view']['defaults']['bookingtype'], 'out')
+ self.assertEqual(result['view']['defaults']['cashbook'], None)
+ self.assertEqual(result['view']['defaults']['amount'], None)
+ self.assertEqual(result['view']['defaults']['party'], None)
+ self.assertEqual(result['view']['defaults']['booktransf'], None)
+ self.assertEqual(result['view']['defaults']['description'], None)
+ self.assertEqual(result['view']['defaults']['category'], None)
+
+ self.assertEqual(len(books[0].lines), 0)
+ self.assertEqual(len(books[1].lines), 0)
+
+ r1 = {
+ 'amount': Decimal('10.0'),
+ 'cashbook': books[0].id,
+ 'description': 'Test 1',
+ 'booktransf': books[1].id,
+ 'bookingtype': 'mvout',
+ }
+ for x in r1.keys():
+ setattr(w_obj.start, x, r1[x])
+
+ IrDate.today = MagicMock(return_value=date(2022, 5, 1))
+ result = BookingWiz.execute(sess_id, {'start': r1}, 'save_')
+ BookingWiz.delete(sess_id)
+ IrDate.today = MagicMock(return_value=date.today())
+
+ self.assertEqual(len(books[0].lines), 1)
+ self.assertEqual(len(books[1].lines), 0)
+ self.assertEqual(books[0].lines[0].rec_name,
+ '05/01/2022|to|-10.00 usd|Test 1 [Bank | 0.00 usd | Open]')
+
+# end BookingWizardTestCase
diff --git a/tests/test_config.py b/tests/test_config.py
index 7932229..368ad10 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -42,6 +42,7 @@ class ConfigTestCase(ModuleTestCase):
self.assertEqual(cfg2.checked, True)
self.assertEqual(cfg2.done, False)
self.assertEqual(cfg2.catnamelong, True)
+ self.assertEqual(cfg2.defbook, None)
return cfg2
def prep_party(self, name='Party'):
@@ -105,6 +106,37 @@ class ConfigTestCase(ModuleTestCase):
"""
self.prep_config()
+ @with_transaction()
+ def test_config_defbook(self):
+ """ create config, add default-cashbook
+ """
+ pool = Pool()
+ Configuration = pool.get('cashbook.configuration')
+ Book = pool.get('cashbook.book')
+
+ self.prep_config()
+ types = self.prep_type()
+ company = self.prep_company()
+ book, = Book.create([{
+ 'name': 'Book 1',
+ 'btype': types.id,
+ 'company': company.id,
+ 'currency': company.currency.id,
+ 'number_sequ': self.prep_sequence().id,
+ }])
+ self.assertEqual(book.name, 'Book 1')
+
+ cfg1 = Configuration.get_singleton()
+
+ Configuration.write(*[
+ [cfg1],
+ {
+ 'defbook': book.id,
+ }])
+
+ cfg2 = Configuration.get_singleton()
+ self.assertEqual(cfg2.defbook.rec_name, 'Book 1 | 0.00 usd | Open')
+
@with_transaction()
def test_config_create_multi_user(self):
""" create config, multi-user
diff --git a/view/configuration_form.xml b/view/configuration_form.xml
index e3a0ee0..36c5e77 100644
--- a/view/configuration_form.xml
+++ b/view/configuration_form.xml
@@ -12,6 +12,10 @@ this repository contains the full copyright notices and license terms. -->
+
+
+
+
diff --git a/view/enterbooking_start_form.xml b/view/enterbooking_start_form.xml
index 8d9337c..3ab0a1b 100644
--- a/view/enterbooking_start_form.xml
+++ b/view/enterbooking_start_form.xml
@@ -11,10 +11,21 @@ full copyright notices and license terms. -->
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/wizard_booking.py b/wizard_booking.py
index a6ae457..fc85987 100644
--- a/wizard_booking.py
+++ b/wizard_booking.py
@@ -8,10 +8,12 @@ from trytond.wizard import Wizard, StateView, StateTransition, Button
from trytond.i18n import gettext
from trytond.pool import Pool
from trytond.transaction import Transaction
-from trytond.pyson import Eval
+from trytond.pyson import Eval, Bool, If, And
from decimal import Decimal
from .line import sel_bookingtype
+sel_booktypewiz = [x for x in sel_bookingtype if not x[0] in ['spin', 'spout']]
+
class EnterBookingStart(ModelView):
'Enter Booking'
@@ -23,6 +25,9 @@ class EnterBookingStart(ModelView):
cashbooks = fields.One2Many(string='Cashbooks', field=None,
model_name='cashbook.book', readonly=True,
states={'invisible': True})
+ owner_cashbook = fields.Function(fields.Many2One(string='Owner', readonly=True,
+ states={'invisible': True}, model_name='res.user'),
+ 'on_change_with_owner_cashbook')
balance = fields.Numeric(string='Balance', readonly=True,
digits=(16, Eval('currency_digits', 2)),
depends=['currency_digits'])
@@ -33,15 +38,55 @@ class EnterBookingStart(ModelView):
readonly=True, states={'invisible': True}),
'on_change_with_currency_digits')
bookingtype = fields.Selection(string='Type', required=True,
- selection=sel_bookingtype)
+ selection=sel_booktypewiz)
amount = fields.Numeric(string='Amount',
depends=['currency_digits', 'bookingtype'],
- digits=(16, Eval('currency_digits', 2)),
- domain=[('amount', '>=', Decimal('0.0'))],
+ digits=(16, Eval('currency_digits', 2)), required=True,
+ domain=[('amount', '>=', Decimal('0.0'))])
+ description = fields.Text(string='Description')
+ category = fields.Many2One(string='Category',
+ model_name='cashbook.category', depends=['bookingtype'],
states={
- 'readonly': Eval('bookingtype', '').in_(['spin', 'spout']),
- 'required': Eval('bookingtype', '').in_(['in', 'out', 'mvin', 'mvout']),
- })
+ 'readonly': Bool(Eval('bookingtype')) == False,
+ 'required': Eval('bookingtype', '').in_(['in', 'out']),
+ 'invisible': ~Eval('bookingtype', '').in_(['in', 'out']),
+ },
+ domain=[
+ If(
+ Eval('bookingtype', '').in_(['in', 'mvin']),
+ ('cattype', '=', 'in'),
+ ('cattype', '=', 'out'),
+ )])
+
+ # party or cashbook as counterpart
+ booktransf = fields.Many2One(string='Source/Dest',
+ model_name='cashbook.book',
+ domain=[
+ ('owner.id', '=', Eval('owner_cashbook', -1)),
+ ('id', '!=', Eval('cashbook', -1)),
+ ],
+ states={
+ 'invisible': ~Eval('bookingtype', '').in_(['mvin', 'mvout']),
+ 'required': Eval('bookingtype', '').in_(['mvin', 'mvout']),
+ }, depends=['bookingtype', 'owner_cashbook', 'cashbook'])
+ party = fields.Many2One(string='Party', model_name='party.party',
+ states={
+ 'invisible': ~Eval('bookingtype', '').in_(['in', 'out']),
+ }, depends=['bookingtype'])
+
+ @fields.depends('bookingtype', 'category')
+ def on_change_bookingtype(self):
+ """ clear category if not valid type
+ """
+ types = {
+ 'in': ['in', 'mvin'],
+ 'out': ['out', 'mvout'],
+ }
+
+ if self.bookingtype:
+ if self.category:
+ if not self.bookingtype in types.get(self.category.cattype, ''):
+ self.category = None
@fields.depends('cashbook', 'balance')
def on_change_cashbook(self):
@@ -52,6 +97,13 @@ class EnterBookingStart(ModelView):
else :
self.balance = None
+ @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.currency')
def on_change_with_currency(self, name=None):
""" digits
@@ -79,28 +131,64 @@ class EnterBookingWizard(Wizard):
start = StateView('cashbook.enterbooking.start',
'cashbook.enterbooking_start_form', [
Button('Cancel', 'end', 'tryton-cancel'),
- Button('Save', 'save_', 'tryton-add', default=True),
+ Button('Save', 'save_', 'tryton-save', default=True),
+ Button('Save & Next', 'savenext_', 'tryton-forward'),
])
save_ = StateTransition()
+ savenext_ = StateTransition()
def default_start(self, fields):
""" setup form
"""
pool = Pool()
Cashbook = pool.get('cashbook.book')
+ Configuration = pool.get('cashbook.configuration')
+
+ cfg1 = Configuration.get_singleton()
result = {
'cashbooks': [x.id for x in Cashbook.search([
('state', '=', 'open'),
('owner.id', '=', Transaction().user),
])],
- 'bookingtype': 'out',
+ 'bookingtype': getattr(self.start, 'bookingtype', 'out'),
+ 'cashbook': getattr(getattr(cfg1, 'defbook', None), 'id', None),
+ 'amount': None,
+ 'party': None,
+ 'booktransf': None,
+ 'description': None,
+ 'category': None,
}
return result
- def transition_save(self):
+ def transition_save_(self):
""" store booking
"""
+ pool = Pool()
+ Line = pool.get('cashbook.line')
+ IrDate = pool.get('ir.date')
+
+ query = {
+ 'cashbook': self.start.cashbook.id,
+ 'description': self.start.description,
+ 'date': IrDate.today(),
+ 'bookingtype': self.start.bookingtype,
+ 'amount': self.start.amount,
+ }
+
+ if self.start.bookingtype in ['in', 'out']:
+ query['category'] = self.start.category.id
+ query['party'] = getattr(self.start.party, 'id', None)
+ elif self.start.bookingtype in ['mvin', 'mvout']:
+ query['booktransf'] = self.start.booktransf.id
+
+ Line.create([query])
return 'end'
+ def transition_savenext_(self):
+ """ store booking & restart
+ """
+ self.transition_save_()
+ return 'start'
+
# end EnterBookingWizard