line, book - wf + tests

This commit is contained in:
Frederik Jaeckel 2022-08-08 14:31:42 +02:00
parent ba442b726e
commit 654e9d2ee7
25 changed files with 786 additions and 160 deletions

View file

@ -5,13 +5,13 @@
from trytond.pool import Pool
from .book import Book
from .account_type import AccountType
from .types import Type
from .line import Line, LineContext
from .wizard_openline import OpenCashBook, OpenCashBookStart
def register():
Pool.register(
AccountType,
Type,
Book,
LineContext,
Line,

120
book.py
View file

@ -3,22 +3,126 @@
# The COPYRIGHT file at the top level of this repository contains the
# full copyright notices and license terms.
from trytond.model import ModelView, ModelSQL, fields
from trytond.model import Workflow, ModelView, ModelSQL, fields, Check
from trytond.pyson import Eval
from trytond.exceptions import UserError
from trytond.i18n import gettext
class Book(ModelSQL, ModelView):
'Account'
STATES = {
'readonly': Eval('state', '') != 'open',
}
DEPENDS=['state']
sel_state_book = [
('open', 'Open'),
('closed', 'Closed'),
('archive', 'Archive'),
]
class Book(Workflow, ModelSQL, ModelView):
'Cashbook'
__name__ = 'cashbook.book'
name = fields.Char(string='Name', required=True)
btype = fields.Many2One(string='Account Type', required=True,
model_name='cashbook.type', ondelete='RESTRICT')
lines = fields.One2Many(string='Lines', field='account',
model_name='cashbook.line')
name = fields.Char(string='Name', required=True,
states=STATES, depends=DEPENDS)
btype = fields.Many2One(string='Type', required=True,
model_name='cashbook.type', ondelete='RESTRICT',
states=STATES, depends=DEPENDS)
lines = fields.One2Many(string='Lines', field='cashbook',
model_name='cashbook.line',
states=STATES, depends=DEPENDS)
state = fields.Selection(string='State', required=True,
readonly=True, selection=sel_state_book)
state_string = state.translated('state')
@classmethod
def __setup__(cls):
super(Book, cls).__setup__()
cls._order.insert(0, ('name', 'ASC'))
t = cls.__table__()
cls._sql_constraints.extend([
('state_val',
Check(t, t.state.in_(['open', 'closed', 'archive'])),
'cashbook.msg_book_wrong_state_value'),
])
cls._transitions |= set((
('open', 'closed'),
('closed', 'open'),
('closed', 'archive'),
))
cls._buttons.update({
'wfopen': {
'invisible': Eval('state', '') != 'closed',
'depends': ['state'],
},
'wfclosed': {
'invisible': Eval('state') != 'open',
'depends': ['state'],
},
'wfarchive': {
'invisible': Eval('state') != 'closed',
'depends': ['state'],
},
})
@classmethod
def default_state(cls):
return 'open'
@classmethod
@ModelView.button
@Workflow.transition('open')
def wfopen(cls, books):
""" open cashbook
"""
pass
@classmethod
@ModelView.button
@Workflow.transition('closed')
def wfclosed(cls, books):
""" cashbook is closed
"""
pass
@classmethod
@ModelView.button
@Workflow.transition('archive')
def wfarchive(cls, books):
""" cashbook is archived
"""
pass
@classmethod
def write(cls, *args):
""" deny update if book is not 'open'
"""
actions = iter(args)
for books, values in zip(actions, actions):
for book in books:
if book.state != 'open':
# allow state-update, if its the only action
if not (('state' in values.keys()) and (len(values.keys()) == 1)):
raise UserError(gettext(
'cashbook.msg_book_deny_write',
bookname = book.rec_name,
state_txt = book.state_string,
))
super(Book, cls).write(*args)
@classmethod
def delete(cls, books):
""" deny delete if book has lines
"""
for book in books:
if (len(book.lines) > 0) and (book.state != 'archive'):
raise UserError(gettext(
'cashbook.msg_book_deny_delete',
bookname = book.rec_name,
booklines = len(book.lines),
))
return super(Book, cls).delete(books)
# end Book

View file

@ -21,7 +21,7 @@ full copyright notices and license terms. -->
<!-- action view-->
<record model="ir.action.act_window" id="act_book_view">
<field name="name">Account</field>
<field name="name">Cashbook</field>
<field name="res_model">cashbook.book</field>
</record>
<record model="ir.action.act_window.view" id="act_book_view-1">
@ -63,5 +63,41 @@ full copyright notices and license terms. -->
<field name="perm_delete" eval="False"/>
</record>
<!-- button - open -->
<record model="ir.model.button" id="book_wfopen_button">
<field name="name">wfopen</field>
<field name="string">Open</field>
<field name="model" search="[('model', '=', 'cashbook.book')]"/>
</record>
<record model="ir.model.button-res.group"
id="book_wfopen_button-group_admin">
<field name="button" ref="book_wfopen_button"/>
<field name="group" ref="res.group_admin"/>
</record>
<!-- button - close -->
<record model="ir.model.button" id="book_wfclosed_button">
<field name="name">wfclosed</field>
<field name="string">Close</field>
<field name="model" search="[('model', '=', 'cashbook.book')]"/>
</record>
<record model="ir.model.button-res.group"
id="book_wfclosed_button-group_admin">
<field name="button" ref="book_wfclosed_button"/>
<field name="group" ref="res.group_admin"/>
</record>
<!-- button - done -->
<record model="ir.model.button" id="book_wfarchive_button">
<field name="name">wfarchive</field>
<field name="string">Archive</field>
<field name="model" search="[('model', '=', 'cashbook.book')]"/>
</record>
<record model="ir.model.button-res.group"
id="book_wfarchive_button-group_admin">
<field name="button" ref="book_wfarchive_button"/>
<field name="group" ref="res.group_admin"/>
</record>
</data>
</tryton>

99
line.py
View file

@ -3,10 +3,14 @@
# The COPYRIGHT file at the top level of this repository contains the
# full copyright notices and license terms.
from trytond.model import ModelView, ModelSQL, Workflow, fields
from trytond.model import ModelView, ModelSQL, Workflow, fields, Check
from trytond.pool import Pool
from trytond.pyson import Eval, If
from trytond.pyson import Eval, If, Or
from trytond.transaction import Transaction
from trytond.report import Report
from trytond.exceptions import UserError
from trytond.i18n import gettext
from .book import sel_state_book
sel_linetype = [
('edit', 'Edit'),
@ -16,20 +20,23 @@ sel_linetype = [
STATES = {
'readonly': Eval('state', '') != 'edit',
'readonly': Or(
Eval('state', '') != 'edit',
Eval('state_cashbook', '') != 'open',
),
}
DEPENDS=['state']
DEPENDS=['state', 'state_cashbook']
class LineContext(ModelView):
'Line Context'
__name__ = 'cashbook.line.context'
account = fields.Many2One(string='Account', required=True,
cashbook = fields.Many2One(string='Cashbook', required=True,
model_name='cashbook.book',
states={
'readonly': Eval('num_account', 0) < 2,
}, depends=['num_account'])
'readonly': Eval('num_cashbook', 0) < 2,
}, depends=['num_cashbook'])
date_from = fields.Date(string='Start Date', depends=['date_to'],
domain=[
If(Eval('date_to') & Eval('date_from'),
@ -46,16 +53,16 @@ class LineContext(ModelView):
help='Show account lines in Checked-state.')
done = fields.Boolean(string='Done',
help='Show account lines in Done-state.')
num_account = fields.Function(fields.Integer(string='Number of Accounts',
num_cashbook = fields.Function(fields.Integer(string='Number of Cashbook',
readonly=True, states={'invisible': True}),
'on_change_with_num_account')
'on_change_with_num_cashbook')
@classmethod
def default_account(cls):
def default_cashbook(cls):
""" get default from context
"""
context = Transaction().context
return context.get('account', None)
return context.get('cashbook', None)
@classmethod
def default_date_from(cls):
@ -85,8 +92,8 @@ class LineContext(ModelView):
context = Transaction().context
return context.get('done', False)
def on_change_with_num_account(self, name=None):
""" get number of accessible accounts,
def on_change_with_num_cashbook(self, name=None):
""" get number of accessible cashbooks,
depends on user-permissions
"""
CashBook = Pool().get('cashbook.book')
@ -96,10 +103,10 @@ class LineContext(ModelView):
class Line(Workflow, ModelSQL, ModelView):
'Account Line'
'Cashbook Line'
__name__ = 'cashbook.line'
account = fields.Many2One(string='Account', required=True, select=True,
cashbook = fields.Many2One(string='Cashbook', required=True, select=True,
model_name='cashbook.book', ondelete='CASCADE', readonly=True)
date = fields.Date(string='Date', required=True, select=True,
states=STATES, depends=DEPENDS)
@ -107,11 +114,21 @@ class Line(Workflow, ModelSQL, ModelView):
states=STATES, depends=DEPENDS)
state = fields.Selection(string='State', required=True, readonly=True,
select=True, selection=sel_linetype)
state_string = state.translated('state')
state_cashbook = fields.Function(fields.Selection(string='State of Cashbook',
readonly=True, states={'invisible': True}, selection=sel_state_book),
'on_change_with_state_cashbook', searcher='search_state_cashbook')
@classmethod
def __setup__(cls):
super(Line, cls).__setup__()
cls._order.insert(0, ('date', 'ASC'))
t = cls.__table__()
cls._sql_constraints.extend([
('state_val',
Check(t, t.state.in_(['edit', 'check', 'done'])),
'cashbook.msg_line_wrong_state_value'),
])
cls._transitions |= set((
('edit', 'check'),
('check', 'done'),
@ -170,10 +187,58 @@ class Line(Workflow, ModelSQL, ModelView):
return IrDate.today()
@classmethod
def default_account(cls):
def default_cashbook(cls):
""" get default from context
"""
context = Transaction().context
return context.get('account', None)
return context.get('cashbook', None)
def get_rec_name(self, name):
""" short + name
"""
return '%(date)s %(desc)s' % {
'date': Report.format_date(self.date),
'desc': (self.description or '-')[:40],
}
@classmethod
def search_rec_name(cls, name, clause):
""" search in description +...
"""
return [('description',) + tuple(clause[1:])]
@fields.depends('cashbook', '_parent_cashbook.state')
def on_change_with_state_cashbook(self, name=None):
""" get state of cashbook
"""
if self.cashbook:
return self.cashbook.state
@classmethod
def search_state_cashbook(cls, names, clause):
""" search in state of cashbook
"""
return [('cashbook.state',) + tuple(clause[1:])]
@classmethod
def delete(cls, lines):
""" deny delete if book is not 'open' or wf is not 'edit'
"""
for line in lines:
if line.cashbook.state == 'closed':
raise UserError(gettext(
'cashbook.msg_line_deny_delete1',
linetxt = line.rec_name,
bookname = line.cashbook.rec_name,
bookstate = line.cashbook.state_string,
))
if line.state != 'edit':
raise UserError(gettext(
'cashbook.msg_line_deny_delete2',
linetxt = line.rec_name,
linestate = line.state_string,
))
return super(Line, cls).delete(lines)
# end Line

View file

@ -27,12 +27,12 @@ full copyright notices and license terms. -->
<!-- action view -->
<record model="ir.action.act_window" id="act_line_view">
<field name="name">Account Line</field>
<field name="name">Cashbook Line</field>
<field name="res_model">cashbook.line</field>
<field name="context_model">cashbook.line.context</field>
<field name="context_domain"
eval="[
('account', '=', Eval('account', -1)),
('cashbook', '=', Eval('cashbook', -1)),
If(Bool(Eval('date_from')), ('date', '&gt;=', Eval('date_from')), ()),
If(Bool(Eval('date_to')), ('date', '&lt;=', Eval('date_to')), ()),
['OR',

View file

@ -14,6 +14,32 @@ msgctxt "model:ir.message,text:msg_name_cashbook"
msgid "Cashbook"
msgstr "Kassenbuch"
msgctxt "model:ir.message,text:msg_line_wrong_state_value"
msgid "Invalid value of the field 'Status', allowed: edit, check, done."
msgstr "Ungültiger Wert des Feldes 'Status', erlaubt: edit, check, done."
msgctxt "model:ir.message,text:msg_book_wrong_state_value"
msgid "Invalid value of the field 'Status', allowed: open, closed, archive."
msgstr "Ungültiger Wert des Feldes 'Status', erlaubt: open, closed, archive."
msgctxt "model:ir.message,text:msg_book_deny_delete"
msgid "The cashbook '%(bookname)s' cannot be deleted because it contains %(booklines)s lines and is not in the status 'Archive'."
msgstr "Das Kassenbuch '%(bookname)s' kann nicht gelöscht werden, da es %(booklines)s Zeilen enthält und nicht im Status 'Archiv' ist."
msgctxt "model:ir.message,text:msg_book_deny_write"
msgid "The cash book '%(bookname)s' is '%(state_txt)s' and cannot be changed."
msgstr "Das Kassenbuch '%(bookname)s' ist '%(state_txt)s' und kann nicht geändert werden."
msgctxt "model:ir.message,text:msg_line_deny_delete1"
msgid "The cashbook line '%(linetxt)s' cannot be deleted because the Cashbook '%(bookname)s' is in state '%(bookstate)s'."
msgstr "Die Kassenbuchzeile '%(linetxt)s' kann nicht gelöscht werden, weil das Kassenbuch '%(bookname)s' im Status '%(bookstate)s' ist."
msgctxt "model:ir.message,text:msg_line_deny_delete2"
msgid "The cashbook line '%(linetxt)s' cannot be deleted, its in state '%(linestate)s'."
msgstr "Die Kassenbuchzeile '%(linetxt)s' kann nicht gelöscht werden, da sie im Status '%(linestate)s' ist."
#############
# res.group #
@ -42,15 +68,15 @@ msgctxt "model:ir.ui.menu,name:menu_config"
msgid "Configuration"
msgstr "Konfiguration"
msgctxt "model:ir.ui.menu,name:menu_accounttype"
msgid "Account Type"
msgstr "Kontotyp"
msgctxt "model:ir.ui.menu,name:menu_typeconfig"
msgid "Cashbook Type"
msgstr "Kassenbuchtyp"
msgctxt "model:ir.ui.menu,name:menu_account"
msgid "Account"
msgstr "Konto"
msgctxt "model:ir.ui.menu,name:menu_bookconfig"
msgid "Cashbook"
msgstr "Kassenbuch"
msgctxt "model:ir.ui.menu,name:menu_open_accountlines"
msgctxt "model:ir.ui.menu,name:menu_open_lines"
msgid "Open Cashbook"
msgstr "Kassenbuch öffnen"
@ -62,17 +88,13 @@ msgctxt "model:ir.action,name:act_book_view"
msgid "Account"
msgstr "Konto"
msgctxt "model:ir.action,name:act_accounttype_view"
msgid "Account Type"
msgstr "Kontotyp"
msgctxt "model:ir.action,name:act_type_view"
msgid "Cashbook Type"
msgstr "Kassenbuchtyp"
msgctxt "model:ir.action,name:act_line_view"
msgid "Account Line"
msgstr "Kontozeile"
msgctxt "model:ir.action,name:act_line_view"
msgid "Account Line"
msgstr "Kontozeile"
msgid "Cashbook Line"
msgstr "Kassenbuchzeile"
msgctxt "model:ir.action,name:act_open_chart"
msgid "Open Cashbook"
@ -94,33 +116,69 @@ msgctxt "model:ir.model.button,string:line_wfdone_button"
msgid "Done"
msgstr "Fertig"
msgctxt "model:ir.model.button,string:book_wfopen_button"
msgid "Open"
msgstr "Öffnen"
msgctxt "model:ir.model.button,string:book_wfclosed_button"
msgid "Close"
msgstr "Schliessen"
msgctxt "model:ir.model.button,string:book_wfarchive_button"
msgid "Archive"
msgstr "Archiv"
#################
# cashbook.book #
#################
msgctxt "model:cashbook.book,name:"
msgid "Account"
msgstr "Konto"
msgid "Cashbook"
msgstr "Kassenbuch"
msgctxt "field:cashbook.book,name:"
msgid "Name"
msgstr "Name"
msgctxt "field:cashbook.book,btype:"
msgid "Account Type"
msgstr "Kontotyp"
msgid "Type"
msgstr "Typ"
msgctxt "field:cashbook.book,state:"
msgid "State"
msgstr "Status"
msgctxt "selection:cashbook.book,state:"
msgid "Open"
msgstr "Geöffnet"
msgctxt "selection:cashbook.book,state:"
msgid "Closed"
msgstr "Geschlossen"
msgctxt "selection:cashbook.book,state:"
msgid "Archive"
msgstr "Archiv"
#################
# cashbook.line #
#################
msgctxt "model:cashbook.line,name:"
msgid "Account Line"
msgstr "Kontozeile"
msgid "Cashbook Line"
msgstr "Kassenbuchzeile"
msgctxt "field:cashbook.line,account:"
msgid "Account"
msgstr "Konto"
msgctxt "view:cashbook.line:"
msgid "Cashbook Line"
msgstr "Kassenbuchzeile"
msgctxt "view:cashbook.line:"
msgid "State"
msgstr "Status"
msgctxt "field:cashbook.line,cashbook:"
msgid "Cashbook"
msgstr "Kassenbuch"
msgctxt "field:cashbook.line,date:"
msgid "Date"
@ -151,8 +209,8 @@ msgstr "Fertig"
# cashbook.type #
#################
msgctxt "model:cashbook.type,name:"
msgid "Account Type"
msgstr "Kontotyp"
msgid "Cashbook Type"
msgstr "Kassenbuchtyp"
msgctxt "field:cashbook.type,name:"
msgid "Name"
@ -166,76 +224,63 @@ msgctxt "model:cashbook.type,name:atype_cash"
msgid "Cash"
msgstr "Bar"
msgctxt "model:cashbook.type,short:atype_cash"
msgid "CAS"
msgstr "CAS"
msgctxt "model:cashbook.type,name:atype_giro"
msgid "Giro"
msgstr "Giro"
msgctxt "model:cashbook.type,short:atype_giro"
msgid "GIR"
msgstr "GIR"
msgctxt "model:cashbook.type,name:atype_fixtermdep"
msgid "Fixed-term deposit"
msgstr "Festgeld"
msgctxt "model:cashbook.type,short:atype_fixtermdep"
msgid "FIX"
msgstr "FIX"
####################################
# cashbook.open_accountlines.start #
####################################
msgctxt "model:cashbook.open_accountlines.start,name:"
#############################
# cashbook.open_lines.start #
#############################
msgctxt "model:cashbook.open_lines.start,name:"
msgid "Open Cashbook"
msgstr "Kassenbuch öffnen"
msgctxt "field:cashbook.open_accountlines.start,account:"
msgid "Account"
msgstr "Konto"
msgctxt "field:cashbook.open_lines.start,cashbook:"
msgid "Cashbook"
msgstr "Kassenbuch"
msgctxt "field:cashbook.open_accountlines.start,checked:"
msgctxt "field:cashbook.open_lines.start,checked:"
msgid "Checked"
msgstr "Geprüft"
msgctxt "help:cashbook.open_accountlines.start,checked:"
msgid "Show account lines in Checked-state."
msgstr "Zeigt Kontozeilen im 'Geprüft'-Status"
msgctxt "help:cashbook.open_lines.start,checked:"
msgid "Show cashbook lines in Checked-state."
msgstr "Zeigt Kassenbuchzeilen im 'Geprüft'-Status"
msgctxt "field:cashbook.open_accountlines.start,done:"
msgctxt "field:cashbook.open_lines.start,done:"
msgid "Done"
msgstr "Fertig"
msgctxt "help:cashbook.open_accountlines.start,done:"
msgid "Show account lines in Done-state."
msgstr "Zeigt Kontozeilen im 'Fertig'-Status"
msgctxt "help:cashbook.open_lines.start,done:"
msgid "Show cashbook lines in Done-state."
msgstr "Zeigt Kassenbuchzeilen im 'Fertig'-Status"
msgctxt "field:cashbook.open_accountlines.start,date_from:"
msgctxt "field:cashbook.open_lines.start,date_from:"
msgid "Start Date"
msgstr "Beginndatum"
msgctxt "field:cashbook.open_accountlines.start,date_to:"
msgctxt "field:cashbook.open_lines.start,date_to:"
msgid "End Date"
msgstr "Endedatum"
##############################
# cashbook.open_accountlines #
##############################
msgctxt "model:cashbook.open_accountlines,name:"
#######################
# cashbook.open_lines #
#######################
msgctxt "model:cashbook.open_lines,name:"
msgid "Open Cashbook"
msgstr "Kassenbuch öffnen"
msgctxt "wizard_button:cashbook.open_accountlines,start,end:"
msgctxt "wizard_button:cashbook.open_lines,start,end:"
msgid "Cancel"
msgstr "Abbruch"
msgctxt "wizard_button:cashbook.open_accountlines,start,open_:"
msgctxt "wizard_button:cashbook.open_lines,start,open_:"
msgid "Open"
msgstr "Öffnen"
@ -247,25 +292,25 @@ msgctxt "model:cashbook.line.context,name:"
msgid "Line Context"
msgstr "Zeilenkontext"
msgctxt "field:cashbook.line.context,account:"
msgid "Account"
msgstr "Konto"
msgctxt "field:cashbook.line.context,cashbook:"
msgid "Cashbook"
msgstr "Kassenbuch"
msgctxt "field:cashbook.line.context,checked:"
msgid "Checked"
msgstr "Geprüft"
msgctxt "help:cashbook.line.context,checked:"
msgid "Show account lines in Checked-state."
msgstr "Zeigt Kontozeilen im 'Geprüft'-Status"
msgid "Show cashbook lines in Checked-state."
msgstr "Zeigt Kassenbuchzeilen im 'Geprüft'-Status"
msgctxt "field:cashbook.line.context,done:"
msgid "Done"
msgstr "Fertig"
msgctxt "help:cashbook.line.context,done:"
msgid "Show account lines in Done-state."
msgstr "Zeigt Kontozeilen im 'Fertig'-Status"
msgid "Show cashbook lines in Done-state."
msgstr "Zeigt Kassenbuchzeilen im 'Fertig'-Status"
msgctxt "field:cashbook.line.context,date_from:"
msgid "Start Date"

View file

@ -26,26 +26,26 @@ full copyright notices and license terms. -->
<field name="group" ref="res.group_admin"/>
</record>
<!-- menu: /Cashbook/Configuration/Account -->
<menuitem id="menu_account" action="act_book_view"
<!-- menu: /Cashbook/Configuration/Cashbook -->
<menuitem id="menu_bookconfig" action="act_book_view"
icon="tryton-list"
parent="menu_config" sequence="10"/>
<record model="ir.ui.menu-res.group" id="menu_account-group_admin">
<field name="menu" ref="menu_account"/>
<record model="ir.ui.menu-res.group" id="menu_bookconfig-group_admin">
<field name="menu" ref="menu_bookconfig"/>
<field name="group" ref="res.group_admin"/>
</record>
<!-- menu: /Cashbook/Configuration/Account types -->
<menuitem id="menu_accounttype" action="act_accounttype_view"
<!-- menu: /Cashbook/Configuration/Types -->
<menuitem id="menu_typeconfig" action="act_type_view"
icon="tryton-list"
parent="menu_config" sequence="20"/>
<record model="ir.ui.menu-res.group" id="menu_accounttype-group_admin">
<field name="menu" ref="menu_accounttype"/>
<record model="ir.ui.menu-res.group" id="menu_typeconfig-group_admin">
<field name="menu" ref="menu_typeconfig"/>
<field name="group" ref="res.group_admin"/>
</record>
<!-- menu: /Cashbook/Open Cashbook -->
<menuitem id="menu_open_accountlines" action="act_open_accountlines"
<menuitem id="menu_open_lines" action="act_open_lines"
icon="tryton-tree"
parent="menu_cashbook" sequence="20"/>

View file

@ -11,6 +11,24 @@ full copyright notices and license terms. -->
<record model="ir.message" id="msg_name_cashbook">
<field name="text">Cashbook</field>
</record>
<record model="ir.message" id="msg_line_wrong_state_value">
<field name="text">Invalid value of the field 'Status', allowed: edit, check, done.</field>
</record>
<record model="ir.message" id="msg_book_wrong_state_value">
<field name="text">Invalid value of the field 'Status', allowed: open, closed, archive.</field>
</record>
<record model="ir.message" id="msg_book_deny_delete">
<field name="text">The cash book '%(bookname)s' cannot be deleted because it contains %(booklines)s lines and is not in the status 'Archive'.</field>
</record>
<record model="ir.message" id="msg_book_deny_write">
<field name="text">The cash book '%(bookname)s' is '%(state_txt)s' and cannot be changed.</field>
</record>
<record model="ir.message" id="msg_line_deny_delete1">
<field name="text">The cashbook line '%(linetxt)s' cannot be deleted because the Cashbook '%(bookname)s' is in state '%(bookstate)s'.</field>
</record>
<record model="ir.message" id="msg_line_deny_delete2">
<field name="text">The cashbook line '%(linetxt)s' cannot be deleted, its in state '%(linestate)s'.</field>
</record>
</data>
</tryton>

View file

@ -4,14 +4,17 @@
import trytond.tests.test_tryton
import unittest
from trytond.modules.cashbook.tests.test_accounttype import AccounttypeTestCase
from trytond.modules.cashbook.tests.test_type import TypeTestCase
from trytond.modules.cashbook.tests.test_book import BookTestCase
from trytond.modules.cashbook.tests.test_line import LineTestCase
__all__ = ['suite']
class CashbookTestCase(\
AccounttypeTestCase,
LineTestCase,
BookTestCase,
TypeTestCase,
):
'Test cashbook module'
module = 'cashbook'

176
tests/test_book.py Normal file
View file

@ -0,0 +1,176 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
import os, requests
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
class BookTestCase(ModuleTestCase):
'Test cashbook book module'
module = 'cashbook'
@with_transaction()
def test_book_create(self):
""" create cashbook
"""
pool = Pool()
Book = pool.get('cashbook.book')
Types = pool.get('cashbook.type')
types, = Types.search([('short', '=','CAS')])
book, = Book.create([{
'name': 'Book 1',
'btype': types.id,
}])
self.assertEqual(book.name, 'Book 1')
self.assertEqual(book.btype.rec_name, 'CAS - Cash')
self.assertEqual(book.state, 'open')
self.assertEqual(book.state_string, 'Open')
@with_transaction()
def test_book_deny_delete_open(self):
""" create cashbook, add lines, try to delete in state 'open'
"""
pool = Pool()
Book = pool.get('cashbook.book')
Types = pool.get('cashbook.type')
types, = Types.search([('short', '=', 'CAS')])
book, = Book.create([{
'name': 'Book 1',
'btype': types.id,
'lines': [('create', [{
'date': date(2022, 5, 1),
'description': 'test 1',
}])],
}])
self.assertEqual(book.name, 'Book 1')
self.assertEqual(book.state, 'open')
self.assertRaisesRegex(UserError,
"The cash book 'Book 1' cannot be deleted because it contains 1 lines and is not in the status 'Archive'.",
Book.delete,
[book])
@with_transaction()
def test_book_deny_delete_closed(self):
""" create cashbook, add lines, try to delete in state 'closed'
"""
pool = Pool()
Book = pool.get('cashbook.book')
Types = pool.get('cashbook.type')
types, = Types.search([('short', '=', 'CAS')])
book, = Book.create([{
'name': 'Book 1',
'btype': types.id,
'lines': [('create', [{
'date': date(2022, 5, 1),
'description': 'test 1',
}])],
}])
self.assertEqual(book.name, 'Book 1')
self.assertEqual(book.state, 'open')
Book.wfclosed([book])
self.assertEqual(book.state, 'closed')
self.assertRaisesRegex(UserError,
"The cash book 'Book 1' cannot be deleted because it contains 1 lines and is not in the status 'Archive'.",
Book.delete,
[book])
@with_transaction()
def test_book_deny_delete_archive(self):
""" create cashbook, add lines, try to delete in state 'archive'
"""
pool = Pool()
Book = pool.get('cashbook.book')
Types = pool.get('cashbook.type')
types, = Types.search([('short', '=', 'CAS')])
book, = Book.create([{
'name': 'Book 1',
'btype': types.id,
'lines': [('create', [{
'date': date(2022, 5, 1),
'description': 'test 1',
}])],
}])
self.assertEqual(book.name, 'Book 1')
self.assertEqual(book.state, 'open')
Book.wfclosed([book])
Book.wfarchive([book])
self.assertEqual(book.state, 'archive')
Book.delete([book])
@with_transaction()
def test_book_deny_update_1(self):
""" create cashbook, try to update in states open, closed, archive
"""
pool = Pool()
Book = pool.get('cashbook.book')
Types = pool.get('cashbook.type')
types, = Types.search([('short', '=', 'CAS')])
book, = Book.create([{
'name': 'Book 1',
'btype': types.id,
}])
self.assertEqual(book.name, 'Book 1')
self.assertEqual(book.state, 'open')
Book.write(*[
[book],
{
'name': 'Book 1a',
}])
self.assertEqual(book.name, 'Book 1a')
# wf: open --> closed
Book.wfclosed([book])
self.assertEqual(book.state, 'closed')
self.assertRaisesRegex(UserError,
"The cash book 'Book 1a' is 'Closed' and cannot be changed.",
Book.write,
*[
[book],
{
'name': 'Book 1b',
},
])
Book.wfopen([book])
self.assertEqual(book.state, 'open')
Book.write(*[
[book],
{
'name': 'Book 1c',
}])
self.assertEqual(book.name, 'Book 1c')
Book.wfclosed([book])
Book.wfarchive([book])
self.assertRaisesRegex(UserError,
"The cash book 'Book 1c' is 'Archive' and cannot be changed.",
Book.write,
*[
[book],
{
'name': 'Book 1d',
},
])
# end BookTestCase

153
tests/test_line.py Normal file
View file

@ -0,0 +1,153 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
import os, requests
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
class LineTestCase(ModuleTestCase):
'Test cashbook line module'
module = 'cashbook'
@with_transaction()
def test_line_create_check_names_search(self):
""" create cashbook + line
"""
pool = Pool()
Book = pool.get('cashbook.book')
Types = pool.get('cashbook.type')
Lines = pool.get('cashbook.line')
types, = Types.search([('short', '=','CAS')])
book, = Book.create([{
'name': 'Book 1',
'btype': types.id,
'lines': [('create', [{
'date': date(2022, 5, 1),
'description': 'Text 1',
}, {
'date': date(2022, 5, 2),
'description': 'Text 2',
}])],
}])
self.assertEqual(book.name, 'Book 1')
self.assertEqual(book.btype.rec_name, 'CAS - Cash')
self.assertEqual(book.state, 'open')
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 Text 1')
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 Text 2')
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', 'ilike', 'text%')]), 2)
self.assertEqual(Lines.search_count([('state_cashbook', '=', 'open')]), 2)
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', '=', 'closed')]), 0)
@with_transaction()
def test_line_delete_with_book_in_open_state(self):
""" create cashbook + line, book in state=open, delete a line
"""
pool = Pool()
Book = pool.get('cashbook.book')
Types = pool.get('cashbook.type')
Lines = pool.get('cashbook.line')
types, = Types.search([('short', '=','CAS')])
book, = Book.create([{
'name': 'Book 1',
'btype': types.id,
'lines': [('create', [{
'date': date(2022, 5, 1),
'description': 'Text 1',
}, {
'date': date(2022, 5, 2),
'description': 'Text 2',
}])],
}])
self.assertEqual(book.name, 'Book 1')
self.assertEqual(len(book.lines), 2)
self.assertEqual(book.state, 'open')
Lines.delete([book.lines[0]])
@with_transaction()
def test_line_delete_with_book_in_closed_state(self):
""" create cashbook + line, book in state=closed, delete a line
"""
pool = Pool()
Book = pool.get('cashbook.book')
Types = pool.get('cashbook.type')
Lines = pool.get('cashbook.line')
types, = Types.search([('short', '=','CAS')])
book, = Book.create([{
'name': 'Book 1',
'btype': types.id,
'lines': [('create', [{
'date': date(2022, 5, 1),
'description': 'Text 1',
}, {
'date': date(2022, 5, 2),
'description': 'Text 2',
}])],
}])
self.assertEqual(book.name, 'Book 1')
self.assertEqual(len(book.lines), 2)
self.assertEqual(book.state, 'open')
Book.wfclosed([book])
self.assertEqual(book.state, 'closed')
self.assertRaisesRegex(UserError,
"The cashbook line '05/01/2022 Text 1' cannot be deleted because the Cashbook 'Book 1' is in state 'Closed'.",
Lines.delete,
[book.lines[0]])
@with_transaction()
def test_line_delete_with_line_in_check_state(self):
""" create cashbook + line, line in state=check, delete a line
"""
pool = Pool()
Book = pool.get('cashbook.book')
Types = pool.get('cashbook.type')
Lines = pool.get('cashbook.line')
types, = Types.search([('short', '=','CAS')])
book, = Book.create([{
'name': 'Book 1',
'btype': types.id,
'lines': [('create', [{
'date': date(2022, 5, 1),
'description': 'Text 1',
}, {
'date': date(2022, 5, 2),
'description': 'Text 2',
}])],
}])
self.assertEqual(book.name, 'Book 1')
self.assertEqual(len(book.lines), 2)
self.assertEqual(book.state, 'open')
self.assertEqual(book.lines[0].state, 'edit')
Lines.wfcheck([book.lines[0]])
self.assertEqual(book.lines[0].state, 'check')
self.assertRaisesRegex(UserError,
"The cashbook line '05/01/2022 Text 1' cannot be deleted, its in state 'Checked'.",
Lines.delete,
[book.lines[0]])
# end BookTestCase

View file

@ -8,12 +8,24 @@ from trytond.transaction import Transaction
from trytond.exceptions import UserError
class AccounttypeTestCase(ModuleTestCase):
'Test account type module'
class TypeTestCase(ModuleTestCase):
'Test cashbook type module'
module = 'cashbook'
@with_transaction()
def test_accounttype_create(self):
def test_type_read_existing(self):
""" read predefined types
"""
AccType = Pool().get('cashbook.type')
t_lst = AccType.search([], order=[('name', 'ASC')])
self.assertEqual(len(t_lst), 3)
self.assertEqual(t_lst[0].rec_name, 'CAS - Cash')
self.assertEqual(t_lst[1].rec_name, 'FTD - Fixed-term deposit')
self.assertEqual(t_lst[2].rec_name, 'GIR - Giro')
@with_transaction()
def test_type_create(self):
""" create account type
"""
AccType = Pool().get('cashbook.type')
@ -34,4 +46,4 @@ class AccounttypeTestCase(ModuleTestCase):
'short': 'T1',
}])
# end AccounttypeTestCase
# end TypeTestCase

View file

@ -6,7 +6,7 @@ xml:
icon.xml
group.xml
message.xml
account_type.xml
types.xml
book.xml
line.xml
wizard_openline.xml

View file

@ -6,8 +6,8 @@
from trytond.model import ModelView, ModelSQL, fields, Unique
class AccountType(ModelSQL, ModelView):
'Account Type'
class Type(ModelSQL, ModelView):
'Cashbook Type'
__name__ = 'cashbook.type'
name = fields.Char(string='Name', required=True, translate=True)
@ -15,7 +15,7 @@ class AccountType(ModelSQL, ModelView):
@classmethod
def __setup__(cls):
super(AccountType, cls).__setup__()
super(Type, cls).__setup__()
cls._order.insert(0, ('name', 'ASC'))
t = cls.__table__()
cls._sql_constraints = [
@ -39,4 +39,4 @@ class AccountType(ModelSQL, ModelView):
('short',) + tuple(clause[1:]),
]
# end AccountType
# end Type

View file

@ -6,33 +6,33 @@ full copyright notices and license terms. -->
<data>
<!-- views -->
<record model="ir.ui.view" id="accounttype_view_list">
<record model="ir.ui.view" id="type_view_list">
<field name="model">cashbook.type</field>
<field name="type">tree</field>
<field name="priority" eval="10"/>
<field name="name">accounttype_list</field>
<field name="name">type_list</field>
</record>
<record model="ir.ui.view" id="accounttype_view_form">
<record model="ir.ui.view" id="type_view_form">
<field name="model">cashbook.type</field>
<field name="type">form</field>
<field name="priority" eval="20"/>
<field name="name">accounttype_form</field>
<field name="name">type_form</field>
</record>
<!-- action view-->
<record model="ir.action.act_window" id="act_accounttype_view">
<field name="name">Account Type</field>
<record model="ir.action.act_window" id="act_type_view">
<field name="name">Cashbook Type</field>
<field name="res_model">cashbook.type</field>
</record>
<record model="ir.action.act_window.view" id="act_accounttype_view-1">
<record model="ir.action.act_window.view" id="act_type_view-1">
<field name="sequence" eval="10"/>
<field name="view" ref="accounttype_view_list"/>
<field name="act_window" ref="act_accounttype_view"/>
<field name="view" ref="type_view_list"/>
<field name="act_window" ref="act_type_view"/>
</record>
<record model="ir.action.act_window.view" id="act_accounttype_view-2">
<record model="ir.action.act_window.view" id="act_type_view-2">
<field name="sequence" eval="20"/>
<field name="view" ref="accounttype_view_form"/>
<field name="act_window" ref="act_accounttype_view"/>
<field name="view" ref="type_view_form"/>
<field name="act_window" ref="act_type_view"/>
</record>
<!-- permission -->

View file

@ -7,4 +7,13 @@ full copyright notices and license terms. -->
<field name="name"/>
<label name="btype"/>
<field name="btype"/>
<label name="state"/>
<field name="state"/>
<group id="grpstate" col="3" colspan="2">
<button name="wfopen"/>
<button name="wfclosed"/>
<button name="wfarchive"/>
</group>
</form>

View file

@ -5,4 +5,7 @@ full copyright notices and license terms. -->
<tree>
<field name="name"/>
<field name="btype"/>
<field name="state"/>
<button name="wfopen"/>
<button name="wfclosed"/>
</tree>

View file

@ -3,8 +3,8 @@
The COPYRIGHT file at the top level of this repository contains the
full copyright notices and license terms. -->
<form col="10">
<label name="account"/>
<field name="account" widget="selection"/>
<label name="cashbook"/>
<field name="cashbook" widget="selection"/>
<label name="date_from"/>
<field name="date_from"/>

View file

@ -2,19 +2,21 @@
<!-- 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>
<label name="account"/>
<field name="account" colspan="3"/>
<form col="6">
<separator name="cashbook" colspan="4" string="Cashbook Line"/>
<separator name="state" colspan="2" string="State"/>
<field name="cashbook" colspan="2"/>
<label name="date"/>
<field name="date"/>
<label name="state"/>
<field name="state"/>
<label name="description"/>
<field name="description"/>
<group id="grpstate" col="2">
<button name="wfedit"/>
<button name="wfcheck"/>
</group>
<label name="description"/>
<field name="description" colspan="3"/>
</form>

View file

@ -3,7 +3,7 @@
The COPYRIGHT file at the top level of this repository contains the
full copyright notices and license terms. -->
<tree>
<field name="account"/>
<field name="cashbook"/>
<field name="date"/>
<field name="description"/>
<field name="state"/>

View file

@ -3,8 +3,8 @@
The COPYRIGHT file at the top level of this repository contains the
full copyright notices and license terms. -->
<form>
<label name="account"/>
<field name="account" colspan="3" widget="selection"/>
<label name="cashbook"/>
<field name="cashbook" colspan="3" widget="selection"/>
<label name="date_from"/>
<field name="date_from"/>

View file

@ -11,12 +11,12 @@ from trytond.i18n import gettext
class OpenCashBookStart(ModelView):
'Open Cashbook'
__name__ = 'cashbook.open_accountlines.start'
__name__ = 'cashbook.open_lines.start'
account = fields.Many2One(string='Account', model_name='cashbook.book',
cashbook = fields.Many2One(string='Cashbook', model_name='cashbook.book',
required=True)
checked = fields.Boolean(string='Checked', help="Show account lines in Checked-state.")
done = fields.Boolean(string='Done', help="Show account lines in Done-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")
date_from = fields.Date(string='Start Date')
date_to = fields.Date(string='End Date')
@ -33,10 +33,10 @@ class OpenCashBookStart(ModelView):
class OpenCashBook(Wizard):
'Open Cashbook'
__name__ = 'cashbook.open_accountlines'
__name__ = 'cashbook.open_lines'
start = StateView('cashbook.open_accountlines.start',
'cashbook.open_accountlines_view_form', [
start = StateView('cashbook.open_lines.start',
'cashbook.open_lines_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Open', 'open_', 'tryton-ok', default=True),
])
@ -44,15 +44,15 @@ class OpenCashBook(Wizard):
def do_open_(self, action):
action['pyson_context'] = PYSONEncoder().encode({
'account': self.start.account.id,
'cashbook': self.start.cashbook.id,
'date_from': self.start.date_from,
'date_to': self.start.date_to,
'checked': self.start.checked,
'done': self.start.done,
})
action['name'] = '%(name)s: %(account)s' % {
action['name'] = '%(name)s: %(cashbook)s' % {
'name': gettext('cashbook.msg_name_cashbook'),
'account': self.start.account.rec_name,
'cashbook': self.start.cashbook.rec_name,
}
return action, {}

View file

@ -5,16 +5,16 @@ full copyright notices and license terms. -->
<tryton>
<data>
<record model="ir.ui.view" id="open_accountlines_view_form">
<field name="model">cashbook.open_accountlines.start</field>
<record model="ir.ui.view" id="open_lines_view_form">
<field name="model">cashbook.open_lines.start</field>
<field name="type">form</field>
<field name="name">wizard_openline_form</field>
</record>
<!-- open line view by wizard to select account -->
<record model="ir.action.wizard" id="act_open_accountlines">
<record model="ir.action.wizard" id="act_open_lines">
<field name="name">Open Cashbook</field>
<field name="wiz_name">cashbook.open_accountlines</field>
<field name="wiz_name">cashbook.open_lines</field>
</record>
</data>