book: neues Feld 'description', sperrt btype-->None mit Zeilen + Test

line: verwendet nur kassenbücher mit typ
This commit is contained in:
Frederik Jaeckel 2022-09-16 10:15:51 +02:00
parent a2e7f192f8
commit 2acdc55efb
12 changed files with 107 additions and 11 deletions

11
book.py
View file

@ -45,6 +45,8 @@ class Book(tree(separator='/'), 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)
description = fields.Text(string='Description',
states=STATES, depends=DEPENDS)
btype = fields.Many2One(string='Type', btype = fields.Many2One(string='Type',
help='A cash book with type can contain postings. Without type is a view.', help='A cash book with type can contain postings. Without type is a view.',
model_name='cashbook.type', ondelete='RESTRICT', model_name='cashbook.type', ondelete='RESTRICT',
@ -307,6 +309,15 @@ class Book(tree(separator='/'), Workflow, ModelSQL, ModelView):
actions = iter(args) actions = iter(args)
for books, values in zip(actions, actions): for books, values in zip(actions, actions):
for book in books: for book in books:
# deny btype-->None if lines not empty
if 'btype' in values.keys():
if (values['btype'] is None) and (len(book.lines) > 0):
raise UserError(gettext(
'cashbook.msg_book_btype_with_lines',
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)):

View file

@ -17,7 +17,8 @@ field_catnamelong = fields.Boolean(string='Category: Show long name',
help='Shows the long name of the category in the Category field of a cash book line.') help='Shows the long name of the category in the Category field of a cash book line.')
field_defbook = fields.Many2One(string='Default Cashbook', field_defbook = fields.Many2One(string='Default Cashbook',
help='The default cashbook is selected when you open the booking wizard.', help='The default cashbook is selected when you open the booking wizard.',
model_name='cashbook.book', ondelete='SET NULL') model_name='cashbook.book', ondelete='SET NULL',
domain=[('btype', '!=', None)])
class Configuration(ModelSingleton, ModelSQL, ModelView, UserMultiValueMixin): class Configuration(ModelSingleton, ModelSQL, ModelView, UserMultiValueMixin):

View file

@ -931,6 +931,7 @@ 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'])
@ -993,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

@ -146,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 #
@ -434,10 +438,18 @@ 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"

View file

@ -142,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"
@ -398,10 +402,18 @@ 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"

View file

@ -110,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

@ -117,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'

View file

@ -24,6 +24,9 @@ full copyright notices and license terms. -->
<page name="reconciliations" string="Reconciliations" col="1"> <page name="reconciliations" string="Reconciliations" col="1">
<field name="reconciliations"/> <field name="reconciliations"/>
</page> </page>
<page name="description" string="Description" col="1">
<field name="description"/>
</page>
<page id="pggeneral" string="General Information" col="4"> <page id="pggeneral" string="General Information" col="4">
<label name="company"/> <label name="company"/>
<field name="company"/> <field name="company"/>

View file

@ -3,10 +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="balance" sum="Balance"/>
<field name="currency"/> <field name="currency"/>
<field name="owner"/>
<field name="state"/> <field name="state"/>
</tree> </tree>

View file

@ -4,10 +4,8 @@ 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="name"/>
<field name="btype"/>
<field name="balance" sum="Balance"/> <field name="balance" sum="Balance"/>
<field name="currency"/> <field name="currency"/>
<field name="owner"/>
<field name="state"/> <field name="state"/>
<field name="parent" tree_invisible="1"/> <field name="parent" tree_invisible="1"/>
<field name="childs" tree_invisible="1"/> <field name="childs" tree_invisible="1"/>

View file

@ -20,7 +20,7 @@ class EnterBookingStart(ModelView):
__name__ = 'cashbook.enterbooking.start' __name__ = 'cashbook.enterbooking.start'
cashbook = fields.Many2One(string='Cashbook', model_name='cashbook.book', cashbook = fields.Many2One(string='Cashbook', model_name='cashbook.book',
domain=[('id', 'in', Eval('cashbooks', []))], domain=[('id', 'in', Eval('cashbooks', [])), ('btype', '!=', None)],
depends=['cashbooks'], required=True) depends=['cashbooks'], required=True)
cashbooks = fields.One2Many(string='Cashbooks', field=None, cashbooks = fields.One2Many(string='Cashbooks', field=None,
model_name='cashbook.book', readonly=True, model_name='cashbook.book', readonly=True,
@ -137,6 +137,7 @@ class EnterBookingWizard(Wizard):
result = { result = {
'cashbooks': [x.id for x in Cashbook.search([ 'cashbooks': [x.id for x in Cashbook.search([
('state', '=', 'open'), ('state', '=', 'open'),
('btype', '!=', None),
('owner.id', '=', Transaction().user), ('owner.id', '=', Transaction().user),
])], ])],
'bookingtype': getattr(self.start, 'bookingtype', 'out'), 'bookingtype': getattr(self.start, 'bookingtype', 'out'),

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]