Compare commits

...

50 commits

Author SHA1 Message Date
Frederik Jaeckel
aab73ff871 Version 6.0.12 2022-09-18 12:56:55 +02:00
Frederik Jaeckel
3f317d80b2 konfig/buchung eingeben: ausgewählte kassenbücher 2022-09-18 12:52:31 +02:00
Frederik Jaeckel
fdd7290cfc Etikett ver 6.0.11 zum Änderungssatz 6a103b4be54a hinzugefügt 2022-09-16 10:19:22 +02:00
Frederik Jaeckel
785ec8f9a9 Version 6.0.11 2022-09-16 10:19:11 +02:00
Frederik Jaeckel
e024044ccc book: neues Feld 'description', sperrt btype-->None mit Zeilen + Test
line: verwendet nur kassenbücher mit typ
2022-09-16 10:15:51 +02:00
Frederik Jaeckel
8421db2221 book: hierarchie + test
book: Feld 'start_balance' entfernt
2022-09-15 23:49:54 +02:00
Frederik Jaeckel
57ade40eb8 Etikett ver 6.0.10 zum Änderungssatz 67ae1a871e54 hinzugefügt 2022-09-13 22:55:01 +02:00
Frederik Jaeckel
96cfe3a32c Version 6.0.10 2022-09-13 22:54:52 +02:00
Frederik Jaeckel
ec4466176e line: buchungstyp für gegenbuchung bei splitbuchung korrigiert 2022-09-13 22:18:31 +02:00
Frederik Jaeckel
843ade71e9 line: test korrigiert 2022-09-12 00:16:06 +02:00
Frederik Jaeckel
b2147f56b3 line: splitbuchung mit transfer 2022-09-10 21:21:17 +02:00
Frederik Jaeckel
1fee218ee6 line: suche in splitline (kategorie+beschreibung) 2022-09-09 22:50:12 +02:00
Frederik Jaeckel
fe8a6bbed6 Etikett ver 6.0.9 zum Änderungssatz cc8e50efd3cb hinzugefügt 2022-09-08 17:27:44 +02:00
Frederik Jaeckel
5fd4bb9093 Etikett ver 6.0.9 gelöscht 2022-09-08 17:27:34 +02:00
Frederik Jaeckel
dca9732fce lne: zeilenumbruch in beschreibung korrigiert 2022-09-08 17:27:14 +02:00
Frederik Jaeckel
7d97128f24 Etikett ver 6.0.9 zum Änderungssatz 84defce3fc9c hinzugefügt 2022-09-08 12:55:02 +02:00
Frederik Jaeckel
48d6246611 Version 6.0.9 2022-09-08 12:54:52 +02:00
Frederik Jaeckel
638524a2c3 line: amount darf negativ sein + test, constrain gelöscht 2022-09-08 12:13:30 +02:00
Frederik Jaeckel
9103f828dc Etikett ver 6.0.8 zum Änderungssatz 9c12aaba4577 hinzugefügt 2022-09-07 16:35:20 +02:00
Frederik Jaeckel
e14dd92c36 Version 6.0.8 2022-09-07 16:35:08 +02:00
Frederik Jaeckel
c24fdef4cf Wizardform optimiert 2022-09-07 16:33:58 +02:00
Frederik Jaeckel
f2b228aba6 Etikett ver 6.0.7 zum Änderungssatz 3f8cf055086e hinzugefügt 2022-09-07 11:38:18 +02:00
Frederik Jaeckel
46515aaf26 Version 6.0.7 2022-09-07 11:38:09 +02:00
Frederik Jaeckel
a2cb2b308b konfig: kassenbuch-default für buchungswizard
buchungswizard: ok + test
2022-09-07 11:36:37 +02:00
Frederik Jaeckel
25db274d2d wizard: buchungseingabe begonnen 2022-09-06 17:26:53 +02:00
Frederik Jaeckel
11b3f7d004 Etikett ver 6.0.6 zum Änderungssatz 21ba264f9b2f hinzugefügt 2022-09-06 16:15:28 +02:00
Frederik Jaeckel
75a873e25d Version 6.0.6 2022-09-06 16:15:21 +02:00
Frederik Jaeckel
de0de8a85f line: suche optimiert (jetzt: kategorie, payee, beschreibung)
line: kontext-form optimiert,
abstimmung: anzeige-sortierung umgedreht,
kassenbuch: weniger spalten
2022-09-06 16:10:25 +02:00
Frederik Jaeckel
0fcc997f28 Etikett ver 6.0.5 zum Änderungssatz 94b913c2892e hinzugefügt 2022-09-05 17:21:09 +02:00
Frederik Jaeckel
5797f42489 Version 6.0.5 2022-09-05 17:21:00 +02:00
Frederik Jaeckel
4afd9f835d line: spalte 'kategorie' ergänzt 2022-09-05 13:12:08 +02:00
Frederik Jaeckel
70ef448beb line: contraint für line>=0 2022-09-05 12:39:09 +02:00
Frederik Jaeckel
34a4958936 book: felder in tab verlegt 2022-09-05 12:05:26 +02:00
Frederik Jaeckel
f661f5ed38 Etikett ver 6.0.4 zum Änderungssatz cc7007ee2a1d hinzugefügt 2022-09-05 10:17:49 +02:00
Frederik Jaeckel
19389b3865 Version 6.0.4 2022-09-05 10:17:29 +02:00
Frederik Jaeckel
b4e7cface3 line: darstellung beschleunigt 2022-09-03 20:39:20 +02:00
Frederik Jaeckel
bb24a94cd1 line: zeilen-saldo optimiert 2022-09-02 16:04:31 +02:00
Frederik Jaeckel
f76f91b35a line: 'number' bei check->done schreiben korrigert + test 2022-09-02 15:18:28 +02:00
Frederik Jaeckel
8abeb63441 übersetzung 2022-09-01 13:25:09 +02:00
Frederik Jaeckel
8b0a2a6ccd book: summieren des saldo in listenansicht 2022-09-01 09:38:23 +02:00
Frederik Jaeckel
613f4e9767 Etikett ver 6.0.3 zum Änderungssatz 8c2841adfbf9 hinzugefügt 2022-08-31 10:19:27 +02:00
Frederik Jaeckel
a84e3ed8ba Version 6.0.3 2022-08-31 10:19:19 +02:00
Frederik Jaeckel
4876b06421 book: digits für startbalance/balance ergänzt 2022-08-31 10:17:29 +02:00
Frederik Jaeckel
619a4e9ed6 kategorie: hierarchische sortierung, sequence-spalte entfernt 2022-08-30 11:56:27 +02:00
Frederik Jaeckel
559a5d0656 kategory: sortierung begnnen 2022-08-29 23:34:36 +02:00
Frederik Jaeckel
2fdee39611 kategorie: constraint gegen gleiche Namen auf toplevel,
importer: list/erstellt kategorie, list transaktionen
2022-08-28 12:24:25 +02:00
Frederik Jaeckel
4df6284257 kategorie: domain-views, importer ergänzt 2022-08-27 09:32:17 +02:00
Frederik Jaeckel
0aa9df2f1d importer begonnen 2022-08-26 23:47:51 +02:00
Frederik Jaeckel
dadcfb618f Etikett ver 6.0.2 zum Änderungssatz 1e230c14c823 hinzugefügt 2022-08-25 15:57:30 +02:00
Frederik Jaeckel
603a9d7477 Version 6.0.2 2022-08-25 15:57:19 +02:00
38 changed files with 2335 additions and 271 deletions

View file

@ -14,6 +14,56 @@ Requires
Changes Changes
======= =======
*6.0.12 - 18.09.2022*
- add: selected cashbooks in 'enter-booking-dialog'
*6.0.11 - 16.09.2022*
- add: hierarchy for cashbooks
*6.0.10 - 13.09.2022*
- add: split-booking with transfer
*6.0.9 - 08.09.2022*
- updt: allow negative amounts
*6.0.8 - 07.09.2022*
- updt: enter-booking form optimized
*6.0.7 - 07.09.2022*
- add: enter-booking-wizard
*6.0.6 - 06.09.2022*
- updt: optimized form - line, line-context
- updt: extended search in cashbook-lines
*6.0.5 - 05.09.2022*
- updt: view of book + line optimized
*6.0.4 - 05.09.2022*
- fix: write number at state-change 'check' -> 'done'
- updt: speedup transaction view
*6.0.3 - 31.08.2022*
- updt: checks, sorting
*6.0.2 - 25.08.2022*
- add: split-booking
*6.0.1 - 23.08.2022*
- works
*6.0.0 - 05.08.2022* *6.0.0 - 05.08.2022*
- init - init

View file

@ -10,6 +10,7 @@ from .line import Line, LineContext
from .splitline import SplitLine from .splitline import SplitLine
from .wizard_openline import OpenCashBook, OpenCashBookStart from .wizard_openline import OpenCashBook, OpenCashBookStart
from .wizard_runreport import RunCbReport, RunCbReportStart from .wizard_runreport import RunCbReport, RunCbReportStart
from .wizard_booking import EnterBookingWizard, EnterBookingStart
from .configuration import Configuration, UserConfiguration from .configuration import Configuration, UserConfiguration
from .category import Category from .category import Category
from .reconciliation import Reconciliation from .reconciliation import Reconciliation
@ -28,6 +29,7 @@ def register():
Reconciliation, Reconciliation,
OpenCashBookStart, OpenCashBookStart,
RunCbReportStart, RunCbReportStart,
EnterBookingStart,
module='cashbook', type_='model') module='cashbook', type_='model')
Pool.register( Pool.register(
ReconciliationReport, ReconciliationReport,
@ -35,4 +37,5 @@ def register():
Pool.register( Pool.register(
OpenCashBook, OpenCashBook,
RunCbReport, RunCbReport,
EnterBookingWizard,
module='cashbook', type_='wizard') module='cashbook', type_='wizard')

162
book.py
View file

@ -3,7 +3,7 @@
# The COPYRIGHT file at the top level of this repository contains the # The COPYRIGHT file at the top level of this repository contains the
# full copyright notices and license terms. # full copyright notices and license terms.
from trytond.model import Workflow, ModelView, ModelSQL, fields, Check from trytond.model import Workflow, ModelView, ModelSQL, fields, Check, tree
from trytond.pyson import Eval, Or, Bool, Id from trytond.pyson import Eval, Or, Bool, Id
from trytond.exceptions import UserError from trytond.exceptions import UserError
from trytond.i18n import gettext from trytond.i18n import gettext
@ -20,6 +20,16 @@ STATES = {
} }
DEPENDS=['state'] DEPENDS=['state']
# states in case of 'btype'!=None
STATES2 = {
'readonly': Or(
Eval('state', '') != 'open',
~Bool(Eval('btype')),
),
'invisible': ~Bool(Eval('btype')),
}
DEPENDS2 = ['state', 'btype']
sel_state_book = [ sel_state_book = [
('open', 'Open'), ('open', 'Open'),
('closed', 'Closed'), ('closed', 'Closed'),
@ -27,7 +37,7 @@ sel_state_book = [
] ]
class Book(Workflow, ModelSQL, ModelView): class Book(tree(separator='/'), Workflow, ModelSQL, ModelView):
'Cashbook' 'Cashbook'
__name__ = 'cashbook.book' __name__ = 'cashbook.book'
@ -35,9 +45,17 @@ class Book(Workflow, ModelSQL, ModelView):
required=True, ondelete="RESTRICT") required=True, ondelete="RESTRICT")
name = fields.Char(string='Name', required=True, name = fields.Char(string='Name', required=True,
states=STATES, depends=DEPENDS) states=STATES, depends=DEPENDS)
btype = fields.Many2One(string='Type', required=True, description = fields.Text(string='Description',
model_name='cashbook.type', ondelete='RESTRICT',
states=STATES, depends=DEPENDS) states=STATES, depends=DEPENDS)
btype = fields.Many2One(string='Type',
help='A cash book with type can contain postings. Without type is a view.',
model_name='cashbook.type', ondelete='RESTRICT',
states={
'readonly': Or(
STATES['readonly'],
Bool(Eval('lines')),
),
}, depends=DEPENDS+['lines'])
owner = fields.Many2One(string='Owner', required=True, select=True, owner = fields.Many2One(string='Owner', required=True, select=True,
model_name='res.user', ondelete='SET NULL', model_name='res.user', ondelete='SET NULL',
states=STATES, depends=DEPENDS) states=STATES, depends=DEPENDS)
@ -54,8 +72,8 @@ class Book(Workflow, ModelSQL, ModelView):
states=STATES, depends=DEPENDS) states=STATES, depends=DEPENDS)
reconciliations = fields.One2Many(string='Reconciliations', reconciliations = fields.One2Many(string='Reconciliations',
field='cashbook', model_name='cashbook.recon', field='cashbook', model_name='cashbook.recon',
states=STATES, depends=DEPENDS) states=STATES2, depends=DEPENDS2)
number_sequ = fields.Many2One(string='Line numbering', required=True, number_sequ = fields.Many2One(string='Line numbering',
help='Number sequence for numbering of the cash book lines.', help='Number sequence for numbering of the cash book lines.',
model_name='ir.sequence', model_name='ir.sequence',
domain=[ domain=[
@ -65,37 +83,56 @@ class Book(Workflow, ModelSQL, ModelView):
('company', '=', Eval('company', -1)), ('company', '=', Eval('company', -1)),
], ],
], ],
states=STATES, depends=DEPENDS+['company']) states={
'readonly': STATES2['readonly'],
'required': Bool(Eval('btype')),
}, depends=DEPENDS2+['company'])
number_atcheck = fields.Boolean(string="number when 'Checking'", number_atcheck = fields.Boolean(string="number when 'Checking'",
help="The numbering of the lines is done in the step Check. If the check mark is inactive, this happens with Done.") help="The numbering of the lines is done in the step Check. If the check mark is inactive, this happens with Done.",
start_date = fields.Date(string='Initial Date', required=True, states=STATES2, depends=DEPENDS2)
start_date = fields.Date(string='Initial Date',
states={ states={
'readonly': Or( 'readonly': Or(
STATES['readonly'], STATES2['readonly'],
Bool(Eval('lines')), Bool(Eval('lines')),
), ),
}, depends=DEPENDS+['lines']) 'invisible': STATES2['invisible'],
start_balance = fields.Numeric(string='Initial Amount', required=True, 'required': Bool(Eval('btype')),
states={ }, depends=DEPENDS2+['lines'])
'readonly': Or( balance = fields.Function(fields.Numeric(string='Balance', readonly=True,
STATES['readonly'], digits=(16, Eval('currency_digits', 2)),
Bool(Eval('lines')), depends=['currency_digits']), 'on_change_with_balance')
), currency = fields.Many2One(string='Currency',
}, depends=DEPENDS+['lines'])
balance = fields.Function(fields.Numeric(string='Balance', readonly=True),
'on_change_with_balance')
currency = fields.Many2One(string='Currency', required=True,
model_name='currency.currency', model_name='currency.currency',
states={ states={
'readonly': Or( 'readonly': Or(
STATES['readonly'], STATES2['readonly'],
Bool(Eval('lines', [])), Bool(Eval('lines', [])),
), ),
}, depends=DEPENDS+['lines']) 'invisible': STATES2['invisible'],
'required': Bool(Eval('btype')),
}, depends=DEPENDS2+['lines'])
currency_digits = fields.Function(fields.Integer(string='Currency Digits',
readonly=True), 'on_change_with_currency_digits')
state = fields.Selection(string='State', required=True, state = fields.Selection(string='State', required=True,
readonly=True, selection=sel_state_book) readonly=True, selection=sel_state_book)
state_string = state.translated('state') state_string = state.translated('state')
parent = fields.Many2One(string="Parent",
model_name='cashbook.book', ondelete='RESTRICT',
left='left', right='right')
childs = fields.One2Many(string='Children', field='parent',
model_name='cashbook.book')
left = fields.Integer(string='Left', required=True, select=True)
right = fields.Integer(string='Right', required=True, select=True)
@classmethod
def __register__(cls, module_name):
super(Book, cls).__register__(module_name)
table = cls.__table_handler__(module_name)
table.drop_column('start_balance')
@classmethod @classmethod
def __setup__(cls): def __setup__(cls):
super(Book, cls).__setup__() super(Book, cls).__setup__()
@ -127,16 +164,18 @@ class Book(Workflow, ModelSQL, ModelView):
}, },
}) })
@staticmethod
def default_left():
return 0
@staticmethod
def default_right():
return 0
@classmethod @classmethod
def default_number_atcheck(cls): def default_number_atcheck(cls):
return True return True
@classmethod
def default_start_balance(cls):
""" zero
"""
return Decimal('0.0')
@classmethod @classmethod
def default_currency(cls): def default_currency(cls):
""" currency of company """ currency of company
@ -189,33 +228,53 @@ class Book(Workflow, ModelSQL, ModelView):
def get_rec_name(self, name): def get_rec_name(self, name):
""" name, balance, state """ name, balance, state
""" """
recname = super(Book, self).get_rec_name(name)
if self.btype:
return '%(name)s | %(balance)s %(symbol)s | %(state)s' % { return '%(name)s | %(balance)s %(symbol)s | %(state)s' % {
'name': self.name or '-', 'name': recname or '-',
'balance': Report.format_number(self.balance or 0.0, None), 'balance': Report.format_number(self.balance or 0.0, None),
'symbol': getattr(self.currency, 'symbol', '-'), 'symbol': getattr(self.currency, 'symbol', '-'),
'state': self.state_string, 'state': self.state_string,
} }
return recname
@fields.depends('id', 'start_balance') @fields.depends('currency')
def on_change_with_currency_digits(self, name=None):
""" currency of cashbook
"""
if self.currency:
return self.currency.digits
else:
return 2
@fields.depends('id')
def on_change_with_balance(self, name=None): def on_change_with_balance(self, name=None):
""" compute balance """ compute balance
""" """
Line = Pool().get('cashbook.line') pool = Pool()
Book2 = pool.get('cashbook.book')
Line = pool.get('cashbook.line')
tab_line = Line.__table__() tab_line = Line.__table__()
cursor = Transaction().connection.cursor() cursor = Transaction().connection.cursor()
query = tab_line.select( line_query = Line.search([
('cashbook.id', 'in', Book2.search([
('parent', 'child_of', [self.id]),
], query=True)),
], query=True)
query = line_query.join(tab_line,
condition=tab_line.id==line_query.id,
).select(
Sum(tab_line.credit - tab_line.debit).as_('balance'), Sum(tab_line.credit - tab_line.debit).as_('balance'),
group_by=[tab_line.cashbook],
where=tab_line.cashbook == self.id
) )
if self.id: if self.id:
if self.start_balance is not None: balance = Decimal('0.0')
balance = self.start_balance
cursor.execute(*query) cursor.execute(*query)
result = cursor.fetchone() result = cursor.fetchone()
if result: if result:
if result[0] is not None:
balance += result[0] balance += result[0]
return balance return balance
@ -247,15 +306,21 @@ class Book(Workflow, ModelSQL, ModelView):
def write(cls, *args): def write(cls, *args):
""" deny update if book is not 'open' """ deny update if book is not 'open'
""" """
ConfigUser = Pool().get('cashbook.configuration_user')
actions = iter(args) actions = iter(args)
to_write_config = []
for books, values in zip(actions, actions): for books, values in zip(actions, actions):
for book in books: for book in books:
if 'start_balance' in values.keys(): # deny btype-->None if lines not empty
if len(book.lines) > 0: if 'btype' in values.keys():
if (values['btype'] is None) and (len(book.lines) > 0):
raise UserError(gettext( raise UserError(gettext(
'cashbook.msg_book_err_startamount_with_lines', 'cashbook.msg_book_btype_with_lines',
bookname = book.rec_name, cbname = book.rec_name,
numlines = len(book.lines),
)) ))
if book.state != 'open': if book.state != 'open':
# allow state-update, if its the only action # allow state-update, if its the only action
if not (('state' in values.keys()) and (len(values.keys()) == 1)): if not (('state' in values.keys()) and (len(values.keys()) == 1)):
@ -264,8 +329,23 @@ class Book(Workflow, ModelSQL, ModelView):
bookname = book.rec_name, bookname = book.rec_name,
state_txt = book.state_string, state_txt = book.state_string,
)) ))
# if owner changes, remove book from user-config
if 'owner' in values.keys():
if book.owner.id != values['owner']:
for x in ['defbook', 'book1', 'book2', 'book3',
'book4', 'book5']:
cfg1 = ConfigUser.search([
('iduser.id', '=', book.owner.id),
('%s.id' % x, '=', book.id),
])
if len(cfg1) > 0:
to_write_config.extend([ cfg1, {x: None} ])
super(Book, cls).write(*args) super(Book, cls).write(*args)
if len(to_write_config) > 0:
ConfigUser.write(*to_write_config)
@classmethod @classmethod
def delete(cls, books): def delete(cls, books):
""" deny delete if book has lines """ deny delete if book has lines

View file

@ -12,6 +12,13 @@ full copyright notices and license terms. -->
<field name="priority" eval="10"/> <field name="priority" eval="10"/>
<field name="name">book_list</field> <field name="name">book_list</field>
</record> </record>
<record model="ir.ui.view" id="book_view_tree">
<field name="model">cashbook.book</field>
<field name="type">tree</field>
<field name="priority" eval="10"/>
<field name="field_childs">childs</field>
<field name="name">book_tree</field>
</record>
<record model="ir.ui.view" id="book_view_form"> <record model="ir.ui.view" id="book_view_form">
<field name="model">cashbook.book</field> <field name="model">cashbook.book</field>
<field name="type">form</field> <field name="type">form</field>
@ -19,7 +26,7 @@ full copyright notices and license terms. -->
<field name="name">book_form</field> <field name="name">book_form</field>
</record> </record>
<!-- action view--> <!-- action view - list -->
<record model="ir.action.act_window" id="act_book_view"> <record model="ir.action.act_window" id="act_book_view">
<field name="name">Cashbook</field> <field name="name">Cashbook</field>
<field name="res_model">cashbook.book</field> <field name="res_model">cashbook.book</field>
@ -35,6 +42,23 @@ full copyright notices and license terms. -->
<field name="act_window" ref="act_book_view"/> <field name="act_window" ref="act_book_view"/>
</record> </record>
<!-- action view - tree -->
<record model="ir.action.act_window" id="act_book_tree">
<field name="name">Cashbook</field>
<field name="res_model">cashbook.book</field>
<field name="domain" eval="[('parent', '=', None)]" pyson="1"/>
</record>
<record model="ir.action.act_window.view" id="act_book_tree-1">
<field name="sequence" eval="10"/>
<field name="view" ref="book_view_tree"/>
<field name="act_window" ref="act_book_tree"/>
</record>
<record model="ir.action.act_window.view" id="act_book_tree-2">
<field name="sequence" eval="20"/>
<field name="view" ref="book_view_form"/>
<field name="act_window" ref="act_book_tree"/>
</record>
<!-- permission --> <!-- permission -->
<!-- anon: deny all --> <!-- anon: deny all -->
<record model="ir.model.access" id="access_book-anon"> <record model="ir.model.access" id="access_book-anon">
@ -251,18 +275,24 @@ full copyright notices and license terms. -->
<field name="perm_read" eval="False"/> <field name="perm_read" eval="False"/>
<field name="perm_write" eval="False"/> <field name="perm_write" eval="False"/>
</record> </record>
<record model="ir.model.field.access" id="fa_book-start_balance-anon">
<field name="field"
search="[('model.model', '=', 'cashbook.book'), ('name', '=', 'start_balance')]"/>
<field name="perm_read" eval="False"/>
<field name="perm_write" eval="False"/>
</record>
<record model="ir.model.field.access" id="fa_book-currency-anon"> <record model="ir.model.field.access" id="fa_book-currency-anon">
<field name="field" <field name="field"
search="[('model.model', '=', 'cashbook.book'), ('name', '=', 'currency')]"/> search="[('model.model', '=', 'cashbook.book'), ('name', '=', 'currency')]"/>
<field name="perm_read" eval="False"/> <field name="perm_read" eval="False"/>
<field name="perm_write" eval="False"/> <field name="perm_write" eval="False"/>
</record> </record>
<record model="ir.model.field.access" id="fa_book-parent-anon">
<field name="field"
search="[('model.model', '=', 'cashbook.book'), ('name', '=', 'parent')]"/>
<field name="perm_read" eval="False"/>
<field name="perm_write" eval="False"/>
</record>
<record model="ir.model.field.access" id="fa_book-childs-anon">
<field name="field"
search="[('model.model', '=', 'cashbook.book'), ('name', '=', 'childs')]"/>
<field name="perm_read" eval="False"/>
<field name="perm_write" eval="False"/>
</record>
<!-- field-access - group_cashbook_admin --> <!-- field-access - group_cashbook_admin -->
<record model="ir.model.field.access" id="fa_book-company-group_cashbook_admin"> <record model="ir.model.field.access" id="fa_book-company-group_cashbook_admin">
@ -335,16 +365,23 @@ full copyright notices and license terms. -->
<field name="perm_read" eval="True"/> <field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/> <field name="perm_write" eval="True"/>
</record> </record>
<record model="ir.model.field.access" id="fa_book-start_balance-group_cashbook_admin"> <record model="ir.model.field.access" id="fa_book-currency-group_cashbook_admin">
<field name="field" <field name="field"
search="[('model.model', '=', 'cashbook.book'), ('name', '=', 'start_balance')]"/> search="[('model.model', '=', 'cashbook.book'), ('name', '=', 'currency')]"/>
<field name="group" ref="group_cashbook_admin"/> <field name="group" ref="group_cashbook_admin"/>
<field name="perm_read" eval="True"/> <field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/> <field name="perm_write" eval="True"/>
</record> </record>
<record model="ir.model.field.access" id="fa_book-currency-group_cashbook_admin"> <record model="ir.model.field.access" id="fa_book-parent-group_cashbook_admin">
<field name="field" <field name="field"
search="[('model.model', '=', 'cashbook.book'), ('name', '=', 'currency')]"/> search="[('model.model', '=', 'cashbook.book'), ('name', '=', 'parent')]"/>
<field name="group" ref="group_cashbook_admin"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
</record>
<record model="ir.model.field.access" id="fa_book-childs-group_cashbook_admin">
<field name="field"
search="[('model.model', '=', 'cashbook.book'), ('name', '=', 'childs')]"/>
<field name="group" ref="group_cashbook_admin"/> <field name="group" ref="group_cashbook_admin"/>
<field name="perm_read" eval="True"/> <field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/> <field name="perm_write" eval="True"/>
@ -421,16 +458,23 @@ full copyright notices and license terms. -->
<field name="perm_read" eval="True"/> <field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/> <field name="perm_write" eval="False"/>
</record> </record>
<record model="ir.model.field.access" id="fa_book-start_balance-group_cashbook"> <record model="ir.model.field.access" id="fa_book-currency-group_cashbook">
<field name="field" <field name="field"
search="[('model.model', '=', 'cashbook.book'), ('name', '=', 'start_balance')]"/> search="[('model.model', '=', 'cashbook.book'), ('name', '=', 'currency')]"/>
<field name="group" ref="group_cashbook"/> <field name="group" ref="group_cashbook"/>
<field name="perm_read" eval="True"/> <field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/> <field name="perm_write" eval="False"/>
</record> </record>
<record model="ir.model.field.access" id="fa_book-currency-group_cashbook"> <record model="ir.model.field.access" id="fa_book-parent-group_cashbook">
<field name="field" <field name="field"
search="[('model.model', '=', 'cashbook.book'), ('name', '=', 'currency')]"/> search="[('model.model', '=', 'cashbook.book'), ('name', '=', 'parent')]"/>
<field name="group" ref="group_cashbook"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
</record>
<record model="ir.model.field.access" id="fa_book-childs-group_cashbook">
<field name="field"
search="[('model.model', '=', 'cashbook.book'), ('name', '=', 'childs')]"/>
<field name="group" ref="group_cashbook"/> <field name="group" ref="group_cashbook"/>
<field name="perm_read" eval="True"/> <field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/> <field name="perm_write" eval="False"/>

View file

@ -3,12 +3,46 @@
# The COPYRIGHT file at the top level of this repository contains the # The COPYRIGHT file at the top level of this repository contains the
# full copyright notices and license terms. # full copyright notices and license terms.
from trytond.model import ModelView, ModelSQL, fields, Unique, tree, sequence_ordered from trytond.model import ModelView, ModelSQL, fields, Unique, Exclude, tree
from trytond.transaction import Transaction from trytond.transaction import Transaction
from trytond.pool import Pool from trytond.pool import Pool
from trytond.pyson import Eval, If, Bool from trytond.pyson import Eval, If, Bool
from trytond.exceptions import UserError from trytond.exceptions import UserError
from trytond.i18n import gettext from trytond.i18n import gettext
from sql.operators import Equal
from sql.functions import Function
from sql import With, Literal
class ArrayApppend(Function):
""" sql: array_append
"""
__slots__ = ()
_function = 'ARRAY_APPEND'
# end ArrayApppend
class ArrayToString(Function):
""" sql: array_to_string
"""
__slots__ = ()
_function = 'ARRAY_TO_STRING'
# end ArrayToString
class Array(Function):
""" sql: array-type
"""
__slots__ = ()
_function = 'ARRAY'
def __str__(self):
return self._function + '[' + ', '.join(
map(self._format, self.args)) + ']'
# end Array
sel_categorytype = [ sel_categorytype = [
@ -17,7 +51,7 @@ sel_categorytype = [
] ]
class Category(tree(separator='/'), sequence_ordered(), ModelSQL, ModelView): class Category(tree(separator='/'), ModelSQL, ModelView):
'Category' 'Category'
__name__ = 'cashbook.category' __name__ = 'cashbook.category'
@ -36,7 +70,6 @@ class Category(tree(separator='/'), sequence_ordered(), ModelSQL, ModelView):
company = fields.Many2One(string='Company', model_name='company.company', company = fields.Many2One(string='Company', model_name='company.company',
required=True, ondelete="RESTRICT") required=True, ondelete="RESTRICT")
sequence = fields.Integer(string='Sequence', select=True)
parent = fields.Many2One(string="Parent", parent = fields.Many2One(string="Parent",
model_name='cashbook.category', ondelete='RESTRICT', model_name='cashbook.category', ondelete='RESTRICT',
left='left', right='right') left='left', right='right')
@ -45,15 +78,35 @@ class Category(tree(separator='/'), sequence_ordered(), ModelSQL, ModelView):
left = fields.Integer(string='Left', required=True, select=True) left = fields.Integer(string='Left', required=True, select=True)
right = fields.Integer(string='Right', required=True, select=True) right = fields.Integer(string='Right', required=True, select=True)
@classmethod
def __register__(cls, module_name):
super(Category, cls).__register__(module_name)
cls.migrate_sequence(module_name)
@classmethod @classmethod
def __setup__(cls): def __setup__(cls):
super(Category, cls).__setup__() super(Category, cls).__setup__()
cls._order.insert(0, ('name', 'ASC')) cls._order.insert(0, ('rec_name', 'ASC'))
t = cls.__table__() t = cls.__table__()
cls._sql_constraints.extend([ cls._sql_constraints.extend([
('name_uniq', Unique(t, t.name, t.company, t.parent), 'cashbook.msg_category_name_unique'), ('name_uniq',
Unique(t, t.name, t.company, t.parent),
'cashbook.msg_category_name_unique'),
('name2_uniq',
Exclude(t,
(t.name, Equal),
(t.cattype, Equal),
where=(t.parent == None)),
'cashbook.msg_category_name_unique'),
]) ])
@classmethod
def migrate_sequence(cls, module_name):
""" remove colum 'sequence'
"""
table = cls.__table_handler__(module_name)
table.drop_column('sequence')
@classmethod @classmethod
def default_cattype(cls): def default_cattype(cls):
return 'out' return 'out'
@ -70,6 +123,34 @@ class Category(tree(separator='/'), sequence_ordered(), ModelSQL, ModelView):
def default_right(): def default_right():
return 0 return 0
@staticmethod
def order_rec_name(tables):
""" order by pos
a recursive sorting
"""
Category2 = Pool().get('cashbook.category')
tab_cat = Category2.__table__()
tab_cat2 = Category2.__table__()
table, _ = tables[None]
categories = With('id', 'name', 'name_path', recursive=True)
categories.query = tab_cat.select(
tab_cat.id, tab_cat.name, Array(tab_cat.name),
where = tab_cat.parent==None,
)
categories.query |= tab_cat2.join(categories,
condition=categories.id==tab_cat2.parent,
).select(
tab_cat2.id, tab_cat2.name, ArrayApppend(categories.name_path, tab_cat2.name),
)
categories.query.all_ = True
query = categories.select(
ArrayToString(categories.name_path, '/').as_('rec_name'),
where = table.id==categories.id,
with_ = [categories])
return [query]
@fields.depends('parent', '_parent_parent.cattype') @fields.depends('parent', '_parent_parent.cattype')
def on_change_with_parent_cattype(self, name=None): def on_change_with_parent_cattype(self, name=None):
""" get type of parent category or None """ get type of parent category or None

View file

@ -42,6 +42,20 @@ full copyright notices and license terms. -->
<field name="act_window" ref="act_category_list"/> <field name="act_window" ref="act_category_list"/>
</record> </record>
<!-- domain view - list -->
<record model="ir.action.act_window.domain" id="act_category_list_domain_in">
<field name="name">Revenue</field>
<field name="sequence" eval="10"/>
<field name="domain" eval="[('cattype', '=', 'in')]" pyson="1"/>
<field name="act_window" ref="act_category_list"/>
</record>
<record model="ir.action.act_window.domain" id="act_category_list_domain_out">
<field name="name">Expense</field>
<field name="sequence" eval="20"/>
<field name="domain" eval="[('cattype', '=', 'out')]" pyson="1"/>
<field name="act_window" ref="act_category_list"/>
</record>
<!-- action view - tree --> <!-- action view - tree -->
<record model="ir.action.act_window" id="act_category_tree"> <record model="ir.action.act_window" id="act_category_tree">
<field name="name">Category</field> <field name="name">Category</field>
@ -59,6 +73,21 @@ full copyright notices and license terms. -->
<field name="act_window" ref="act_category_tree"/> <field name="act_window" ref="act_category_tree"/>
</record> </record>
<!-- domain view - tree -->
<record model="ir.action.act_window.domain" id="act_category_tree_domain_in">
<field name="name">Revenue</field>
<field name="sequence" eval="10"/>
<field name="domain" eval="[('cattype', '=', 'in')]" pyson="1"/>
<field name="act_window" ref="act_category_tree"/>
</record>
<record model="ir.action.act_window.domain" id="act_category_tree_domain_out">
<field name="name">Expense</field>
<field name="sequence" eval="20"/>
<field name="domain" eval="[('cattype', '=', 'out')]" pyson="1"/>
<field name="act_window" ref="act_category_tree"/>
</record>
<!-- permission --> <!-- permission -->
<!-- anon: deny all --> <!-- anon: deny all -->
<record model="ir.model.access" id="access_category-anon"> <record model="ir.model.access" id="access_category-anon">

View file

@ -36,6 +36,42 @@ class Configuration(ModelSingleton, ModelSQL, ModelView, UserMultiValueMixin):
checked = fields.MultiValue(field_checked) checked = fields.MultiValue(field_checked)
done = fields.MultiValue(field_done) done = fields.MultiValue(field_done)
catnamelong = fields.MultiValue(field_catnamelong) catnamelong = fields.MultiValue(field_catnamelong)
defbook = fields.MultiValue(fields.Many2One(string='Default Cashbook',
help='The default cashbook is selected when you open the booking wizard.',
model_name='cashbook.book', ondelete='SET NULL',
domain=[
('btype', '!=', None), ('state', '=', 'open'),
]))
book1 = fields.MultiValue(fields.Many2One(string='Cashbook 1',
help='Cash book available in selection dialog.',
model_name='cashbook.book', ondelete='SET NULL',
domain=[
('btype', '!=', None), ('state', '=', 'open'),
]))
book2 = fields.MultiValue(fields.Many2One(string='Cashbook 2',
help='Cash book available in selection dialog.',
model_name='cashbook.book', ondelete='SET NULL',
domain=[
('btype', '!=', None), ('state', '=', 'open'),
]))
book3 = fields.MultiValue(fields.Many2One(string='Cashbook 3',
help='Cash book available in selection dialog.',
model_name='cashbook.book', ondelete='SET NULL',
domain=[
('btype', '!=', None), ('state', '=', 'open'),
]))
book4 = fields.MultiValue(fields.Many2One(string='Cashbook 4',
help='Cash book available in selection dialog.',
model_name='cashbook.book', ondelete='SET NULL',
domain=[
('btype', '!=', None), ('state', '=', 'open'),
]))
book5 = fields.MultiValue(fields.Many2One(string='Cashbook 5',
help='Cash book available in selection dialog.',
model_name='cashbook.book', ondelete='SET NULL',
domain=[
('btype', '!=', None), ('state', '=', 'open'),
]))
@classmethod @classmethod
def multivalue_model(cls, field): def multivalue_model(cls, field):
@ -44,7 +80,8 @@ class Configuration(ModelSingleton, ModelSQL, ModelView, UserMultiValueMixin):
pool = Pool() pool = Pool()
if field in ['date_from', 'date_to', 'checked', 'done', if field in ['date_from', 'date_to', 'checked', 'done',
'catnamelong']: 'catnamelong', 'defbook', 'book1', 'book2',
'book3', 'book4', 'book5']:
return pool.get('cashbook.configuration_user') return pool.get('cashbook.configuration_user')
return super(Configuration, cls).multivalue_model(field) return super(Configuration, cls).multivalue_model(field)
@ -82,6 +119,54 @@ class UserConfiguration(ModelSQL, UserValueMixin):
checked = field_checked checked = field_checked
done = field_done done = field_done
catnamelong = field_catnamelong catnamelong = field_catnamelong
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',
domain=[
('btype', '!=', None),
('state', '=', 'open'),
('owner.id', '=', Eval('iduser', -1))
], depends=['iduser'])
book1 = fields.Many2One(string='Cashbook 1',
help='Cash book available in selection dialog.',
model_name='cashbook.book', ondelete='SET NULL',
domain=[
('btype', '!=', None),
('state', '=', 'open'),
('owner.id', '=', Eval('iduser', -1))
], depends=['iduser'])
book2 = fields.Many2One(string='Cashbook 2',
help='Cash book available in selection dialog.',
model_name='cashbook.book', ondelete='SET NULL',
domain=[
('btype', '!=', None),
('state', '=', 'open'),
('owner.id', '=', Eval('iduser', -1))
], depends=['iduser'])
book3 = fields.Many2One(string='Cashbook 3',
help='Cash book available in selection dialog.',
model_name='cashbook.book', ondelete='SET NULL',
domain=[
('btype', '!=', None),
('state', '=', 'open'),
('owner.id', '=', Eval('iduser', -1))
], depends=['iduser'])
book4 = fields.Many2One(string='Cashbook 4',
help='Cash book available in selection dialog.',
model_name='cashbook.book', ondelete='SET NULL',
domain=[
('btype', '!=', None),
('state', '=', 'open'),
('owner.id', '=', Eval('iduser', -1))
], depends=['iduser'])
book5 = fields.Many2One(string='Cashbook 5',
help='Cash book available in selection dialog.',
model_name='cashbook.book', ondelete='SET NULL',
domain=[
('btype', '!=', None),
('state', '=', 'open'),
('owner.id', '=', Eval('iduser', -1))
], depends=['iduser'])
@classmethod @classmethod
def default_checked(cls): def default_checked(cls):

144
line.py
View file

@ -51,14 +51,17 @@ class Line(Workflow, ModelSQL, ModelView):
__name__ = 'cashbook.line' __name__ = 'cashbook.line'
cashbook = fields.Many2One(string='Cashbook', required=True, select=True, cashbook = fields.Many2One(string='Cashbook', required=True, select=True,
model_name='cashbook.book', ondelete='CASCADE', readonly=True) model_name='cashbook.book', ondelete='CASCADE', readonly=True,
domain=[('btype', '!=', None)])
date = fields.Date(string='Date', required=True, select=True, date = fields.Date(string='Date', required=True, select=True,
states=STATES, depends=DEPENDS) states=STATES, depends=DEPENDS)
month = fields.Function(fields.Integer(string='Month', readonly=True), month = fields.Function(fields.Integer(string='Month', readonly=True),
'on_change_with_month', searcher='search_month') 'on_change_with_month', searcher='search_month')
number = fields.Char(string='Number', readonly=True) number = fields.Char(string='Number', readonly=True)
description = fields.Text(string='Description', description = fields.Text(string='Description', select=True,
states=STATES, depends=DEPENDS) states=STATES, depends=DEPENDS)
descr_short = fields.Function(fields.Char(string='Description', readonly=True),
'on_change_with_descr_short', searcher='search_descr_short')
category = fields.Many2One(string='Category', category = fields.Many2One(string='Category',
model_name='cashbook.category', ondelete='RESTRICT', model_name='cashbook.category', ondelete='RESTRICT',
states={ states={
@ -171,6 +174,13 @@ class Line(Workflow, ModelSQL, ModelView):
#image = fields.Binary... #image = fields.Binary...
@classmethod
def __register__(cls, module_name):
super(Line, cls).__register__(module_name)
table = cls.__table_handler__(module_name)
table.drop_constraint('amount_val')
@classmethod @classmethod
def __setup__(cls): def __setup__(cls):
super(Line, cls).__setup__() super(Line, cls).__setup__()
@ -270,8 +280,10 @@ class Line(Workflow, ModelSQL, ModelView):
'cashbook.msg_line_err_write_to_reconciled', 'cashbook.msg_line_err_write_to_reconciled',
datetxt = Report.format_date(line.date), datetxt = Report.format_date(line.date),
)) ))
# in case of 'mvin' or 'mvout' - create counterpart
if (line.bookingtype in ['mvout', 'mvin']) and (line.reference is None): if line.reference is None:
if line.bookingtype in ['mvout', 'mvin']:
# in case of 'mvin' or 'mvout' - add counterpart
values = { values = {
'cashbook': line.booktransf.id, 'cashbook': line.booktransf.id,
'bookingtype': 'mvin' if line.bookingtype == 'mvout' else 'mvout', 'bookingtype': 'mvin' if line.bookingtype == 'mvout' else 'mvout',
@ -283,6 +295,29 @@ class Line(Workflow, ModelSQL, ModelView):
values.update(line.get_amount_by_second_currency(line.booktransf.currency)) values.update(line.get_amount_by_second_currency(line.booktransf.currency))
values.update(cls.get_debit_credit(values)) values.update(cls.get_debit_credit(values))
to_create_line.append(values) to_create_line.append(values)
elif line.bookingtype in ['spout', 'spin']:
# splitbooking can have a transfer - add counterpart
for sp_line in line.splitlines:
if sp_line.splittype != 'tr':
continue
values = {
'cashbook': sp_line.booktransf.id,
'date': line.date,
'description': sp_line.description,
'booktransf': line.cashbook.id,
'reference': line.id,
}
if line.bookingtype.endswith('out'):
values['bookingtype'] = 'mvin'
else :
values['bookingtype'] = 'mvout'
values.update(line.get_amount_by_second_currency(
sp_line.booktransf.currency,
amount = sp_line.amount,
))
values.update(cls.get_debit_credit(values))
to_create_line.append(values)
# add number to line # add number to line
if line.cashbook.number_atcheck == True: if line.cashbook.number_atcheck == True:
@ -345,7 +380,12 @@ class Line(Workflow, ModelSQL, ModelView):
def search_rec_name(cls, name, clause): def search_rec_name(cls, name, clause):
""" search in description +... """ search in description +...
""" """
return [('description',) + tuple(clause[1:])] return cls.search_payee(name, clause) + [
('description',) + tuple(clause[1:]),
('category.rec_name',) + tuple(clause[1:]),
('splitlines.description',) + tuple(clause[1:]),
('splitlines.category.rec_name',) + tuple(clause[1:]),
]
def get_rec_name(self, name): def get_rec_name(self, name):
""" short + name """ short + name
@ -379,7 +419,7 @@ class Line(Workflow, ModelSQL, ModelView):
}): }):
values['amount'] = Currency.compute( values['amount'] = Currency.compute(
self.cashbook.currency, self.cashbook.currency,
self.amount, values['amount'],
to_currency) to_currency)
return values return values
@ -414,6 +454,13 @@ class Line(Workflow, ModelSQL, ModelView):
return [tab2] return [tab2]
@staticmethod
def order_descr_short(tables):
""" order by 'description'
"""
table, _ = tables[None]
return [table.description]
@classmethod @classmethod
def search_payee(cls, names, clause): def search_payee(cls, names, clause):
""" search in payee for party or cashbook """ search in payee for party or cashbook
@ -454,6 +501,12 @@ class Line(Workflow, ModelSQL, ModelView):
""" """
return [('cashbook.state',) + tuple(clause[1:])] return [('cashbook.state',) + tuple(clause[1:])]
@classmethod
def search_descr_short(cls, names, clause):
""" search in description
"""
return [('description',) + tuple(clause[1:])]
@fields.depends('amount', 'splitlines') @fields.depends('amount', 'splitlines')
def on_change_splitlines(self): def on_change_splitlines(self):
""" update amount if splitlines change """ update amount if splitlines change
@ -481,6 +534,13 @@ class Line(Workflow, ModelSQL, ModelView):
else : else :
self.splitlines = [] self.splitlines = []
@fields.depends('description')
def on_change_with_descr_short(self, name=None):
""" to speed up list-view
"""
if self.description:
return self.description[:50].replace('\n', '; ')
@fields.depends('party', 'booktransf', 'bookingtype') @fields.depends('party', 'booktransf', 'bookingtype')
def on_change_with_payee(self, name=None): def on_change_with_payee(self, name=None):
""" get party or cashbook """ get party or cashbook
@ -548,8 +608,9 @@ class Line(Workflow, ModelSQL, ModelView):
return 2 return 2
@fields.depends('id', 'date', 'cashbook', \ @fields.depends('id', 'date', 'cashbook', \
'_parent_cashbook.start_balance', '_parent_cashbook.id',\ '_parent_cashbook.id', 'reconciliation', \
'reconciliation', '_parent_reconciliation.start_amount') '_parent_reconciliation.start_amount',\
'_parent_reconciliation.state')
def on_change_with_balance(self, name=None): def on_change_with_balance(self, name=None):
""" compute balance until current line, with current sort order, """ compute balance until current line, with current sort order,
try to use a reconciliation as start to speed up calculation try to use a reconciliation as start to speed up calculation
@ -558,31 +619,55 @@ class Line(Workflow, ModelSQL, ModelView):
Reconciliation = pool.get('cashbook.recon') Reconciliation = pool.get('cashbook.recon')
Line = pool.get('cashbook.line') Line = pool.get('cashbook.line')
def get_from_last_recon(line2):
""" search last reconciliation in state 'done',
generate query
"""
query2 = []
end_amount = None
recons = Reconciliation.search([
('cashbook.id', '=', self.cashbook.id),
('date_to', '<=', line2.date),
('state', '=', 'done'),
], order=[('date_from', 'DESC')], limit=1)
if len(recons) > 0:
query2.append([
('date', '>=', recons[0].date_to),
('date', '<=', line2.date),
['OR',
('reconciliation', '=', None),
('reconciliation.id', '!=', recons[0]),
],
])
end_amount = recons[0].end_amount
return (query2, end_amount)
if self.cashbook: if self.cashbook:
query = [ query = [
('cashbook.id', '=', self.cashbook.id), ('cashbook.id', '=', self.cashbook.id),
] ]
balance = self.cashbook.start_balance balance = Decimal('0.0')
# get existing reconciliation, starting before current line # get existing reconciliation, starting before current line
# this will speed up calculation of by-line-balance # this will speed up calculation of by-line-balance
if self.date is not None: if self.date is not None:
recons = Reconciliation.search([ if self.reconciliation:
('cashbook.id', '=', self.cashbook.id), if self.reconciliation.state == 'done':
('date_from', '<=', self.date), query.append(
('state', '=', 'done'), ('reconciliation.id', '=', self.reconciliation.id),
], order=[('date_from', 'DESC')], limit=1) )
if len(recons) > 0: balance = self.reconciliation.start_amount
query.extend([ else :
['OR', (query2, balance2) = get_from_last_recon(self)
('date', '>', recons[0].date_from), query.extend(query2)
[ if balance2 is not None:
('date', '=', recons[0].date_from), balance = balance2
('reconciliation.id', '=',recons[0].id), else :
], (query2, balance2) = get_from_last_recon(self)
] query.extend(query2)
]) if balance2 is not None:
balance = recons[0].start_amount balance = balance2
lines = Line.search(query) lines = Line.search(query)
for line in lines: for line in lines:
@ -689,6 +774,8 @@ class Line(Workflow, ModelSQL, ModelView):
# splitline: category <--> bookingtype? # splitline: category <--> bookingtype?
for spline in line.splitlines: for spline in line.splitlines:
if spline.splittype != 'cat':
continue
if not line.bookingtype in types[spline.category.cattype]: if not line.bookingtype in types[spline.category.cattype]:
raise UserError(gettext( raise UserError(gettext(
'cashbook.msg_line_split_invalid_category', 'cashbook.msg_line_split_invalid_category',
@ -722,7 +809,7 @@ class Line(Workflow, ModelSQL, ModelView):
# deny write if line is not 'Edit' # deny write if line is not 'Edit'
if line.state != 'edit': if line.state != 'edit':
# allow state-update, if its the only action # allow state-update, if its the only action
if not ((len(set({'state', 'reconciliation'}).intersection(values.keys())) > 0) \ if not ((len(set({'state', 'reconciliation', 'number'}).intersection(values.keys())) > 0) \
and (len(values.keys()) == 1)): and (len(values.keys()) == 1)):
raise UserError(gettext( raise UserError(gettext(
'cashbook.msg_line_deny_write', 'cashbook.msg_line_deny_write',
@ -844,16 +931,19 @@ class LineContext(ModelView):
cashbook = fields.Many2One(string='Cashbook', required=True, cashbook = fields.Many2One(string='Cashbook', required=True,
model_name='cashbook.book', model_name='cashbook.book',
domain=[('btype', '!=', None)],
states={ states={
'readonly': Eval('num_cashbook', 0) < 2, 'readonly': Eval('num_cashbook', 0) < 2,
}, depends=['num_cashbook']) }, depends=['num_cashbook'])
date_from = fields.Date(string='Start Date', depends=['date_to'], date_from = fields.Date(string='Start Date', depends=['date_to'],
help='Limits the date range for the displayed entries.',
domain=[ domain=[
If(Eval('date_to') & Eval('date_from'), If(Eval('date_to') & Eval('date_from'),
('date_from', '<=', Eval('date_to')), ('date_from', '<=', Eval('date_to')),
()), ()),
]) ])
date_to = fields.Date(string='End Date', depends=['date_from'], date_to = fields.Date(string='End Date', depends=['date_from'],
help='Limits the date range for the displayed entries.',
domain=[ domain=[
If(Eval('date_to') & Eval('date_from'), If(Eval('date_to') & Eval('date_from'),
('date_from', '<=', Eval('date_to')), ('date_from', '<=', Eval('date_to')),
@ -904,7 +994,7 @@ class LineContext(ModelView):
with Transaction().set_context({ with Transaction().set_context({
'_check_access': True, '_check_access': True,
}): }):
return CashBook.search_count([]) return CashBook.search_count([('btype', '!=', None)])
@classmethod @classmethod
def default_done(cls): def default_done(cls):

View file

@ -70,10 +70,6 @@ msgctxt "model:ir.message,text:msg_category_type_not_like_parent"
msgid "The type of the current category '%(catname)s' must be equal to the type of the parent category '%(parentname)s'." msgid "The type of the current category '%(catname)s' must be equal to the type of the parent category '%(parentname)s'."
msgstr "Der Typ der aktuellen Kategorie '%(catname)s' muß gleich dem Typ der übergeordneten Kategorie '%(parentname)s' sein." msgstr "Der Typ der aktuellen Kategorie '%(catname)s' muß gleich dem Typ der übergeordneten Kategorie '%(parentname)s' sein."
msgctxt "model:ir.message,text:msg_book_err_startamount_with_lines"
msgid "The initial amount of the cash book '%(bookname)s' cannot be changed because it already contains bookings."
msgstr "Der Anfangsbetrag des Kassenbuchs '%(bookname)s' kann nicht geändert werden, da es bereits Buchungen enthält."
msgctxt "model:ir.message,text:msg_line_deny_recon_by_state" msgctxt "model:ir.message,text:msg_line_deny_recon_by_state"
msgid "For reconciliation, the line '%(recname)s' must be in the status 'Check' or 'Done'." msgid "For reconciliation, the line '%(recname)s' must be in the status 'Check' or 'Done'."
msgstr "Für die Abstimmung muss die Zeile '%(recname)s' im Status 'Prüfen' oder 'Fertig' sein." msgstr "Für die Abstimmung muss die Zeile '%(recname)s' im Status 'Prüfen' oder 'Fertig' sein."
@ -150,6 +146,10 @@ msgctxt "model:ir.message,text:msg_line_invalid_category"
msgid "The category of the booking line '%(recname)s' does not match the posting type '%(booktype)s'." msgid "The category of the booking line '%(recname)s' does not match the posting type '%(booktype)s'."
msgstr "Die Kategorie der Buchungszeile '%(recname)s' paßt nicht zum Buchungstyp '%(booktype)s'." msgstr "Die Kategorie der Buchungszeile '%(recname)s' paßt nicht zum Buchungstyp '%(booktype)s'."
msgctxt "model:ir.message,text:msg_book_btype_with_lines"
msgid "The type cannot be deleted on the cash book '%(cbname)s' because it still contains %(numlines)s lines."
msgstr "Der Typ kann am Kassenbuch '%(cbname)s' nicht gelöscht werden, da es noch %(numlines)s Zeilen enthält."
############# #############
# res.group # # res.group #
@ -258,6 +258,10 @@ msgctxt "model:ir.ui.menu,name:menu_booklist"
msgid "Cashbook" msgid "Cashbook"
msgstr "Kassenbuch" msgstr "Kassenbuch"
msgctxt "model:ir.ui.menu,name:menu_booktree"
msgid "Cashbook"
msgstr "Kassenbuch"
msgctxt "model:ir.ui.menu,name:menu_open_lines" msgctxt "model:ir.ui.menu,name:menu_open_lines"
msgid "Open Cashbook" msgid "Open Cashbook"
msgstr "Kassenbuch öffnen" msgstr "Kassenbuch öffnen"
@ -274,6 +278,10 @@ msgctxt "model:ir.ui.menu,name:act_category_view"
msgid "Category" msgid "Category"
msgstr "Kategorie" msgstr "Kategorie"
msgctxt "model:ir.ui.menu,name:menu_enter_booking"
msgid "Enter Booking"
msgstr "Buchung eingeben"
############# #############
# ir.action # # ir.action #
@ -282,6 +290,10 @@ msgctxt "model:ir.action,name:act_book_view"
msgid "Cashbook" msgid "Cashbook"
msgstr "Kassenbuch" msgstr "Kassenbuch"
msgctxt "model:ir.action,name:act_book_tree"
msgid "Cashbook"
msgstr "Kassenbuch"
msgctxt "model:ir.action,name:act_type_view" msgctxt "model:ir.action,name:act_type_view"
msgid "Cashbook Type" msgid "Cashbook Type"
msgstr "Kassenbuchtyp" msgstr "Kassenbuchtyp"
@ -302,6 +314,10 @@ msgctxt "model:ir.action,name:act_wizard_report"
msgid "Cashbook Report" msgid "Cashbook Report"
msgstr "Kassenbuch Bericht" msgstr "Kassenbuch Bericht"
msgctxt "model:ir.action,name:act_enterbooking_wiz"
msgid "Enter Booking"
msgstr "Buchung eingeben"
############################### ###############################
# ir.action.act_window.domain # # ir.action.act_window.domain #
@ -318,6 +334,22 @@ msgctxt "model:ir.action.act_window.domain,name:act_line_domain_all"
msgid "All" msgid "All"
msgstr "Alle" msgstr "Alle"
msgctxt "model:ir.action.act_window.domain,name:act_category_tree_domain_in"
msgid "Revenue"
msgstr "Einnahmen"
msgctxt "model:ir.action.act_window.domain,name:act_category_tree_domain_out"
msgid "Expense"
msgstr "Ausgaben"
msgctxt "model:ir.action.act_window.domain,name:act_category_list_domain_in"
msgid "Revenue"
msgstr "Einnahmen"
msgctxt "model:ir.action.act_window.domain,name:act_category_list_domain_out"
msgid "Expense"
msgstr "Ausgaben"
################### ###################
# ir.model.button # # ir.model.button #
@ -390,18 +422,42 @@ msgctxt "view:cashbook.book:"
msgid "Owner and Authorizeds" msgid "Owner and Authorizeds"
msgstr "Eigentümer und Autorisierte" msgstr "Eigentümer und Autorisierte"
msgctxt "view:cashbook.book:"
msgid "General Information"
msgstr "Allgemein"
msgctxt "view:cashbook.book:"
msgid "Amount and Numbering"
msgstr "Betrag und Nummerierung"
msgctxt "view:cashbook.book:"
msgid "Balance"
msgstr "Saldo"
msgctxt "view:cashbook.book:" msgctxt "view:cashbook.book:"
msgid "Reconciliations" msgid "Reconciliations"
msgstr "Abstimmungen" msgstr "Abstimmungen"
msgctxt "view:cashbook.book:"
msgid "Description"
msgstr "Beschreibung"
msgctxt "field:cashbook.book,name:" msgctxt "field:cashbook.book,name:"
msgid "Name" msgid "Name"
msgstr "Name" msgstr "Name"
msgctxt "field:cashbook.book,description:"
msgid "Description"
msgstr "Beschreibung"
msgctxt "field:cashbook.book,btype:" msgctxt "field:cashbook.book,btype:"
msgid "Type" msgid "Type"
msgstr "Typ" msgstr "Typ"
msgctxt "help:cashbook.book,btype:"
msgid "A cash book with type can contain postings. Without type is a view."
msgstr "Ein Kassenbuch mit Typ kann Buchungen enthalten. Ohne Typ ist eine Sicht."
msgctxt "field:cashbook.book,state:" msgctxt "field:cashbook.book,state:"
msgid "State" msgid "State"
msgstr "Status" msgstr "Status"
@ -446,9 +502,9 @@ msgctxt "field:cashbook.book,currency:"
msgid "Currency" msgid "Currency"
msgstr "Währung" msgstr "Währung"
msgctxt "field:cashbook.book,start_balance:" msgctxt "field:cashbook.book,currency_digits:"
msgid "Initial Amount" msgid "Currency Digits"
msgstr "Anfangsbetrag" msgstr "Nachkommastellen Währung"
msgctxt "field:cashbook.book,start_date:" msgctxt "field:cashbook.book,start_date:"
msgid "Initial Date" msgid "Initial Date"
@ -482,6 +538,22 @@ msgctxt "help:cashbook.book,number_atcheck:"
msgid "The numbering of the lines is done in the step Check. If the check mark is inactive, this happens with Done." msgid "The numbering of the lines is done in the step Check. If the check mark is inactive, this happens with Done."
msgstr "Die Nummerierung der Zeilen wird beim Schritt 'Prüfen' erledigt. Bei inaktivem Häkchen passiert dies erst bei 'Fertig'." msgstr "Die Nummerierung der Zeilen wird beim Schritt 'Prüfen' erledigt. Bei inaktivem Häkchen passiert dies erst bei 'Fertig'."
msgctxt "field:cashbook.book,parent:"
msgid "Parent"
msgstr "Übergeordnet"
msgctxt "field:cashbook.book,childs:"
msgid "Children"
msgstr "Untergeordnet"
msgctxt "field:cashbook.book,left:"
msgid "Left"
msgstr "Links"
msgctxt "field:cashbook.book,right:"
msgid "Right"
msgstr "Rechts"
################## ##################
# cashbook.split # # cashbook.split #
@ -570,6 +642,46 @@ msgctxt "field:cashbook.split,state_cashbook:"
msgid "State of Cashbook" msgid "State of Cashbook"
msgstr "Kassenbuchstatus" msgstr "Kassenbuchstatus"
msgctxt "field:cashbook.split,splittype:"
msgid "Type"
msgstr "Typ"
msgctxt "help:cashbook.split,splittype:"
msgid "Type of split booking line"
msgstr "Typ der Splitbuchungszeile"
msgctxt "selection:cashbook.split,splittype:"
msgid "Category"
msgstr "Kategorie"
msgctxt "selection:cashbook.split,splittype:"
msgid "Transfer"
msgstr "Umbuchung"
msgctxt "field:cashbook.split,target:"
msgid "Target"
msgstr "Ziel"
msgctxt "selection:cashbook.split,target:"
msgid "Cashbook"
msgstr "Kassenbuch"
msgctxt "selection:cashbook.split,target:"
msgid "Category"
msgstr "Kategorie"
msgctxt "field:cashbook.split,cashbook:"
msgid "Cashbook"
msgstr "Kassenbuch"
msgctxt "field:cashbook.split,owner_cashbook:"
msgid "Owner"
msgstr "Eigentümer"
msgctxt "field:cashbook.split,booktransf:"
msgid "Source/Dest"
msgstr "Quelle/Ziel"
################# #################
# cashbook.line # # cashbook.line #
@ -618,6 +730,10 @@ msgctxt "field:cashbook.line,description:"
msgid "Description" msgid "Description"
msgstr "Beschreibung" msgstr "Beschreibung"
msgctxt "field:cashbook.line,descr_short:"
msgid "Description"
msgstr "Beschreibung"
msgctxt "field:cashbook.line,state:" msgctxt "field:cashbook.line,state:"
msgid "State" msgid "State"
msgstr "Status" msgstr "Status"
@ -934,10 +1050,18 @@ msgctxt "field:cashbook.line.context,date_from:"
msgid "Start Date" msgid "Start Date"
msgstr "Beginndatum" msgstr "Beginndatum"
msgctxt "help:cashbook.line.context,date_from:"
msgid "Limits the date range for the displayed entries."
msgstr "Begrenzt den Datumsbereich für die angezeigten Einträge."
msgctxt "field:cashbook.line.context,date_to:" msgctxt "field:cashbook.line.context,date_to:"
msgid "End Date" msgid "End Date"
msgstr "Endedatum" msgstr "Endedatum"
msgctxt "help:cashbook.line.context,date_to:"
msgid "Limits the date range for the displayed entries."
msgstr "Begrenzt den Datumsbereich für die angezeigten Einträge."
########################## ##########################
# cashbook.configuration # # cashbook.configuration #
@ -946,6 +1070,10 @@ msgctxt "model:cashbook.configuration,name:"
msgid "Configuration" msgid "Configuration"
msgstr "Konfiguration" msgstr "Konfiguration"
msgctxt "view:cashbook.configuration:"
msgid "Enter Booking Wizard"
msgstr "Dialog: Buchung eingeben"
msgctxt "view:cashbook.configuration:" msgctxt "view:cashbook.configuration:"
msgid "Open Cashbook Wizard" msgid "Open Cashbook Wizard"
msgstr "Dialog: Kassenbuch öffnen" msgstr "Dialog: Kassenbuch öffnen"
@ -954,6 +1082,54 @@ msgctxt "view:cashbook.configuration:"
msgid "Cashbook" msgid "Cashbook"
msgstr "Kassenbuch" 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,book1:"
msgid "Cashbook 1"
msgstr "Kassenbuch 1"
msgctxt "help:cashbook.configuration,book1:"
msgid "Cash book available in selection dialog."
msgstr "in Auswahldialog verfügbares Kassenbuch."
msgctxt "field:cashbook.configuration,book2:"
msgid "Cashbook 2"
msgstr "Kassenbuch 2"
msgctxt "help:cashbook.configuration,book2:"
msgid "Cash book available in selection dialog."
msgstr "in Auswahldialog verfügbares Kassenbuch."
msgctxt "field:cashbook.configuration,book3:"
msgid "Cashbook 3"
msgstr "Kassenbuch 3"
msgctxt "help:cashbook.configuration,book3:"
msgid "Cash book available in selection dialog."
msgstr "in Auswahldialog verfügbares Kassenbuch."
msgctxt "field:cashbook.configuration,book4:"
msgid "Cashbook 4"
msgstr "Kassenbuch 4"
msgctxt "help:cashbook.configuration,book4:"
msgid "Cash book available in selection dialog."
msgstr "in Auswahldialog verfügbares Kassenbuch."
msgctxt "field:cashbook.configuration,book5:"
msgid "Cashbook 5"
msgstr "Kassenbuch 5"
msgctxt "help:cashbook.configuration,book5:"
msgid "Cash book available in selection dialog."
msgstr "in Auswahldialog verfügbares Kassenbuch."
msgctxt "field:cashbook.configuration,date_from:" msgctxt "field:cashbook.configuration,date_from:"
msgid "Start Date" msgid "Start Date"
msgstr "Beginndatum" msgstr "Beginndatum"
@ -1026,6 +1202,54 @@ msgctxt "help:cashbook.configuration_user,catnamelong:"
msgid "Shows the long name of the category in the Category field of a cash book line." 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." 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."
msgctxt "field:cashbook.configuration_user,book1:"
msgid "Cashbook 1"
msgstr "Kassenbuch 1"
msgctxt "help:cashbook.configuration_user,book1:"
msgid "Cash book available in selection dialog."
msgstr "in Auswahldialog verfügbares Kassenbuch."
msgctxt "field:cashbook.configuration_user,book2:"
msgid "Cashbook 2"
msgstr "Kassenbuch 2"
msgctxt "help:cashbook.configuration_user,book2:"
msgid "Cash book available in selection dialog."
msgstr "in Auswahldialog verfügbares Kassenbuch."
msgctxt "field:cashbook.configuration_user,book3:"
msgid "Cashbook 3"
msgstr "Kassenbuch 3"
msgctxt "help:cashbook.configuration_user,book3:"
msgid "Cash book available in selection dialog."
msgstr "in Auswahldialog verfügbares Kassenbuch."
msgctxt "field:cashbook.configuration_user,book4:"
msgid "Cashbook 4"
msgstr "Kassenbuch 4"
msgctxt "help:cashbook.configuration_user,book4:"
msgid "Cash book available in selection dialog."
msgstr "in Auswahldialog verfügbares Kassenbuch."
msgctxt "field:cashbook.configuration_user,book5:"
msgid "Cashbook 5"
msgstr "Kassenbuch 5"
msgctxt "help:cashbook.configuration_user,book5:"
msgid "Cash book available in selection dialog."
msgstr "in Auswahldialog verfügbares Kassenbuch."
################## ##################
# cashbook.recon # # cashbook.recon #
@ -1209,3 +1433,103 @@ msgstr "Gesamt"
msgctxt "report:cashbook.reprecon:" msgctxt "report:cashbook.reprecon:"
msgid "Payee" msgid "Payee"
msgstr "Empfänger" msgstr "Empfänger"
###############################
# cashbook.enterbooking.start #
###############################
msgctxt "model:cashbook.enterbooking.start,name:"
msgid "Enter Booking"
msgstr "Buchung eingeben"
msgctxt "view:cashbook.enterbooking.start:"
msgid "Description"
msgstr "Beschreibung"
msgctxt "view:cashbook.enterbooking.start:"
msgid "Booking"
msgstr "Buchung"
msgctxt "field:cashbook.enterbooking.start,cashbook:"
msgid "Cashbook"
msgstr "Kassenbuch"
msgctxt "field:cashbook.enterbooking.start,cashbooks:"
msgid "Cashbooks"
msgstr "Kassenbücher"
msgctxt "field:cashbook.enterbooking.start,currency_digits:"
msgid "Currency Digits"
msgstr "Nachkommastellen Währung"
msgctxt "field:cashbook.enterbooking.start,currency:"
msgid "Currency"
msgstr "Währung"
msgctxt "field:cashbook.enterbooking.start,bookingtype:"
msgid "Type"
msgstr "Typ"
msgctxt "selection:cashbook.enterbooking.start,bookingtype:"
msgid "Revenue"
msgstr "Einnahme"
msgctxt "selection:cashbook.enterbooking.start,bookingtype:"
msgid "Revenue Splitbooking"
msgstr "Einnahme Splitbuchung"
msgctxt "selection:cashbook.enterbooking.start,bookingtype:"
msgid "Expense"
msgstr "Ausgabe"
msgctxt "selection:cashbook.enterbooking.start,bookingtype:"
msgid "Expense Splitbooking"
msgstr "Ausgabe Splitbuchung"
msgctxt "selection:cashbook.enterbooking.start,bookingtype:"
msgid "Transfer from"
msgstr "Umbuchung von"
msgctxt "selection:cashbook.enterbooking.start,bookingtype:"
msgid "Transfer to"
msgstr "Umbuchung nach"
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"
#########################
# cashbook.enterbooking #
#########################
msgctxt "model:cashbook.enterbooking,name:"
msgid "Enter Booking"
msgstr "Buchung eingeben"
msgctxt "wizard_button:cashbook.enterbooking,start,end:"
msgid "Cancel"
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"

View file

@ -66,10 +66,6 @@ msgctxt "model:ir.message,text:msg_category_type_not_like_parent"
msgid "The type of the current category '%(catname)s' must be equal to the type of the parent category '%(parentname)s'." msgid "The type of the current category '%(catname)s' must be equal to the type of the parent category '%(parentname)s'."
msgstr "The type of the current category '%(catname)s' must be equal to the type of the parent category '%(parentname)s'." msgstr "The type of the current category '%(catname)s' must be equal to the type of the parent category '%(parentname)s'."
msgctxt "model:ir.message,text:msg_book_err_startamount_with_lines"
msgid "The initial amount of the cash book '%(bookname)s' cannot be changed because it already contains bookings."
msgstr "The initial amount of the cash book '%(bookname)s' cannot be changed because it already contains bookings."
msgctxt "model:ir.message,text:msg_line_deny_recon_by_state" msgctxt "model:ir.message,text:msg_line_deny_recon_by_state"
msgid "For reconciliation, the line '%(recname)s' must be in the status 'Check' or 'Done'." msgid "For reconciliation, the line '%(recname)s' must be in the status 'Check' or 'Done'."
msgstr "For reconciliation, the line '%(recname)s' must be in the status 'Check' or 'Done'." msgstr "For reconciliation, the line '%(recname)s' must be in the status 'Check' or 'Done'."
@ -146,6 +142,10 @@ msgctxt "model:ir.message,text:msg_line_invalid_category"
msgid "The category of the booking line '%(recname)s' does not match the posting type '%(booktype)s'." msgid "The category of the booking line '%(recname)s' does not match the posting type '%(booktype)s'."
msgstr "The category of the booking line '%(recname)s' does not match the posting type '%(booktype)s'." msgstr "The category of the booking line '%(recname)s' does not match the posting type '%(booktype)s'."
msgctxt "model:ir.message,text:msg_book_btype_with_lines"
msgid "The type cannot be deleted on the cash book '%(cbname)s' because it still contains %(numlines)s lines."
msgstr "The type cannot be deleted on the cash book '%(cbname)s' because it still contains %(numlines)s lines."
msgctxt "model:res.group,name:group_cashbook" msgctxt "model:res.group,name:group_cashbook"
msgid "Cashbook" msgid "Cashbook"
msgstr "Cashbook" msgstr "Cashbook"
@ -242,6 +242,10 @@ msgctxt "model:ir.ui.menu,name:menu_booklist"
msgid "Cashbook" msgid "Cashbook"
msgstr "Cashbook" msgstr "Cashbook"
msgctxt "model:ir.ui.menu,name:menu_booktree"
msgid "Cashbook"
msgstr "Cashbook"
msgctxt "model:ir.ui.menu,name:menu_open_lines" msgctxt "model:ir.ui.menu,name:menu_open_lines"
msgid "Open Cashbook" msgid "Open Cashbook"
msgstr "Open Cashbook" msgstr "Open Cashbook"
@ -258,10 +262,18 @@ msgctxt "model:ir.ui.menu,name:act_category_view"
msgid "Category" msgid "Category"
msgstr "Category" msgstr "Category"
msgctxt "model:ir.ui.menu,name:menu_enter_booking"
msgid "Enter Booking"
msgstr "Enter Booking"
msgctxt "model:ir.action,name:act_book_view" msgctxt "model:ir.action,name:act_book_view"
msgid "Cashbook" msgid "Cashbook"
msgstr "Cashbook" msgstr "Cashbook"
msgctxt "model:ir.action,name:act_book_tree"
msgid "Cashbook"
msgstr "Cashbook"
msgctxt "model:ir.action,name:act_type_view" msgctxt "model:ir.action,name:act_type_view"
msgid "Cashbook Type" msgid "Cashbook Type"
msgstr "Cashbook Type" msgstr "Cashbook Type"
@ -282,6 +294,10 @@ msgctxt "model:ir.action,name:act_wizard_report"
msgid "Cashbook Report" msgid "Cashbook Report"
msgstr "Cashbook Report" msgstr "Cashbook Report"
msgctxt "model:ir.action,name:act_enterbooking_wiz"
msgid "Enter Booking"
msgstr "Enter Booking"
msgctxt "model:ir.action.act_window.domain,name:act_line_domain_current" msgctxt "model:ir.action.act_window.domain,name:act_line_domain_current"
msgid "Current Month" msgid "Current Month"
msgstr "Current Month" msgstr "Current Month"
@ -294,6 +310,22 @@ msgctxt "model:ir.action.act_window.domain,name:act_line_domain_all"
msgid "All" msgid "All"
msgstr "All" msgstr "All"
msgctxt "model:ir.action.act_window.domain,name:act_category_tree_domain_in"
msgid "Revenue"
msgstr "Revenue"
msgctxt "model:ir.action.act_window.domain,name:act_category_tree_domain_out"
msgid "Expense"
msgstr "Expense"
msgctxt "model:ir.action.act_window.domain,name:act_category_list_domain_in"
msgid "Revenue"
msgstr "Revenue"
msgctxt "model:ir.action.act_window.domain,name:act_category_list_domain_out"
msgid "Expense"
msgstr "Expense"
msgctxt "model:ir.model.button,string:line_wfedit_button" msgctxt "model:ir.model.button,string:line_wfedit_button"
msgid "Edit" msgid "Edit"
msgstr "Edit" msgstr "Edit"
@ -354,18 +386,42 @@ msgctxt "view:cashbook.book:"
msgid "Owner and Authorizeds" msgid "Owner and Authorizeds"
msgstr "Owner and Authorizeds" msgstr "Owner and Authorizeds"
msgctxt "view:cashbook.book:"
msgid "General Information"
msgstr "General Information"
msgctxt "view:cashbook.book:"
msgid "Amount and Numbering"
msgstr "Amount and Numbering"
msgctxt "view:cashbook.book:"
msgid "Balance"
msgstr "Balance"
msgctxt "view:cashbook.book:" msgctxt "view:cashbook.book:"
msgid "Reconciliations" msgid "Reconciliations"
msgstr "Reconciliations" msgstr "Reconciliations"
msgctxt "view:cashbook.book:"
msgid "Description"
msgstr "Description"
msgctxt "field:cashbook.book,name:" msgctxt "field:cashbook.book,name:"
msgid "Name" msgid "Name"
msgstr "Name" msgstr "Name"
msgctxt "field:cashbook.book,description:"
msgid "Description"
msgstr "Description"
msgctxt "field:cashbook.book,btype:" msgctxt "field:cashbook.book,btype:"
msgid "Type" msgid "Type"
msgstr "Type" msgstr "Type"
msgctxt "help:cashbook.book,btype:"
msgid "A cash book with type can contain postings. Without type is a view."
msgstr "A cash book with type can contain postings. Without type is a view."
msgctxt "field:cashbook.book,state:" msgctxt "field:cashbook.book,state:"
msgid "State" msgid "State"
msgstr "State" msgstr "State"
@ -410,9 +466,9 @@ msgctxt "field:cashbook.book,currency:"
msgid "Currency" msgid "Currency"
msgstr "Currency" msgstr "Currency"
msgctxt "field:cashbook.book,start_balance:" msgctxt "field:cashbook.book,currency_digits:"
msgid "Initial Amount" msgid "Currency Digits"
msgstr "Initial Amount" msgstr "Currency Digits"
msgctxt "field:cashbook.book,start_date:" msgctxt "field:cashbook.book,start_date:"
msgid "Initial Date" msgid "Initial Date"
@ -446,6 +502,22 @@ msgctxt "help:cashbook.book,number_atcheck:"
msgid "The numbering of the lines is done in the step Check. If the check mark is inactive, this happens with Done." msgid "The numbering of the lines is done in the step Check. If the check mark is inactive, this happens with Done."
msgstr "The numbering of the lines is done in the step Check. If the check mark is inactive, this happens with Done." msgstr "The numbering of the lines is done in the step Check. If the check mark is inactive, this happens with Done."
msgctxt "field:cashbook.book,parent:"
msgid "Parent"
msgstr "Parent"
msgctxt "field:cashbook.book,childs:"
msgid "Children"
msgstr "Children"
msgctxt "field:cashbook.book,left:"
msgid "Left"
msgstr "Left"
msgctxt "field:cashbook.book,right:"
msgid "Right"
msgstr "Right"
msgctxt "model:cashbook.split,name:" msgctxt "model:cashbook.split,name:"
msgid "Split booking line" msgid "Split booking line"
msgstr "Split booking line" msgstr "Split booking line"
@ -530,6 +602,46 @@ msgctxt "field:cashbook.split,state_cashbook:"
msgid "State of Cashbook" msgid "State of Cashbook"
msgstr "State of Cashbook" msgstr "State of Cashbook"
msgctxt "field:cashbook.split,splittype:"
msgid "Type"
msgstr "Type"
msgctxt "help:cashbook.split,splittype:"
msgid "Type of split booking line"
msgstr "Type of split booking line"
msgctxt "selection:cashbook.split,splittype:"
msgid "Category"
msgstr "Category"
msgctxt "selection:cashbook.split,splittype:"
msgid "Transfer"
msgstr "Transfer"
msgctxt "field:cashbook.split,target:"
msgid "Target"
msgstr "Target"
msgctxt "selection:cashbook.split,target:"
msgid "Cashbook"
msgstr "Cashbook"
msgctxt "selection:cashbook.split,target:"
msgid "Category"
msgstr "Category"
msgctxt "field:cashbook.split,cashbook:"
msgid "Cashbook"
msgstr "Cashbook"
msgctxt "field:cashbook.split,owner_cashbook:"
msgid "Owner"
msgstr "Owner"
msgctxt "field:cashbook.split,booktransf:"
msgid "Source/Dest"
msgstr "Source/Dest"
msgctxt "model:cashbook.line,name:" msgctxt "model:cashbook.line,name:"
msgid "Cashbook Line" msgid "Cashbook Line"
msgstr "Cashbook Line" msgstr "Cashbook Line"
@ -574,6 +686,10 @@ msgctxt "field:cashbook.line,description:"
msgid "Description" msgid "Description"
msgstr "Description" msgstr "Description"
msgctxt "field:cashbook.line,descr_short:"
msgid "Description"
msgstr "Description"
msgctxt "field:cashbook.line,state:" msgctxt "field:cashbook.line,state:"
msgid "State" msgid "State"
msgstr "State" msgstr "State"
@ -870,14 +986,26 @@ msgctxt "field:cashbook.line.context,date_from:"
msgid "Start Date" msgid "Start Date"
msgstr "Start Date" msgstr "Start Date"
msgctxt "help:cashbook.line.context,date_from:"
msgid "Limits the date range for the displayed entries."
msgstr "Limits the date range for the displayed entries."
msgctxt "field:cashbook.line.context,date_to:" msgctxt "field:cashbook.line.context,date_to:"
msgid "End Date" msgid "End Date"
msgstr "End Date" msgstr "End Date"
msgctxt "help:cashbook.line.context,date_to:"
msgid "Limits the date range for the displayed entries."
msgstr "Limits the date range for the displayed entries."
msgctxt "model:cashbook.configuration,name:" msgctxt "model:cashbook.configuration,name:"
msgid "Configuration" msgid "Configuration"
msgstr "Configuration" msgstr "Configuration"
msgctxt "view:cashbook.configuration:"
msgid "Enter Booking Wizard"
msgstr "Enter Booking Wizard"
msgctxt "view:cashbook.configuration:" msgctxt "view:cashbook.configuration:"
msgid "Open Cashbook Wizard" msgid "Open Cashbook Wizard"
msgstr "Open Cashbook Wizard" msgstr "Open Cashbook Wizard"
@ -886,6 +1014,54 @@ msgctxt "view:cashbook.configuration:"
msgid "Cashbook" msgid "Cashbook"
msgstr "Cashbook" msgstr "Cashbook"
msgctxt "field:cashbook.configuration,defbook:"
msgid "Default Cashbook"
msgstr "Default Cashbook"
msgctxt "help:cashbook.configuration,defbook:"
msgid "The default cashbook is selected when you open the booking wizard."
msgstr "The default cashbook is selected when you open the booking wizard."
msgctxt "field:cashbook.configuration,book1:"
msgid "Cashbook 1"
msgstr "Cashbook 1"
msgctxt "help:cashbook.configuration,book1:"
msgid "Cash book available in selection dialog."
msgstr "Cash book available in selection dialog."
msgctxt "field:cashbook.configuration,book2:"
msgid "Cashbook 2"
msgstr "Cashbook 2"
msgctxt "help:cashbook.configuration,book2:"
msgid "Cash book available in selection dialog."
msgstr "Cash book available in selection dialog."
msgctxt "field:cashbook.configuration,book3:"
msgid "Cashbook 3"
msgstr "Cashbook 3"
msgctxt "help:cashbook.configuration,book3:"
msgid "Cash book available in selection dialog."
msgstr "Cash book available in selection dialog."
msgctxt "field:cashbook.configuration,book4:"
msgid "Cashbook 4"
msgstr "Cashbook 4"
msgctxt "help:cashbook.configuration,book4:"
msgid "Cash book available in selection dialog."
msgstr "Cash book available in selection dialog."
msgctxt "field:cashbook.configuration,book5:"
msgid "Cashbook 5"
msgstr "Cashbook 5"
msgctxt "help:cashbook.configuration,book5:"
msgid "Cash book available in selection dialog."
msgstr "Cash book available in selection dialog."
msgctxt "field:cashbook.configuration,date_from:" msgctxt "field:cashbook.configuration,date_from:"
msgid "Start Date" msgid "Start Date"
msgstr "Start Date" msgstr "Start Date"
@ -954,6 +1130,54 @@ msgctxt "help:cashbook.configuration_user,catnamelong:"
msgid "Shows the long name of the category in the Category field of a cash book line." msgid "Shows the long name of the category in the Category field of a cash book line."
msgstr "Shows the long name of the category in the Category field of a cash book line." msgstr "Shows the long name of the category in the Category field of a cash book line."
msgctxt "field:cashbook.configuration_user,defbook:"
msgid "Default Cashbook"
msgstr "Default Cashbook"
msgctxt "help:cashbook.configuration_user,defbook:"
msgid "The default cashbook is selected when you open the booking wizard."
msgstr "The default cashbook is selected when you open the booking wizard."
msgctxt "field:cashbook.configuration_user,book1:"
msgid "Cashbook 1"
msgstr "Cashbook 1"
msgctxt "help:cashbook.configuration_user,book1:"
msgid "Cash book available in selection dialog."
msgstr "Cash book available in selection dialog."
msgctxt "field:cashbook.configuration_user,book2:"
msgid "Cashbook 2"
msgstr "Cashbook 2"
msgctxt "help:cashbook.configuration_user,book2:"
msgid "Cash book available in selection dialog."
msgstr "Cash book available in selection dialog."
msgctxt "field:cashbook.configuration_user,book3:"
msgid "Cashbook 3"
msgstr "Cashbook 3"
msgctxt "help:cashbook.configuration_user,book3:"
msgid "Cash book available in selection dialog."
msgstr "Cash book available in selection dialog."
msgctxt "field:cashbook.configuration_user,book4:"
msgid "Cashbook 4"
msgstr "Cashbook 4"
msgctxt "help:cashbook.configuration_user,book4:"
msgid "Cash book available in selection dialog."
msgstr "Cash book available in selection dialog."
msgctxt "field:cashbook.configuration_user,book5:"
msgid "Cashbook 5"
msgstr "Cashbook 5"
msgctxt "help:cashbook.configuration_user,book5:"
msgid "Cash book available in selection dialog."
msgstr "Cash book available in selection dialog."
msgctxt "model:cashbook.recon,name:" msgctxt "model:cashbook.recon,name:"
msgid "Cashbook Reconciliation" msgid "Cashbook Reconciliation"
msgstr "Cashbook Reconciliation" msgstr "Cashbook Reconciliation"
@ -1122,3 +1346,95 @@ msgctxt "report:cashbook.reprecon:"
msgid "Total" msgid "Total"
msgstr "Total" msgstr "Total"
msgctxt "report:cashbook.reprecon:"
msgid "Payee"
msgstr "Payee"
msgctxt "model:cashbook.enterbooking.start,name:"
msgid "Enter Booking"
msgstr "Enter Booking"
msgctxt "view:cashbook.enterbooking.start:"
msgid "Description"
msgstr "Description"
msgctxt "view:cashbook.enterbooking.start:"
msgid "Booking"
msgstr "Booking"
msgctxt "field:cashbook.enterbooking.start,cashbook:"
msgid "Cashbook"
msgstr "Cashbook"
msgctxt "field:cashbook.enterbooking.start,cashbooks:"
msgid "Cashbooks"
msgstr "Cashbooks"
msgctxt "field:cashbook.enterbooking.start,currency_digits:"
msgid "Currency Digits"
msgstr "Currency Digits"
msgctxt "field:cashbook.enterbooking.start,currency:"
msgid "Currency"
msgstr "Currency"
msgctxt "field:cashbook.enterbooking.start,bookingtype:"
msgid "Type"
msgstr "Type"
msgctxt "selection:cashbook.enterbooking.start,bookingtype:"
msgid "Revenue"
msgstr "Revenue"
msgctxt "selection:cashbook.enterbooking.start,bookingtype:"
msgid "Revenue Splitbooking"
msgstr "Revenue Splitbooking"
msgctxt "selection:cashbook.enterbooking.start,bookingtype:"
msgid "Expense"
msgstr "Expense"
msgctxt "selection:cashbook.enterbooking.start,bookingtype:"
msgid "Expense Splitbooking"
msgstr "Expense Splitbooking"
msgctxt "selection:cashbook.enterbooking.start,bookingtype:"
msgid "Transfer from"
msgstr "Transfer from"
msgctxt "selection:cashbook.enterbooking.start,bookingtype:"
msgid "Transfer to"
msgstr "Transfer to"
msgctxt "field:cashbook.enterbooking.start,amount:"
msgid "Amount"
msgstr "Amount"
msgctxt "field:cashbook.enterbooking.start,owner_cashbook:"
msgid "Owner"
msgstr "Owner"
msgctxt "field:cashbook.enterbooking.start,category:"
msgid "Category"
msgstr "Category"
msgctxt "field:cashbook.enterbooking.start,booktransf:"
msgid "Source/Dest"
msgstr "Source/Dest"
msgctxt "field:cashbook.enterbooking.start,party:"
msgid "Party"
msgstr "Party"
msgctxt "model:cashbook.enterbooking,name:"
msgid "Enter Booking"
msgstr "Enter Booking"
msgctxt "wizard_button:cashbook.enterbooking,start,end:"
msgid "Cancel"
msgstr "Cancel"
msgctxt "wizard_button:cashbook.enterbooking,start,save_:"
msgid "Save"
msgstr "Save"

View file

@ -56,10 +56,23 @@ full copyright notices and license terms. -->
<field name="group" ref="group_cashbook_admin"/> <field name="group" ref="group_cashbook_admin"/>
</record> </record>
<!-- menu: /Cashbook/Enter Booking -->
<menuitem id="menu_enter_booking" action="act_enterbooking_wiz"
icon="tryton-add"
parent="menu_cashbook" sequence="20"/>
<record model="ir.ui.menu-res.group" id="menu_enter_booking-group_cashbook">
<field name="menu" ref="menu_enter_booking"/>
<field name="group" ref="group_cashbook"/>
</record>
<record model="ir.ui.menu-res.group" id="menu_enter_booking-group_cashbook_admin">
<field name="menu" ref="menu_enter_booking"/>
<field name="group" ref="group_cashbook_admin"/>
</record>
<!-- menu: /Cashbook/Open Cashbook --> <!-- menu: /Cashbook/Open Cashbook -->
<menuitem id="menu_open_lines" action="act_open_lines" <menuitem id="menu_open_lines" action="act_open_lines"
icon="tryton-list" icon="tryton-list"
parent="menu_cashbook" sequence="20"/> parent="menu_cashbook" sequence="30"/>
<record model="ir.ui.menu-res.group" id="menu_open_lines-group_cashbook"> <record model="ir.ui.menu-res.group" id="menu_open_lines-group_cashbook">
<field name="menu" ref="menu_open_lines"/> <field name="menu" ref="menu_open_lines"/>
<field name="group" ref="group_cashbook"/> <field name="group" ref="group_cashbook"/>
@ -70,9 +83,21 @@ full copyright notices and license terms. -->
</record> </record>
<!-- menu: /Cashbook/Cashbook --> <!-- menu: /Cashbook/Cashbook -->
<menuitem id="menu_booktree" action="act_book_tree"
icon="tryton-tree"
parent="menu_cashbook" sequence="40"/>
<record model="ir.ui.menu-res.group" id="menu_booktree-group_cashbook">
<field name="menu" ref="menu_booktree"/>
<field name="group" ref="group_cashbook"/>
</record>
<record model="ir.ui.menu-res.group" id="menu_booktree-group_cashbook_admin">
<field name="menu" ref="menu_booktree"/>
<field name="group" ref="group_cashbook_admin"/>
</record>
<!-- menu: /Cashbook/Cashbook/Cashbook -->
<menuitem id="menu_booklist" action="act_book_view" <menuitem id="menu_booklist" action="act_book_view"
icon="tryton-list" icon="tryton-list"
parent="menu_cashbook" sequence="30"/> parent="menu_booktree" sequence="10"/>
<record model="ir.ui.menu-res.group" id="menu_booklist-group_cashbook"> <record model="ir.ui.menu-res.group" id="menu_booklist-group_cashbook">
<field name="menu" ref="menu_booklist"/> <field name="menu" ref="menu_booklist"/>
<field name="group" ref="group_cashbook"/> <field name="group" ref="group_cashbook"/>

View file

@ -53,9 +53,6 @@ full copyright notices and license terms. -->
<record model="ir.message" id="msg_category_type_not_like_parent"> <record model="ir.message" id="msg_category_type_not_like_parent">
<field name="text">The type of the current category '%(catname)s' must be equal to the type of the parent category '%(parentname)s'.</field> <field name="text">The type of the current category '%(catname)s' must be equal to the type of the parent category '%(parentname)s'.</field>
</record> </record>
<record model="ir.message" id="msg_book_err_startamount_with_lines">
<field name="text">The initial amount of the cash book '%(bookname)s' cannot be changed because it already contains bookings.</field>
</record>
<record model="ir.message" id="msg_line_deny_recon_by_state"> <record model="ir.message" id="msg_line_deny_recon_by_state">
<field name="text">For reconciliation, the line '%(recname)s' must be in the status 'Check' or 'Done'.</field> <field name="text">For reconciliation, the line '%(recname)s' must be in the status 'Check' or 'Done'.</field>
</record> </record>
@ -113,6 +110,9 @@ full copyright notices and license terms. -->
<record model="ir.message" id="msg_line_invalid_category"> <record model="ir.message" id="msg_line_invalid_category">
<field name="text">The category of the booking line '%(recname)s' does not match the posting type '%(booktype)s'.</field> <field name="text">The category of the booking line '%(recname)s' does not match the posting type '%(booktype)s'.</field>
</record> </record>
<record model="ir.message" id="msg_book_btype_with_lines">
<field name="text">The type cannot be deleted on the cash book '%(cbname)s' because it still contains %(numlines)s lines.</field>
</record>
</data> </data>
</tryton> </tryton>

View file

@ -96,7 +96,7 @@ class Reconciliation(Workflow, ModelSQL, ModelView):
@classmethod @classmethod
def __setup__(cls): def __setup__(cls):
super(Reconciliation, cls).__setup__() super(Reconciliation, cls).__setup__()
cls._order.insert(0, ('date_from', 'ASC')) cls._order.insert(0, ('date_from', 'DESC'))
cls._transitions |= set(( cls._transitions |= set((
('edit', 'check'), ('edit', 'check'),
('check', 'done'), ('check', 'done'),
@ -226,7 +226,7 @@ class Reconciliation(Workflow, ModelSQL, ModelView):
)) ))
values['start_amount'] = reconciliation.predecessor.end_amount values['start_amount'] = reconciliation.predecessor.end_amount
else : else :
values['start_amount'] = reconciliation.cashbook.start_balance values['start_amount'] = Decimal('0.0')
values['end_amount'] = values['start_amount'] values['end_amount'] = values['start_amount']
# add 'checked'-lines to reconciliation # add 'checked'-lines to reconciliation

View file

@ -14,6 +14,17 @@ from .line import sel_linetype, sel_bookingtype, STATES, DEPENDS
from .book import sel_state_book from .book import sel_state_book
sel_linetype = [
('cat', 'Category'),
('tr', 'Transfer'),
]
sel_target = [
('cashbook.book', 'Cashbook'),
('cashbook.category', 'Category'),
]
class SplitLine(ModelSQL, ModelView): class SplitLine(ModelSQL, ModelView):
'Split booking line' 'Split booking line'
__name__ = 'cashbook.split' __name__ = 'cashbook.split'
@ -23,9 +34,16 @@ class SplitLine(ModelSQL, ModelView):
readonly=True) readonly=True)
description = fields.Text(string='Description', description = fields.Text(string='Description',
states=STATES, depends=DEPENDS) states=STATES, depends=DEPENDS)
splittype = fields.Selection(string='Type', required=True,
help='Type of split booking line', selection=sel_linetype,
states=STATES, depends=DEPENDS)
category = fields.Many2One(string='Category', category = fields.Many2One(string='Category',
model_name='cashbook.category', ondelete='RESTRICT', model_name='cashbook.category', ondelete='RESTRICT',
states=STATES, depends=DEPENDS+['bookingtype'], required=True, states={
'readonly': STATES['readonly'],
'required': Eval('splittype', '') == 'cat',
'invisible': Eval('splittype', '') != 'cat',
}, depends=DEPENDS+['bookingtype', 'splittype'],
domain=[ domain=[
If( If(
Eval('bookingtype', '') == 'spin', Eval('bookingtype', '') == 'spin',
@ -34,9 +52,23 @@ class SplitLine(ModelSQL, ModelView):
)]) )])
category_view = fields.Function(fields.Char(string='Category', readonly=True), category_view = fields.Function(fields.Char(string='Category', readonly=True),
'on_change_with_category_view') 'on_change_with_category_view')
booktransf = fields.Many2One(string='Source/Dest',
ondelete='RESTRICT', model_name='cashbook.book',
domain=[
('owner.id', '=', Eval('owner_cashbook', -1)),
('id', '!=', Eval('cashbook', -1)),
],
states={
'readonly': STATES['readonly'],
'invisible': Eval('splittype', '') != 'tr',
'required': Eval('splittype', '') == 'tr',
}, depends=DEPENDS+['bookingtype', 'owner_cashbook', 'cashbook'])
amount = fields.Numeric(string='Amount', digits=(16, Eval('currency_digits', 2)), amount = fields.Numeric(string='Amount', digits=(16, Eval('currency_digits', 2)),
required=True, states=STATES, depends=DEPENDS+['currency_digits']) required=True, states=STATES, depends=DEPENDS+['currency_digits'])
target = fields.Function(fields.Reference(string='Target', readonly=True,
selection=sel_target), 'on_change_with_target')
currency = fields.Function(fields.Many2One(model_name='currency.currency', currency = fields.Function(fields.Many2One(model_name='currency.currency',
string="Currency", readonly=True), 'on_change_with_currency') string="Currency", readonly=True), 'on_change_with_currency')
currency_digits = fields.Function(fields.Integer(string='Currency Digits', currency_digits = fields.Function(fields.Integer(string='Currency Digits',
@ -45,18 +77,30 @@ class SplitLine(ModelSQL, ModelView):
selection=sel_bookingtype), 'on_change_with_bookingtype') selection=sel_bookingtype), 'on_change_with_bookingtype')
state = fields.Function(fields.Selection(string='State', readonly=True, state = fields.Function(fields.Selection(string='State', readonly=True,
selection=sel_linetype), 'on_change_with_state') selection=sel_linetype), 'on_change_with_state')
cashbook = fields.Function(fields.Many2One(string='Cashbook',
readonly=True, states={'invisible': True}, model_name='cashbook.book'),
'on_change_with_cashbook')
state_cashbook = fields.Function(fields.Selection(string='State of Cashbook', state_cashbook = fields.Function(fields.Selection(string='State of Cashbook',
readonly=True, states={'invisible': True}, selection=sel_state_book), readonly=True, states={'invisible': True}, selection=sel_state_book),
'on_change_with_state_cashbook') 'on_change_with_state_cashbook')
owner_cashbook = fields.Function(fields.Many2One(string='Owner', readonly=True,
states={'invisible': True}, model_name='res.user'),
'on_change_with_owner_cashbook')
@classmethod
def default_splittype(cls):
""" default category
"""
return 'cat'
def get_rec_name(self, name): def get_rec_name(self, name):
""" short + name """ short + name
""" """
return '%(type)s|%(amount)s %(symbol)s|%(desc)s [%(category)s]' % { return '%(type)s|%(amount)s %(symbol)s|%(desc)s [%(target)s]' % {
'desc': (self.description or '-')[:40], 'desc': (self.description or '-')[:40],
'amount': Report.format_number(self.amount, None), 'amount': Report.format_number(self.amount, None),
'symbol': getattr(self.currency, 'symbol', '-'), 'symbol': getattr(self.currency, 'symbol', '-'),
'category': self.category_view, 'target': self.category_view if self.splittype == 'cat' else self.booktransf.rec_name,
'type': gettext('cashbook.msg_line_bookingtype_%s' % self.line.bookingtype), 'type': gettext('cashbook.msg_line_bookingtype_%s' % self.line.bookingtype),
} }
@ -80,6 +124,28 @@ class SplitLine(ModelSQL, ModelView):
to_currency) to_currency)
return values return values
@fields.depends('splittype', 'category', 'booktransf')
def on_change_splittype(self):
""" clear category if not valid type
"""
if self.splittype:
if self.splittype == 'cat':
self.booktransf = None
if self.splittype == 'tr':
self.category = None
@fields.depends('splittype', 'category', 'booktransf')
def on_change_with_target(self, name=None):
""" get category or cashbook
"""
if self.splittype:
if self.splittype == 'cat':
if self.category:
return 'cashbook.category,%d' % self.category.id
elif self.splittype == 'tr':
if self.booktransf:
return 'cashbook.book,%d' % self.booktransf.id
@fields.depends('category') @fields.depends('category')
def on_change_with_category_view(self, name=None): def on_change_with_category_view(self, name=None):
""" show optimizef form of category for list-view """ show optimizef form of category for list-view
@ -101,6 +167,13 @@ class SplitLine(ModelSQL, ModelView):
if self.line: if self.line:
return self.line.state return self.line.state
@fields.depends('line', '_parent_line.cashbook')
def on_change_with_cashbook(self, name=None):
""" get cashbook
"""
if self.line:
return self.line.cashbook.id
@fields.depends('line', '_parent_line.cashbook') @fields.depends('line', '_parent_line.cashbook')
def on_change_with_state_cashbook(self, name=None): def on_change_with_state_cashbook(self, name=None):
""" get state of cashbook """ get state of cashbook
@ -108,6 +181,13 @@ class SplitLine(ModelSQL, ModelView):
if self.line: if self.line:
return self.line.cashbook.state return self.line.cashbook.state
@fields.depends('line', '_parent_line.cashbook')
def on_change_with_owner_cashbook(self, name=None):
""" get current owner
"""
if self.line:
return self.line.cashbook.owner.id
@fields.depends('line', '_parent_line.bookingtype') @fields.depends('line', '_parent_line.bookingtype')
def on_change_with_bookingtype(self, name=None): def on_change_with_bookingtype(self, name=None):
""" get type """ get type

View file

@ -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_config import ConfigTestCase
from trytond.modules.cashbook.tests.test_category import CategoryTestCase from trytond.modules.cashbook.tests.test_category import CategoryTestCase
from trytond.modules.cashbook.tests.test_reconciliation import ReconTestCase from trytond.modules.cashbook.tests.test_reconciliation import ReconTestCase
from trytond.modules.cashbook.tests.test_bookingwiz import BookingWizardTestCase
__all__ = ['suite'] __all__ = ['suite']
class CashbookTestCase(\ class CashbookTestCase(\
BookingWizardTestCase,\
ReconTestCase,\ ReconTestCase,\
CategoryTestCase,\ CategoryTestCase,\
ConfigTestCase,\ ConfigTestCase,\

View file

@ -51,10 +51,37 @@ class BookTestCase(ModuleTestCase):
'number_sequ': self.prep_sequence().id, 'number_sequ': self.prep_sequence().id,
}]) }])
self.assertEqual(book.name, 'Book 1') self.assertEqual(book.name, 'Book 1')
self.assertEqual(book.rec_name, 'Book 1 | 0.00 usd | Open')
self.assertEqual(book.btype.rec_name, 'CAS - Cash') self.assertEqual(book.btype.rec_name, 'CAS - Cash')
self.assertEqual(book.state, 'open') self.assertEqual(book.state, 'open')
self.assertEqual(book.state_string, 'Open') self.assertEqual(book.state_string, 'Open')
@with_transaction()
def test_book_create_hierarchy(self):
""" create cashbook, hierarchical
"""
pool = Pool()
Book = pool.get('cashbook.book')
types = self.prep_type()
company = self.prep_company()
book, = Book.create([{
'name': 'Level 1',
'btype': None,
'company': company.id,
'childs': [('create', [{
'name': 'Level 2',
'btype': types.id,
'company': company.id,
'currency': company.currency.id,
'number_sequ': self.prep_sequence().id,
}])],
}])
self.assertEqual(book.name, 'Level 1')
self.assertEqual(book.rec_name, 'Level 1')
self.assertEqual(len(book.childs), 1)
self.assertEqual(book.childs[0].rec_name, 'Level 1/Level 2 | 0.00 usd | Open')
@with_transaction() @with_transaction()
def test_book_deny_delete_open(self): def test_book_deny_delete_open(self):
""" create cashbook, add lines, try to delete in state 'open' """ create cashbook, add lines, try to delete in state 'open'
@ -90,6 +117,62 @@ class BookTestCase(ModuleTestCase):
Book.delete, Book.delete,
[book]) [book])
@with_transaction()
def test_book_deny_btype_set_none(self):
""" create cashbook, add lines,
try to set btype to None with lines
"""
pool = Pool()
Book = pool.get('cashbook.book')
types = self.prep_type()
category = self.prep_category(cattype='in')
company = self.prep_company()
party = self.prep_party()
book, = Book.create([{
'name': 'Book 1',
'btype': types.id,
'company': company.id,
'currency': company.currency.id,
'number_sequ': self.prep_sequence().id,
'start_date': date(2022, 5, 1),
'lines': [('create', [{
'date': date(2022, 5, 1),
'description': 'test 1',
'category': category.id,
'bookingtype': 'in',
'amount': Decimal('1.0'),
'party': party.id,
}])],
}])
self.assertEqual(book.name, 'Book 1')
self.assertEqual(book.btype.rec_name, 'CAS - Cash')
self.assertRaisesRegex(UserError,
"The type cannot be deleted on the cash book 'Book 1 | 1.00 usd | Open' because it still contains 1 lines.",
Book.write,
*[
[book],
{
'btype': None,
},
])
Book.write(*[
[book],
{
'lines': [('delete', [book.lines[0].id])],
}])
self.assertEqual(len(book.lines), 0)
self.assertEqual(book.btype.rec_name, 'CAS - Cash')
Book.write(*[
[book],
{
'btype': None,
}])
self.assertEqual(book.btype, None)
@with_transaction() @with_transaction()
def test_book_deny_delete_closed(self): def test_book_deny_delete_closed(self):
""" create cashbook, add lines, try to delete in state 'closed' """ create cashbook, add lines, try to delete in state 'closed'
@ -225,62 +308,6 @@ class BookTestCase(ModuleTestCase):
}, },
]) ])
@with_transaction()
def test_book_deny_update_start_amount(self):
""" create cashbook, add lines, update start-amount
"""
pool = Pool()
Book = pool.get('cashbook.book')
types = self.prep_type()
company = self.prep_company()
category = self.prep_category(cattype='in')
party = self.prep_party()
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')
self.assertEqual(book.start_balance, Decimal('0.0'))
self.assertEqual(book.rec_name, 'Book 1 | 0.00 usd | Open')
Book.write(*[
[book],
{
'start_balance': Decimal('1.0'),
}])
self.assertEqual(book.start_balance, Decimal('1.0'))
self.assertEqual(book.balance, Decimal('1.0'))
Book.write(*[
[book],
{
'lines': [('create', [{
'amount': Decimal('2.0'),
'description': 'Test',
'category': category.id,
'bookingtype': 'in',
'party': party.id,
}])],
}])
self.assertEqual(book.start_balance, Decimal('1.0'))
self.assertEqual(book.balance, Decimal('3.0'))
self.assertEqual(len(book.lines), 1)
self.assertEqual(book.lines[0].balance, Decimal('3.0'))
self.assertRaisesRegex(UserError,
"The initial amount of the cash book 'Fridas book | 3.00 usd | Open' cannot be changed because it already contains bookings.",
Book.write,
*[
[book],
{
'start_balance': Decimal('1.5'),
},
])
@with_transaction() @with_transaction()
def test_book_permission_owner(self): def test_book_permission_owner(self):
""" create book + 2x users, add users to group, check access """ create book + 2x users, add users to group, check access

176
tests/test_bookingwiz.py Normal file
View file

@ -0,0 +1,176 @@
# -*- 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),
}])
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),
}, {
'name': 'Bank',
'btype': types.id,
'company': company.id,
'currency': company.currency.id,
'number_sequ': self.prep_sequence().id,
'start_date': date(2022, 1, 1),
}])
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

View file

@ -28,6 +28,72 @@ class CategoryTestCase(ModuleTestCase):
}]) }])
return category return category
@with_transaction()
def test_category_check_rec_name(self):
""" create category, test rec_name, search, order
"""
pool = Pool()
Category = pool.get('cashbook.category')
company = self.prep_company()
Category.create([{
'company': company.id,
'name': 'Level 1',
'cattype': 'in',
'childs': [('create', [{
'company': company.id,
'name': 'Level 2a',
'cattype': 'in',
}, {
'company': company.id,
'name': 'Level 2b',
'cattype': 'in',
}])],
}, {
'company': company.id,
'name': 'Level 1b',
'cattype': 'in',
'childs': [('create', [{
'company': company.id,
'name': 'Level 1b.2a',
'cattype': 'in',
}, {
'company': company.id,
'name': 'Level 1b.2b',
'cattype': 'in',
}])],
}])
self.assertEqual(Category.search_count([
('rec_name', 'ilike', '%1b.2b%'),
]), 1)
self.assertEqual(Category.search_count([
('rec_name', 'ilike', '%1b.2%'),
]), 2)
self.assertEqual(Category.search_count([
('rec_name', '=', 'Level 1b/Level 1b.2b'),
]), 1)
# ordering #1
categories = Category.search([], order=[('rec_name', 'ASC')])
self.assertEqual(len(categories), 6)
self.assertEqual(categories[0].rec_name, 'Level 1')
self.assertEqual(categories[1].rec_name, 'Level 1b')
self.assertEqual(categories[2].rec_name, 'Level 1b/Level 1b.2a')
self.assertEqual(categories[3].rec_name, 'Level 1b/Level 1b.2b')
self.assertEqual(categories[4].rec_name, 'Level 1/Level 2a')
self.assertEqual(categories[5].rec_name, 'Level 1/Level 2b')
# ordering #2
categories = Category.search([], order=[('rec_name', 'DESC')])
self.assertEqual(len(categories), 6)
self.assertEqual(categories[0].rec_name, 'Level 1/Level 2b')
self.assertEqual(categories[1].rec_name, 'Level 1/Level 2a')
self.assertEqual(categories[2].rec_name, 'Level 1b/Level 1b.2b')
self.assertEqual(categories[3].rec_name, 'Level 1b/Level 1b.2a')
self.assertEqual(categories[4].rec_name, 'Level 1b')
self.assertEqual(categories[5].rec_name, 'Level 1')
@with_transaction() @with_transaction()
def test_category_create_check_category_type(self): def test_category_create_check_category_type(self):
""" create category, update type of category """ create category, update type of category
@ -90,6 +156,7 @@ class CategoryTestCase(ModuleTestCase):
cat1, = Category.create([{ cat1, = Category.create([{
'name': 'Test 1', 'name': 'Test 1',
'description': 'Info', 'description': 'Info',
'cattype': 'in',
}]) }])
self.assertEqual(cat1.name, 'Test 1') self.assertEqual(cat1.name, 'Test 1')
self.assertEqual(cat1.rec_name, 'Test 1') self.assertEqual(cat1.rec_name, 'Test 1')
@ -97,10 +164,11 @@ class CategoryTestCase(ModuleTestCase):
self.assertEqual(cat1.company.rec_name, 'm-ds') self.assertEqual(cat1.company.rec_name, 'm-ds')
self.assertEqual(cat1.parent, None) self.assertEqual(cat1.parent, None)
# duplicate, allowed # duplicate of different type, allowed
cat2, = Category.create([{ cat2, = Category.create([{
'name': 'Test 1', 'name': 'Test 1',
'description': 'Info', 'description': 'Info',
'cattype': 'out',
}]) }])
self.assertEqual(cat2.name, 'Test 1') self.assertEqual(cat2.name, 'Test 1')
self.assertEqual(cat2.rec_name, 'Test 1') self.assertEqual(cat2.rec_name, 'Test 1')
@ -108,6 +176,16 @@ class CategoryTestCase(ModuleTestCase):
self.assertEqual(cat2.company.rec_name, 'm-ds') self.assertEqual(cat2.company.rec_name, 'm-ds')
self.assertEqual(cat2.parent, None) self.assertEqual(cat2.parent, None)
# deny duplicate of same type
self.assertRaisesRegex(UserError,
'The category name already exists at this level.',
Category.create,
[{
'name': 'Test 1',
'description': 'Info',
'cattype': 'in',
}])
@with_transaction() @with_transaction()
def test_category_create_nodupl_diff_level(self): def test_category_create_nodupl_diff_level(self):
""" create category """ create category

View file

@ -42,6 +42,7 @@ class ConfigTestCase(ModuleTestCase):
self.assertEqual(cfg2.checked, True) self.assertEqual(cfg2.checked, True)
self.assertEqual(cfg2.done, False) self.assertEqual(cfg2.done, False)
self.assertEqual(cfg2.catnamelong, True) self.assertEqual(cfg2.catnamelong, True)
self.assertEqual(cfg2.defbook, None)
return cfg2 return cfg2
def prep_party(self, name='Party'): def prep_party(self, name='Party'):
@ -105,6 +106,37 @@ class ConfigTestCase(ModuleTestCase):
""" """
self.prep_config() 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() @with_transaction()
def test_config_create_multi_user(self): def test_config_create_multi_user(self):
""" create config, multi-user """ create config, multi-user

View file

@ -366,6 +366,93 @@ class LineTestCase(ModuleTestCase):
self.assertEqual(book.lines[3].reconciliation, None) self.assertEqual(book.lines[3].reconciliation, None)
self.assertEqual(book.lines[3].state, 'edit') self.assertEqual(book.lines[3].state, 'edit')
@with_transaction()
def test_line_set_number_with_done(self):
""" create cashbook + line, write number to line
at state-change check->done
"""
pool = Pool()
Book = pool.get('cashbook.book')
Lines = pool.get('cashbook.line')
Reconciliation = pool.get('cashbook.recon')
types = self.prep_type()
category = self.prep_category(cattype='in')
company = self.prep_company()
party = self.prep_party()
with Transaction().set_context({
'company': company.id,
}):
book, = Book.create([{
'name': 'Book 1',
'btype': types.id,
'company': company.id,
'currency': company.currency.id,
'number_sequ': self.prep_sequence().id,
'start_date': date(2022, 5, 1),
'number_atcheck': False,
'lines': [('create', [{
'date': date(2022, 5, 1),
'description': 'Text 1',
'category': category.id,
'bookingtype': 'in',
'amount': Decimal('1.0'),
'party': party.id,
}, {
'date': date(2022, 5, 2),
'description': 'Text 2',
'category': category.id,
'bookingtype': 'in',
'amount': Decimal('1.0'),
'party': party.id,
}])],
}])
self.assertEqual(book.name, 'Book 1')
self.assertEqual(book.btype.rec_name, 'CAS - Cash')
self.assertEqual(book.state, 'open')
self.assertEqual(book.number_atcheck, False)
self.assertEqual(len(book.lines), 2)
self.assertEqual(book.lines[0].date, date(2022, 5, 1))
self.assertEqual(book.lines[0].rec_name, '05/01/2022|Rev|1.00 usd|Text 1 [Cat1]')
self.assertEqual(book.lines[0].state_cashbook, 'open')
self.assertEqual(book.lines[1].date, date(2022, 5, 2))
self.assertEqual(book.lines[1].rec_name, '05/02/2022|Rev|1.00 usd|Text 2 [Cat1]')
# add reconciliation
Book.write(*[
[book],
{
'reconciliations': [('create', [{
'date': date(2022, 5, 1),
'date_from': date(2022, 5, 1),
'date_to': date(2022, 5, 30),
}])],
}])
self.assertEqual(len(book.reconciliations), 1)
self.assertEqual(len(book.reconciliations[0].lines), 0)
self.assertEqual(book.reconciliations[0].date_from, date(2022, 5, 1))
self.assertEqual(book.reconciliations[0].date_to, date(2022, 5, 30))
self.assertEqual(book.reconciliations[0].state, 'edit')
Lines.wfcheck(book.lines)
self.assertEqual(book.lines[0].state, 'check')
self.assertEqual(book.lines[0].number, None)
self.assertEqual(book.lines[1].state, 'check')
self.assertEqual(book.lines[1].number, None)
Reconciliation.wfcheck(book.reconciliations)
self.assertEqual(len(book.reconciliations[0].lines), 2)
self.assertEqual(book.reconciliations[0].lines[0].rec_name, '05/01/2022|Rev|1.00 usd|Text 1 [Cat1]')
self.assertEqual(book.reconciliations[0].lines[1].rec_name, '05/02/2022|Rev|1.00 usd|Text 2 [Cat1]')
self.assertEqual(book.reconciliations[0].lines[0].number, None)
self.assertEqual(book.reconciliations[0].lines[1].number, None)
Reconciliation.wfdone(book.reconciliations)
self.assertEqual(book.reconciliations[0].lines[0].number, '1')
self.assertEqual(book.reconciliations[0].lines[0].state, 'done')
self.assertEqual(book.reconciliations[0].lines[1].number, '2')
self.assertEqual(book.reconciliations[0].lines[1].state, 'done')
@with_transaction() @with_transaction()
def test_line_create_check_names_search(self): def test_line_create_check_names_search(self):
""" create cashbook + line """ create cashbook + line
@ -373,10 +460,18 @@ class LineTestCase(ModuleTestCase):
pool = Pool() pool = Pool()
Book = pool.get('cashbook.book') Book = pool.get('cashbook.book')
Lines = pool.get('cashbook.line') Lines = pool.get('cashbook.line')
Category = pool.get('cashbook.category')
types = self.prep_type() types = self.prep_type()
category = self.prep_category(cattype='in')
company = self.prep_company() company = self.prep_company()
category = self.prep_category(cattype='in')
category2, = Category.create([{
'name': 'sp-cat1',
'cattype': 'in',
'company': company.id,
}])
self.assertEqual(category2.rec_name, 'sp-cat1')
party = self.prep_party() party = self.prep_party()
book, = Book.create([{ book, = Book.create([{
'name': 'Book 1', 'name': 'Book 1',
@ -399,33 +494,60 @@ class LineTestCase(ModuleTestCase):
'bookingtype': 'in', 'bookingtype': 'in',
'amount': Decimal('1.0'), 'amount': Decimal('1.0'),
'party': party.id, 'party': party.id,
}, {
'date': date(2022, 5, 3),
'description': 'Text 3',
'bookingtype': 'spin',
'amount': Decimal('1.0'),
'party': party.id,
'splitlines': [('create', [{
'amount': Decimal('1.0'),
'description': 'text3-spline1',
'category': category2.id,
}])],
}])], }])],
}]) }])
self.assertEqual(book.name, 'Book 1') self.assertEqual(book.name, 'Book 1')
self.assertEqual(book.btype.rec_name, 'CAS - Cash') self.assertEqual(book.btype.rec_name, 'CAS - Cash')
self.assertEqual(book.state, 'open') self.assertEqual(book.state, 'open')
self.assertEqual(len(book.lines), 2) self.assertEqual(len(book.lines), 3)
self.assertEqual(book.lines[0].date, date(2022, 5, 1)) self.assertEqual(book.lines[0].date, date(2022, 5, 1))
self.assertEqual(book.lines[0].rec_name, '05/01/2022|Rev|1.00 usd|Text 1 [Cat1]') self.assertEqual(book.lines[0].rec_name, '05/01/2022|Rev|1.00 usd|Text 1 [Cat1]')
self.assertEqual(book.lines[0].state_cashbook, 'open') self.assertEqual(book.lines[0].state_cashbook, 'open')
self.assertEqual(book.lines[1].date, date(2022, 5, 2)) self.assertEqual(book.lines[1].date, date(2022, 5, 2))
self.assertEqual(book.lines[1].rec_name, '05/02/2022|Rev|1.00 usd|Text 2 [Cat1]') self.assertEqual(book.lines[1].rec_name, '05/02/2022|Rev|1.00 usd|Text 2 [Cat1]')
self.assertEqual(book.lines[2].date, date(2022, 5, 3))
self.assertEqual(book.lines[2].rec_name, '05/03/2022|Rev/Sp|1.00 usd|Text 3 [-]')
self.assertEqual(Lines.search_count([('rec_name', '=', 'Text 1')]), 1) self.assertEqual(Lines.search_count([('rec_name', '=', 'Text 1')]), 1)
self.assertEqual(Lines.search_count([('rec_name', '=', 'Text 1a')]), 0) self.assertEqual(Lines.search_count([('rec_name', '=', 'Text 1a')]), 0)
self.assertEqual(Lines.search_count([('rec_name', 'ilike', 'text%')]), 2) self.assertEqual(Lines.search_count([('rec_name', 'ilike', 'text%')]), 3)
# search in category of split-line
self.assertEqual(Lines.search_count([('rec_name', '=', 'sp-cat1')]), 1)
# search in description of split-line
self.assertEqual(Lines.search_count([('rec_name', '=', 'text3-spline1')]), 1)
# ilike fails in fields.Text to find subtext...
self.assertEqual(Lines.search_count([('rec_name', 'ilike', '%spline%')]), 0)
# ...but it uses separator-chars
self.assertEqual(Lines.search_count([('rec_name', 'ilike', 'text3%')]), 1)
self.assertEqual(Lines.search_count([('rec_name', 'ilike', 'spline1')]), 1)
self.assertEqual(Lines.search_count([('rec_name', 'ilike', '%spline1')]), 1)
self.assertEqual(Lines.search_count([('rec_name', 'ilike', 'spline1%')]), 0)
self.assertEqual(Lines.search_count([('rec_name', 'ilike', 'text3')]), 1)
self.assertEqual(Lines.search_count([('state_cashbook', '=', 'open')]), 2) self.assertEqual(Lines.search_count([('state_cashbook', '=', 'open')]), 3)
self.assertEqual(Lines.search_count([('state_cashbook', '=', 'closed')]), 0) self.assertEqual(Lines.search_count([('state_cashbook', '=', 'closed')]), 0)
self.assertEqual(Lines.search_count([('cashbook.state', '=', 'open')]), 2) self.assertEqual(Lines.search_count([('cashbook.state', '=', 'open')]), 3)
self.assertEqual(Lines.search_count([('cashbook.state', '=', 'closed')]), 0) self.assertEqual(Lines.search_count([('cashbook.state', '=', 'closed')]), 0)
# sorting: date -> state -> id # sorting: date -> state -> id
self.assertEqual(len(book.lines), 2) self.assertEqual(len(book.lines), 3)
self.assertEqual(book.lines[0].rec_name, '05/01/2022|Rev|1.00 usd|Text 1 [Cat1]') self.assertEqual(book.lines[0].rec_name, '05/01/2022|Rev|1.00 usd|Text 1 [Cat1]')
self.assertEqual(book.lines[0].state, 'edit') self.assertEqual(book.lines[0].state, 'edit')
self.assertEqual(book.lines[1].rec_name, '05/02/2022|Rev|1.00 usd|Text 2 [Cat1]') self.assertEqual(book.lines[1].rec_name, '05/02/2022|Rev|1.00 usd|Text 2 [Cat1]')
self.assertEqual(book.lines[1].state, 'edit') self.assertEqual(book.lines[1].state, 'edit')
self.assertEqual(book.lines[2].rec_name, '05/03/2022|Rev/Sp|1.00 usd|Text 3 [-]')
self.assertEqual(book.lines[2].state, 'edit')
# set to same date # set to same date
Lines.write(*[ Lines.write(*[
@ -439,6 +561,8 @@ class LineTestCase(ModuleTestCase):
self.assertEqual(book.lines[0].state, 'edit') self.assertEqual(book.lines[0].state, 'edit')
self.assertEqual(book.lines[1].rec_name, '05/01/2022|Rev|1.00 usd|Text 2 [Cat1]') self.assertEqual(book.lines[1].rec_name, '05/01/2022|Rev|1.00 usd|Text 2 [Cat1]')
self.assertEqual(book.lines[1].state, 'edit') self.assertEqual(book.lines[1].state, 'edit')
self.assertEqual(book.lines[2].rec_name, '05/01/2022|Rev/Sp|1.00 usd|Text 3 [-]')
self.assertEqual(book.lines[2].state, 'edit')
# set to 'check', will sort first # set to 'check', will sort first
Lines.wfcheck([book.lines[1]]) Lines.wfcheck([book.lines[1]])
@ -447,6 +571,38 @@ class LineTestCase(ModuleTestCase):
self.assertEqual(book.lines[0].state, 'check') self.assertEqual(book.lines[0].state, 'check')
self.assertEqual(book.lines[1].rec_name, '05/01/2022|Rev|1.00 usd|Text 1 [Cat1]') self.assertEqual(book.lines[1].rec_name, '05/01/2022|Rev|1.00 usd|Text 1 [Cat1]')
self.assertEqual(book.lines[1].state, 'edit') self.assertEqual(book.lines[1].state, 'edit')
self.assertEqual(book.lines[2].rec_name, '05/01/2022|Rev/Sp|1.00 usd|Text 3 [-]')
self.assertEqual(book.lines[2].state, 'edit')
@with_transaction()
def test_line_to_non_type_book(self):
""" create cashbook w/o type
"""
pool = Pool()
Book = pool.get('cashbook.book')
Line = pool.get('cashbook.line')
category = self.prep_category(cattype='in')
company = self.prep_company()
party = self.prep_party()
book, = Book.create([{
'name': 'Book 1',
'btype': None,
'company': company.id,
}])
self.assertEqual(book.name, 'Book 1')
self.assertEqual(book.state, 'open')
self.assertRaisesRegex(UserError,
'The value for field "Cashbook" in "Cashbook Line" is not valid according to its domain.',
Line.create,
[{
'cashbook': book.id,
'date': date(2022, 5, 1),
'category': category.id,
'bookingtype': 'in',
'amount': Decimal('0.0'),
}])
@with_transaction() @with_transaction()
def test_line_create_check_deny_write(self): def test_line_create_check_deny_write(self):
@ -783,11 +939,25 @@ class LineTestCase(ModuleTestCase):
'bookingtype': 'mvout', 'bookingtype': 'mvout',
'amount': Decimal('1.0'), 'amount': Decimal('1.0'),
'booktransf': book2.id, 'booktransf': book2.id,
}, {
'date': date(2022, 6, 1), # in-category, return
'description': 'in-return', # amount negative
'category': category_in.id,
'bookingtype': 'in',
'amount': Decimal('-1.0'),
'party': party.id,
}, {
'date': date(2022, 6, 1), # out-category, return
'description': 'out-return', # amount negative
'category': category_out.id,
'bookingtype': 'out',
'amount': Decimal('-1.0'),
'party': party.id,
}])], }])],
}]) }])
self.assertEqual(book.name, 'Book 1') self.assertEqual(book.name, 'Book 1')
self.assertEqual(book.state, 'open') self.assertEqual(book.state, 'open')
self.assertEqual(len(book.lines), 4) self.assertEqual(len(book.lines), 6)
self.assertEqual(book.lines[0].amount, Decimal('1.0')) self.assertEqual(book.lines[0].amount, Decimal('1.0'))
self.assertEqual(book.lines[0].bookingtype, 'in') self.assertEqual(book.lines[0].bookingtype, 'in')
@ -796,7 +966,7 @@ class LineTestCase(ModuleTestCase):
# check payee # check payee
self.assertEqual(book.lines[0].payee.rec_name, 'Party') self.assertEqual(book.lines[0].payee.rec_name, 'Party')
self.assertEqual(Line.search_count([('payee', 'ilike', 'party%')]), 2) self.assertEqual(Line.search_count([('payee', 'ilike', 'party%')]), 4)
self.assertEqual(Line.search_count([('payee', 'ilike', 'book%')]), 2) self.assertEqual(Line.search_count([('payee', 'ilike', 'book%')]), 2)
self.assertEqual(book.lines[1].amount, Decimal('1.0')) self.assertEqual(book.lines[1].amount, Decimal('1.0'))
@ -814,6 +984,16 @@ class LineTestCase(ModuleTestCase):
self.assertEqual(book.lines[3].credit, Decimal('0.0')) self.assertEqual(book.lines[3].credit, Decimal('0.0'))
self.assertEqual(book.lines[3].debit, Decimal('1.0')) self.assertEqual(book.lines[3].debit, Decimal('1.0'))
self.assertEqual(book.lines[4].amount, Decimal('-1.0'))
self.assertEqual(book.lines[4].bookingtype, 'in')
self.assertEqual(book.lines[4].credit, Decimal('-1.0'))
self.assertEqual(book.lines[4].debit, Decimal('0.0'))
self.assertEqual(book.lines[5].amount, Decimal('-1.0'))
self.assertEqual(book.lines[5].bookingtype, 'out')
self.assertEqual(book.lines[5].credit, Decimal('0.0'))
self.assertEqual(book.lines[5].debit, Decimal('-1.0'))
Line.write(*[ Line.write(*[
[book.lines[0]], [book.lines[0]],
{ {

View file

@ -49,14 +49,14 @@ class ReconTestCase(ModuleTestCase):
'date_to': date(2022, 6, 30), 'date_to': date(2022, 6, 30),
}])], }])],
}]) }])
self.assertEqual(book.reconciliations[0].rec_name, '05/01/2022 - 05/31/2022 | 0.00 usd - 0.00 usd [0]') self.assertEqual(book.reconciliations[0].rec_name, '05/31/2022 - 06/30/2022 | 0.00 usd - 0.00 usd [0]')
self.assertEqual(book.reconciliations[1].rec_name, '05/31/2022 - 06/30/2022 | 0.00 usd - 0.00 usd [0]') self.assertEqual(book.reconciliations[1].rec_name, '05/01/2022 - 05/31/2022 | 0.00 usd - 0.00 usd [0]')
self.assertRaisesRegex(UserError, self.assertRaisesRegex(UserError,
'The date range overlaps with another reconciliation.', 'The date range overlaps with another reconciliation.',
Reconciliation.write, Reconciliation.write,
*[ *[
[book.reconciliations[1]], [book.reconciliations[0]],
{ {
'date_from': date(2022, 4, 15), 'date_from': date(2022, 4, 15),
'date_to': date(2022, 5, 2), 'date_to': date(2022, 5, 2),
@ -97,14 +97,14 @@ class ReconTestCase(ModuleTestCase):
'date_to': date(2022, 6, 30), 'date_to': date(2022, 6, 30),
}])], }])],
}]) }])
self.assertEqual(book.reconciliations[0].rec_name, '05/01/2022 - 05/31/2022 | 0.00 usd - 0.00 usd [0]') self.assertEqual(book.reconciliations[0].rec_name, '05/31/2022 - 06/30/2022 | 0.00 usd - 0.00 usd [0]')
self.assertEqual(book.reconciliations[1].rec_name, '05/31/2022 - 06/30/2022 | 0.00 usd - 0.00 usd [0]') self.assertEqual(book.reconciliations[1].rec_name, '05/01/2022 - 05/31/2022 | 0.00 usd - 0.00 usd [0]')
self.assertRaisesRegex(UserError, self.assertRaisesRegex(UserError,
'The date range overlaps with another reconciliation.', 'The date range overlaps with another reconciliation.',
Reconciliation.write, Reconciliation.write,
*[ *[
[book.reconciliations[1]], [book.reconciliations[0]],
{ {
'date_from': date(2022, 5, 30), 'date_from': date(2022, 5, 30),
}, },
@ -144,14 +144,14 @@ class ReconTestCase(ModuleTestCase):
'date_to': date(2022, 6, 30), 'date_to': date(2022, 6, 30),
}])], }])],
}]) }])
self.assertEqual(book.reconciliations[0].rec_name, '05/01/2022 - 05/31/2022 | 0.00 usd - 0.00 usd [0]') self.assertEqual(book.reconciliations[0].rec_name, '05/31/2022 - 06/30/2022 | 0.00 usd - 0.00 usd [0]')
self.assertEqual(book.reconciliations[1].rec_name, '05/31/2022 - 06/30/2022 | 0.00 usd - 0.00 usd [0]') self.assertEqual(book.reconciliations[1].rec_name, '05/01/2022 - 05/31/2022 | 0.00 usd - 0.00 usd [0]')
self.assertRaisesRegex(UserError, self.assertRaisesRegex(UserError,
'The date range overlaps with another reconciliation.', 'The date range overlaps with another reconciliation.',
Reconciliation.write, Reconciliation.write,
*[ *[
[book.reconciliations[1]], [book.reconciliations[0]],
{ {
'date_from': date(2022, 5, 5), 'date_from': date(2022, 5, 5),
'date_to': date(2022, 5, 15), 'date_to': date(2022, 5, 15),
@ -192,8 +192,8 @@ class ReconTestCase(ModuleTestCase):
'date_to': date(2022, 6, 30), 'date_to': date(2022, 6, 30),
}])], }])],
}]) }])
self.assertEqual(book.reconciliations[0].rec_name, '05/01/2022 - 05/31/2022 | 0.00 usd - 0.00 usd [0]') self.assertEqual(book.reconciliations[0].rec_name, '05/31/2022 - 06/30/2022 | 0.00 usd - 0.00 usd [0]')
self.assertEqual(book.reconciliations[1].rec_name, '05/31/2022 - 06/30/2022 | 0.00 usd - 0.00 usd [0]') self.assertEqual(book.reconciliations[1].rec_name, '05/01/2022 - 05/31/2022 | 0.00 usd - 0.00 usd [0]')
self.assertRaisesRegex(UserError, self.assertRaisesRegex(UserError,
'The date range overlaps with another reconciliation.', 'The date range overlaps with another reconciliation.',
@ -221,7 +221,6 @@ class ReconTestCase(ModuleTestCase):
'btype': types.id, 'btype': types.id,
'company': company.id, 'company': company.id,
'currency': company.currency.id, 'currency': company.currency.id,
'start_balance': Decimal('12.50'),
'start_date': date(2022, 5, 1), 'start_date': date(2022, 5, 1),
'number_sequ': self.prep_sequence().id, 'number_sequ': self.prep_sequence().id,
'reconciliations': [('create', [{ 'reconciliations': [('create', [{
@ -234,7 +233,7 @@ class ReconTestCase(ModuleTestCase):
self.assertEqual(book.reconciliations[0].rec_name, '05/01/2022 - 05/31/2022 | 0.00 usd - 0.00 usd [0]') self.assertEqual(book.reconciliations[0].rec_name, '05/01/2022 - 05/31/2022 | 0.00 usd - 0.00 usd [0]')
Reconciliation.wfcheck(list(book.reconciliations)) Reconciliation.wfcheck(list(book.reconciliations))
self.assertEqual(book.reconciliations[0].rec_name, '05/01/2022 - 05/31/2022 | 12.50 usd - 12.50 usd [0]') self.assertEqual(book.reconciliations[0].rec_name, '05/01/2022 - 05/31/2022 | 0.00 usd - 0.00 usd [0]')
@with_transaction() @with_transaction()
def test_recon_set_start_amount_by_predecessor(self): def test_recon_set_start_amount_by_predecessor(self):
@ -254,7 +253,6 @@ class ReconTestCase(ModuleTestCase):
'btype': types.id, 'btype': types.id,
'company': company.id, 'company': company.id,
'currency': company.currency.id, 'currency': company.currency.id,
'start_balance': Decimal('12.50'),
'start_date': date(2022, 5, 1), 'start_date': date(2022, 5, 1),
'number_sequ': self.prep_sequence().id, 'number_sequ': self.prep_sequence().id,
'reconciliations': [('create', [{ 'reconciliations': [('create', [{
@ -287,7 +285,7 @@ class ReconTestCase(ModuleTestCase):
Reconciliation.wfcheck(list(book.reconciliations)) Reconciliation.wfcheck(list(book.reconciliations))
self.assertEqual(book.reconciliations[0].state, 'check') self.assertEqual(book.reconciliations[0].state, 'check')
self.assertEqual(book.reconciliations[0].rec_name, '05/01/2022 - 05/31/2022 | 12.50 usd - 24.50 usd [2]') self.assertEqual(book.reconciliations[0].rec_name, '05/01/2022 - 05/31/2022 | 0.00 usd - 12.00 usd [2]')
Reconciliation.wfdone(list(book.reconciliations)) Reconciliation.wfdone(list(book.reconciliations))
self.assertEqual(book.reconciliations[0].state, 'done') self.assertEqual(book.reconciliations[0].state, 'done')
@ -298,7 +296,7 @@ class ReconTestCase(ModuleTestCase):
}]) }])
self.assertEqual(recons[0].rec_name, '05/31/2022 - 06/30/2022 | 0.00 usd - 0.00 usd [0]') self.assertEqual(recons[0].rec_name, '05/31/2022 - 06/30/2022 | 0.00 usd - 0.00 usd [0]')
Reconciliation.wfcheck(recons) Reconciliation.wfcheck(recons)
self.assertEqual(recons[0].rec_name, '05/31/2022 - 06/30/2022 | 24.50 usd - 24.50 usd [0]') self.assertEqual(recons[0].rec_name, '05/31/2022 - 06/30/2022 | 12.00 usd - 12.00 usd [0]')
@with_transaction() @with_transaction()
def test_recon_predecessor_done(self): def test_recon_predecessor_done(self):
@ -536,9 +534,11 @@ class ReconTestCase(ModuleTestCase):
self.assertEqual(len(book.reconciliations[0].lines), 1) self.assertEqual(len(book.reconciliations[0].lines), 1)
self.assertEqual(book.reconciliations[0].lines[0].rec_name, '05/01/2022|Rev|1.00 usd|Text 1 [Cat1]') self.assertEqual(book.reconciliations[0].lines[0].rec_name, '05/01/2022|Rev|1.00 usd|Text 1 [Cat1]')
self.assertEqual(book.lines[0].rec_name, '05/01/2022|Rev|1.00 usd|Text 1 [Cat1]') self.assertEqual(book.lines[0].rec_name, '05/01/2022|Rev|1.00 usd|Text 1 [Cat1]')
self.assertEqual(book.lines[0].state, 'check')
self.assertEqual(book.lines[1].rec_name, '06/01/2022|Rev|1.00 usd|Text 2 [Cat1]') self.assertEqual(book.lines[1].rec_name, '06/01/2022|Rev|1.00 usd|Text 2 [Cat1]')
self.assertEqual(book.lines[1].state, 'edit')
# move 2nd line into date-range of checked-reconciliation, wf-check # move 1st line into date-range of checked-reconciliation, wf-check
Lines.write(*[ Lines.write(*[
[book.lines[1]], [book.lines[1]],
{ {
@ -570,7 +570,9 @@ class ReconTestCase(ModuleTestCase):
'date_from': date(2022, 5, 31), 'date_from': date(2022, 5, 31),
'date_to': date(2022, 6, 30), 'date_to': date(2022, 6, 30),
}]) }])
Reconciliation.wfdone([book.reconciliations[0]]) self.assertEqual(book.reconciliations[0].rec_name, '05/31/2022 - 06/30/2022 | 0.00 usd - 0.00 usd [0]')
self.assertEqual(book.reconciliations[1].rec_name, '05/01/2022 - 05/31/2022 | 0.00 usd - 1.00 usd [1]')
Reconciliation.wfdone([book.reconciliations[1]])
Reconciliation.wfcheck([recon2]) Reconciliation.wfcheck([recon2])
Lines.write(*[ Lines.write(*[

View file

@ -16,6 +16,96 @@ class SplitLineTestCase(ModuleTestCase):
'Test split line module' 'Test split line module'
module = 'cashbook' module = 'cashbook'
@with_transaction()
def test_splitline_category_and_transfer(self):
""" add book, line, two split-lines,
category + transfer
"""
pool = Pool()
Book = pool.get('cashbook.book')
Line = pool.get('cashbook.line')
types = self.prep_type()
category1 = self.prep_category(cattype='in')
company = self.prep_company()
party = self.prep_party()
books = Book.create([{
'name': 'Book 1',
'btype': types.id,
'company': company.id,
'currency': company.currency.id,
'number_sequ': self.prep_sequence().id,
'start_date': date(2022, 5, 1),
'lines': [('create', [{
'date': date(2022, 5, 1),
'description': 'Text 1',
'category': category1.id,
'bookingtype': 'in',
'amount': Decimal('1.0'),
'party': party.id,
}])],
}, {
'name': 'Book 2',
'btype': types.id,
'company': company.id,
'currency': company.currency.id,
'number_sequ': self.prep_sequence().id,
'start_date': date(2022, 5, 1),
}])
self.assertEqual(books[0].rec_name, 'Book 1 | 1.00 usd | Open')
self.assertEqual(len(books[0].lines), 1)
self.assertEqual(books[0].lines[0].rec_name, '05/01/2022|Rev|1.00 usd|Text 1 [Cat1]')
self.assertEqual(books[1].rec_name, 'Book 2 | 0.00 usd | Open')
Book.write(*[
[books[0]],
{
'lines': [('write', [books[0].lines[0]], {
'bookingtype': 'spin',
'splitlines': [('create', [{
'amount': Decimal('5.0'),
'splittype': 'cat',
'description': 'from category',
'category': category1.id,
}, {
'amount': Decimal('6.0'),
'splittype': 'tr',
'description': 'from cashbook',
'booktransf': books[1].id,
}])],
})]
}])
self.assertEqual(len(books[0].lines), 1)
self.assertEqual(books[0].lines[0].rec_name, '05/01/2022|Rev/Sp|11.00 usd|Text 1 [-]')
self.assertEqual(books[0].lines[0].category, None)
self.assertEqual(len(books[0].lines[0].splitlines), 2)
self.assertEqual(books[0].lines[0].splitlines[0].rec_name,
'Rev/Sp|5.00 usd|from category [Cat1]')
self.assertEqual(books[0].lines[0].splitlines[1].rec_name,
'Rev/Sp|6.00 usd|from cashbook [Book 2 | 0.00 usd | Open]')
self.assertEqual(len(books[1].lines), 0)
# wf: edit -> check
Line.wfcheck(books[0].lines)
self.assertEqual(len(books[0].lines), 1)
self.assertEqual(books[0].lines[0].state, 'check')
self.assertEqual(books[0].lines[0].number, '1')
self.assertEqual(len(books[0].lines[0].references), 1)
self.assertEqual(books[0].lines[0].references[0].rec_name,
'05/01/2022|to|-6.00 usd|from cashbook [Book 1 | 11.00 usd | Open]')
self.assertEqual(len(books[1].lines), 1)
self.assertEqual(books[1].lines[0].reference.rec_name,
'05/01/2022|Rev/Sp|11.00 usd|Text 1 [-]')
self.assertEqual(books[1].lines[0].rec_name,
'05/01/2022|to|-6.00 usd|from cashbook [Book 1 | 11.00 usd | Open]')
# wf: check --> edit
Line.wfedit(books[0].lines)
self.assertEqual(len(books[0].lines), 1)
self.assertEqual(len(books[0].lines[0].references), 0)
self.assertEqual(len(books[1].lines), 0)
@with_transaction() @with_transaction()
def test_splitline_check_clear_by_bookingtype(self): def test_splitline_check_clear_by_bookingtype(self):
""" add book, line, category, set line to 'in', """ add book, line, category, set line to 'in',

View file

@ -1,5 +1,5 @@
[tryton] [tryton]
version=6.0.0 version=6.0.12
depends: depends:
res res
currency currency
@ -19,4 +19,5 @@ xml:
splitline.xml splitline.xml
wizard_openline.xml wizard_openline.xml
wizard_runreport.xml wizard_runreport.xml
wizard_booking.xml
menu.xml menu.xml

View file

@ -2,10 +2,10 @@
<!-- This file is part of the cashbook-module from m-ds for Tryton. <!-- 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 The COPYRIGHT file at the top level of this repository contains the
full copyright notices and license terms. --> full copyright notices and license terms. -->
<form col="6"> <form col="4">
<label name="name"/> <label name="name" xexpand="0"/>
<field name="name" colspan="3"/> <field name="name" colspan="2"/>
<group id="grpst" colspan="2" col="3"> <group id="grpst" col="3">
<label name="state"/> <label name="state"/>
<field name="state"/> <field name="state"/>
<group id="grpstate" col="3"> <group id="grpstate" col="3">
@ -15,32 +15,37 @@ full copyright notices and license terms. -->
</group> </group>
</group> </group>
<label name="btype"/>
<field name="btype"/>
<label name="balance"/>
<field name="balance"/>
<notebook colspan="4">
<page name="reconciliations" string="Reconciliations" col="1">
<field name="reconciliations"/>
</page>
<page name="description" string="Description" col="1">
<field name="description"/>
</page>
<page id="pggeneral" string="General Information" col="4">
<label name="company"/>
<field name="company"/>
<label name="parent"/>
<field name="parent"/>
<field name="childs" colspan="4"/>
</page>
<page name="number_sequ" string="Amount and Numbering" col="4">
<label name="number_sequ"/> <label name="number_sequ"/>
<field name="number_sequ"/> <field name="number_sequ"/>
<label name="number_atcheck"/> <label name="number_atcheck"/>
<field name="number_atcheck"/> <field name="number_atcheck"/>
<newline/>
<label name="btype"/>
<field name="btype"/>
<label name="currency"/>
<field name="currency"/>
<newline/>
<label id="phaccount" colspan="2" string=" "/>
<newline/>
<label name="start_balance"/>
<field name="start_balance"/>
<label name="balance"/>
<field name="balance"/>
<newline/>
<label name="start_date"/> <label name="start_date"/>
<field name="start_date"/> <field name="start_date"/>
<newline/> <label name="currency"/>
<field name="currency"/>
<notebook colspan="6"> <label id="phaccount" colspan="2" string=" "/>
<page id="pgrecon" string="Reconciliations" col="1">
<field name="reconciliations"/>
</page> </page>
<page id="pgperm" string="Owner and Authorizeds" col="4"> <page id="pgperm" string="Owner and Authorizeds" col="4">
<label name="owner"/> <label name="owner"/>

View file

@ -3,15 +3,8 @@
The COPYRIGHT file at the top level of this repository contains the The COPYRIGHT file at the top level of this repository contains the
full copyright notices and license terms. --> full copyright notices and license terms. -->
<tree> <tree>
<field name="name"/> <field name="rec_name"/>
<field name="btype"/> <field name="balance" sum="Balance"/>
<field name="start_balance"/>
<field name="balance"/>
<field name="currency"/> <field name="currency"/>
<field name="owner"/>
<field name="reviewer"/>
<field name="observer"/>
<field name="state"/> <field name="state"/>
<button name="wfopen"/>
<button name="wfclosed"/>
</tree> </tree>

12
view/book_tree.xml Normal file
View file

@ -0,0 +1,12 @@
<?xml version="1.0"?>
<!-- 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. -->
<tree>
<field name="name"/>
<field name="balance" sum="Balance"/>
<field name="currency"/>
<field name="state"/>
<field name="parent" tree_invisible="1"/>
<field name="childs" tree_invisible="1"/>
</tree>

View file

@ -2,17 +2,17 @@
<!-- This file is part of the cashbook-module from m-ds for Tryton. <!-- 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 The COPYRIGHT file at the top level of this repository contains the
full copyright notices and license terms. --> full copyright notices and license terms. -->
<form col="10"> <form col="6">
<label name="cashbook"/> <label name="cashbook"/>
<field name="cashbook" widget="selection"/> <field name="cashbook" widget="selection" colspan="3"/>
<label name="checked"/>
<field name="checked"/>
<label name="date_from"/> <label name="date_from"/>
<field name="date_from"/> <field name="date_from"/>
<label name="date_to"/> <label name="date_to"/>
<field name="date_to"/> <field name="date_to"/>
<label name="checked"/>
<field name="checked"/>
<label name="done"/> <label name="done"/>
<field name="done"/> <field name="done"/>

View file

@ -13,14 +13,12 @@ full copyright notices and license terms. -->
<field name="description" colspan="5"/> <field name="description" colspan="5"/>
<notebook colspan="6"> <notebook colspan="6">
<page string="General Information" id="general" col="6"> <page string="General Information" id="general" col="4">
<label name="company"/> <label name="company"/>
<field name="company"/> <field name="company"/>
<label name="parent"/> <label name="parent"/>
<field name="parent"/> <field name="parent"/>
<label name="sequence"/> <field name="childs" colspan="4"/>
<field name="sequence"/>
<field name="childs" colspan="6"/>
</page> </page>
</notebook> </notebook>

View file

@ -2,8 +2,6 @@
<!-- This file is part of the cashbook-module from m-ds for Tryton. <!-- 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 The COPYRIGHT file at the top level of this repository contains the
full copyright notices and license terms. --> full copyright notices and license terms. -->
<tree sequence="sequence"> <tree>
<field name="rec_name"/> <field name="rec_name"/>
<field name="cattype"/>
<field name="sequence" tree_invisible="1"/>
</tree> </tree>

View file

@ -2,10 +2,8 @@
<!-- This file is part of the cashbook-module from m-ds for Tryton. <!-- 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 The COPYRIGHT file at the top level of this repository contains the
full copyright notices and license terms. --> full copyright notices and license terms. -->
<tree sequence="sequence"> <tree >
<field name="name"/> <field name="name"/>
<field name="cattype"/>
<field name="sequence" tree_invisible="1"/>
<field name="parent" tree_invisible="1"/> <field name="parent" tree_invisible="1"/>
<field name="childs" tree_invisible="1"/> <field name="childs" tree_invisible="1"/>
</tree> </tree>

View file

@ -12,6 +12,22 @@ this repository contains the full copyright notices and license terms. -->
<label name="done"/> <label name="done"/>
<field name="done"/> <field name="done"/>
<separator id="sepenterbook" colspan="4" string="Enter Booking Wizard"/>
<label name="defbook"/>
<field name="defbook"/>
<label name="book1"/>
<field name="book1"/>
<label name="book2"/>
<field name="book2"/>
<label name="book3"/>
<field name="book3"/>
<label name="book4"/>
<field name="book4"/>
<label name="book5"/>
<field name="book5"/>
<separator id="sepcb" colspan="4" string="Cashbook"/> <separator id="sepcb" colspan="4" string="Cashbook"/>
<label name="catnamelong"/> <label name="catnamelong"/>
<field name="catnamelong"/> <field name="catnamelong"/>

View file

@ -0,0 +1,32 @@
<?xml version="1.0"?>
<!-- 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. -->
<form col="2">
<label name="cashbook"/>
<field name="cashbook" widget="selection"/>
<notebook colspan="2">
<page id="general" string="Booking" col="2">
<label name="bookingtype"/>
<field name="bookingtype"/>
<label name="amount"/>
<field name="amount" symbol="currency"/>
<label name="category"/>
<field name="category"/>
<label name="booktransf"/>
<field name="booktransf"/>
<label name="party"/>
<field name="party"/>
</page>
<page id="descr" string="Description" col="1">
<field name="description"/>
</page>
</notebook>
<field name="cashbooks"/>
<field name="currency_digits"/>
<field name="currency"/>
<field name="owner_cashbook"/>
</form>

View file

@ -8,11 +8,10 @@ full copyright notices and license terms. -->
<field name="date"/> <field name="date"/>
<field name="payee"/> <field name="payee"/>
<field name="category_view"/> <field name="category_view"/>
<field name="description" expand="1"/> <field name="descr_short" expand="1"/>
<field name="credit" sum="Credit"/> <field name="credit" sum="Credit"/>
<field name="debit" sum="Debit"/> <field name="debit" sum="Debit"/>
<field name="balance"/> <field name="balance"/>
<field name="currency"/>
<field name="state"/> <field name="state"/>
<button name="wfedit"/> <button name="wfedit"/>
<button name="wfcheck"/> <button name="wfcheck"/>

View file

@ -9,8 +9,13 @@ full copyright notices and license terms. -->
<label name="amount"/> <label name="amount"/>
<field name="amount" symbol="currency"/> <field name="amount" symbol="currency"/>
<label name="splittype"/>
<field name="splittype"/>
<label name="category"/> <label name="category"/>
<field name="category"/> <field name="category" colspan="3"/>
<label name="booktransf"/>
<field name="booktransf" colspan="3"/>
<group name="description" colspan="4" col="1" string="Description" yexpand="1"> <group name="description" colspan="4" col="1" string="Description" yexpand="1">
<field name="description" yfill="1"/> <field name="description" yfill="1"/>

View file

@ -4,7 +4,9 @@ The COPYRIGHT file at the top level of this repository contains the
full copyright notices and license terms. --> full copyright notices and license terms. -->
<tree editable="1"> <tree editable="1">
<field name="line" tree_invisible="1"/> <field name="line" tree_invisible="1"/>
<field name="splittype"/>
<field name="category"/> <field name="category"/>
<field name="booktransf"/>
<field name="description" expand="1"/> <field name="description" expand="1"/>
<field name="amount" sum="Amount"/> <field name="amount" sum="Amount"/>
<field name="currency"/> <field name="currency"/>

189
wizard_booking.py Normal file
View file

@ -0,0 +1,189 @@
# -*- 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.model import ModelView, fields
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, 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'
__name__ = 'cashbook.enterbooking.start'
cashbook = fields.Many2One(string='Cashbook', model_name='cashbook.book',
domain=[('id', 'in', Eval('cashbooks', [])), ('btype', '!=', None)],
depends=['cashbooks'], required=True)
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')
currency = fields.Function(fields.Many2One(string='Currency',
model_name='currency.currency', states={'invisible': True}),
'on_change_with_currency')
currency_digits = fields.Function(fields.Integer(string='Currency Digits',
readonly=True, states={'invisible': True}),
'on_change_with_currency_digits')
bookingtype = fields.Selection(string='Type', required=True,
selection=sel_booktypewiz)
amount = fields.Numeric(string='Amount',
depends=['currency_digits', 'bookingtype'],
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': 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', '_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
"""
if self.cashbook:
return self.cashbook.currency.id
@fields.depends('cashbook', '_parent_cashbook.currency')
def on_change_with_currency_digits(self, name=None):
""" digits
"""
if self.cashbook:
return self.cashbook.currency.digits
else :
return 2
# end EnterBookingStart
class EnterBookingWizard(Wizard):
'Enter Booking'
__name__ = 'cashbook.enterbooking'
start_state = 'start'
start = StateView('cashbook.enterbooking.start',
'cashbook.enterbooking_start_form', [
Button('Cancel', 'end', 'tryton-cancel'),
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()
book_ids = []
for x in ['defbook', 'book1', 'book2', 'book3', 'book4', 'book5']:
if getattr(cfg1, x, None) is not None:
book_ids.append(getattr(cfg1, x, None).id)
result = {
'cashbooks': [x.id for x in Cashbook.search([
('state', '=', 'open'),
('btype', '!=', None),
('owner.id', '=', Transaction().user),
('id', 'in', book_ids),
])],
'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):
""" 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

21
wizard_booking.xml Normal file
View file

@ -0,0 +1,21 @@
<?xml version="1.0"?>
<!-- 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. -->
<tryton>
<data>
<record model="ir.ui.view" id="enterbooking_start_form">
<field name="model">cashbook.enterbooking.start</field>
<field name="type">form</field>
<field name="name">enterbooking_start_form</field>
</record>
<!-- enter booking -->
<record model="ir.action.wizard" id="act_enterbooking_wiz">
<field name="name">Enter Booking</field>
<field name="wiz_name">cashbook.enterbooking</field>
</record>
</data>
</tryton>

View file

@ -16,7 +16,7 @@ class OpenCashBookStart(ModelView):
__name__ = 'cashbook.open_lines.start' __name__ = 'cashbook.open_lines.start'
cashbook = fields.Many2One(string='Cashbook', model_name='cashbook.book', cashbook = fields.Many2One(string='Cashbook', model_name='cashbook.book',
required=True) required=True, domain=[('btype', '!=', None)])
checked = fields.Boolean(string='Checked', help="Show cashbook lines in Checked-state.") checked = fields.Boolean(string='Checked', help="Show cashbook lines in Checked-state.")
done = fields.Boolean(string='Done', help="Show cashbook lines in Done-state") done = fields.Boolean(string='Done', help="Show cashbook lines in Done-state")
date_from = fields.Date(string='Start Date') date_from = fields.Date(string='Start Date')
@ -54,7 +54,7 @@ class OpenCashBook(Wizard):
with Transaction().set_context({ with Transaction().set_context({
'_check_access': True, '_check_access': True,
}): }):
books = Book.search([]) books = Book.search([('btype', '!=', None)])
if len(books) == 1: if len(books) == 1:
return 'open_' return 'open_'
return 'askuser' return 'askuser'
@ -90,7 +90,7 @@ class OpenCashBook(Wizard):
with Transaction().set_context({ with Transaction().set_context({
'_check_access': True, '_check_access': True,
}): }):
books = Book.search([]) books = Book.search([('btype', '!=', None)])
if len(books) > 0: if len(books) > 0:
book = books[0] book = books[0]