splitbuchungen + test

This commit is contained in:
Frederik Jaeckel 2022-08-25 15:55:24 +02:00
parent bdbc9dc27f
commit aefb5cde51
9 changed files with 618 additions and 124 deletions

318
line.py
View file

@ -81,8 +81,15 @@ class Line(Workflow, ModelSQL, ModelView):
bookingtype = fields.Selection(string='Type', required=True,
help='Type of Booking', selection=sel_bookingtype,
states=STATES, depends=DEPENDS)
bookingtype_string = bookingtype.translated('bookingtype')
amount = fields.Numeric(string='Amount', digits=(16, Eval('currency_digits', 2)),
required=True, states=STATES, depends=DEPENDS+['currency_digits'])
required=True,
states={
'readonly': Or(
STATES['readonly'],
Eval('bookingtype', '').in_(['spin', 'spout']),
),
}, depends=DEPENDS+['currency_digits', 'bookingtype'])
debit = fields.Numeric(string='Debit', digits=(16, Eval('currency_digits', 2)),
required=True, readonly=True, depends=['currency_digits'])
credit = fields.Numeric(string='Credit', digits=(16, Eval('currency_digits', 2)),
@ -123,10 +130,15 @@ class Line(Workflow, ModelSQL, ModelView):
}, field='reference', readonly=True)
splitlines = fields.One2Many(string='Split booking lines',
model_name='cashbook.split',
help='The rows are created and managed by the current record.',
help='Rows with different categories form the total sum of the booking',
states={
'invisible': ~Bool(Eval('splitlines')),
}, field='line', readonly=True)
'invisible': ~Eval('bookingtype' '').in_(['spin', 'spout']),
'readonly': Or(
~Eval('bookingtype' '').in_(['spin', 'spout']),
STATES['readonly'],
),
'required': Eval('bookingtype' '').in_(['spin', 'spout']),
}, field='line', depends=DEPENDS+['bookingtype'])
reconciliation = fields.Many2One(string='Reconciliation', readonly=True,
model_name='cashbook.recon', ondelete='SET NULL',
@ -143,9 +155,9 @@ class Line(Workflow, ModelSQL, ModelView):
'on_change_with_balance')
currency = fields.Function(fields.Many2One(model_name='currency.currency',
string="Currency"), 'on_change_with_currency')
currency_digits = fields.Function(fields.Integer(string='Currency Digits'),
'on_change_with_currency_digits')
string="Currency", readonly=True), 'on_change_with_currency')
currency_digits = fields.Function(fields.Integer(string='Currency Digits',
readonly=True), 'on_change_with_currency_digits')
state = fields.Selection(string='State', required=True, readonly=True,
select=True, selection=sel_linetype)
@ -351,14 +363,14 @@ class Line(Workflow, ModelSQL, ModelView):
'type': gettext('cashbook.msg_line_bookingtype_%s' % self.bookingtype),
}
def get_amount_by_second_currency(self, to_currency):
def get_amount_by_second_currency(self, to_currency, amount=None):
""" get amount, calculate credit/debit from currency of current
cashbook to 'to_currency'
"""
Currency = Pool().get('currency.currency')
values = {
'amount': self.amount,
'amount': amount if amount is not None else self.amount,
}
if to_currency.id != self.cashbook.currency.id:
@ -402,18 +414,6 @@ class Line(Workflow, ModelSQL, ModelView):
return [tab2]
@fields.depends('party', 'booktransf', 'bookingtype')
def on_change_with_payee(self, name=None):
""" get party or cashbook
"""
if self.bookingtype:
if self.bookingtype in ['in', 'out', 'spin', 'spout']:
if self.party:
return 'party.party,%d' % self.party.id
elif self.bookingtype in ['mvin', 'mvout']:
if self.booktransf:
return 'cashbook.book,%d' % self.booktransf.id
@classmethod
def search_payee(cls, names, clause):
""" search in payee for party or cashbook
@ -423,36 +423,12 @@ class Line(Workflow, ModelSQL, ModelView):
('booktransf.rec_name',) + tuple(clause[1:]),
]
@fields.depends('category')
def on_change_with_category_view(self, name=None):
""" show optimizef form of category for list-view
"""
Configuration = Pool().get('cashbook.configuration')
if self.category:
cfg1 = Configuration.get_singleton()
if getattr(cfg1, 'catnamelong', True) == True:
return self.category.rec_name
else :
return self.category.name
@classmethod
def search_category_view(cls, name, clause):
""" search in category
"""
return [('category.rec_name',) + tuple(clause[1:])]
@fields.depends('date')
def on_change_with_month(self, name=None):
""" get difference of month to current date
"""
IrDate = Pool().get('ir.date')
if self.date is not None:
dt1 = IrDate.today()
return (12 * dt1.year + dt1.month) - \
(12 * self.date.year + self.date.month)
@classmethod
def search_month(cls, names, clause):
""" search in month
@ -472,6 +448,75 @@ class Line(Workflow, ModelSQL, ModelView):
)
return [('id', 'in', query)]
@classmethod
def search_state_cashbook(cls, names, clause):
""" search in state of cashbook
"""
return [('cashbook.state',) + tuple(clause[1:])]
@fields.depends('amount', 'splitlines')
def on_change_splitlines(self):
""" update amount if splitlines change
"""
self.amount = sum([x.amount for x in self.splitlines if x.amount is not None])
@fields.depends('bookingtype', 'category', 'splitlines')
def on_change_bookingtype(self):
""" clear category if not valid type
"""
types = {
'in': ['in', 'mvin', 'spin'],
'out': ['out', 'mvout', 'spout'],
}
if self.bookingtype:
if self.category:
if not self.bookingtype in types.get(self.category.cattype, ''):
self.category = None
if self.bookingtype in ['spin', 'spout']:
for spline in self.splitlines:
if not self.bookingtype in types.get(getattr(spline.category, 'cattype', '-'), ''):
spline.category = None
else :
self.splitlines = []
@fields.depends('party', 'booktransf', 'bookingtype')
def on_change_with_payee(self, name=None):
""" get party or cashbook
"""
if self.bookingtype:
if self.bookingtype in ['in', 'out', 'spin', 'spout']:
if self.party:
return 'party.party,%d' % self.party.id
elif self.bookingtype in ['mvin', 'mvout']:
if self.booktransf:
return 'cashbook.book,%d' % self.booktransf.id
@fields.depends('category')
def on_change_with_category_view(self, name=None):
""" show optimizef form of category for list-view
"""
Configuration = Pool().get('cashbook.configuration')
if self.category:
cfg1 = Configuration.get_singleton()
if getattr(cfg1, 'catnamelong', True) == True:
return self.category.rec_name
else :
return self.category.name
@fields.depends('date')
def on_change_with_month(self, name=None):
""" get difference of month to current date
"""
IrDate = Pool().get('ir.date')
if self.date is not None:
dt1 = IrDate.today()
return (12 * dt1.year + dt1.month) - \
(12 * self.date.year + self.date.month)
@fields.depends('cashbook', '_parent_cashbook.owner')
def on_change_with_owner_cashbook(self, name=None):
""" get current owner
@ -486,26 +531,6 @@ class Line(Workflow, ModelSQL, ModelView):
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:])]
@fields.depends('bookingtype', 'category')
def on_change_bookingtype(self):
""" clear category if not valid type
"""
types = {
'in': ['in', 'mvin', 'spin'],
'out': ['out', 'mvout', 'spout'],
}
if self.bookingtype:
if self.category:
if not self.bookingtype in types.get(self.category.cattype, ''):
self.category = None
@fields.depends('cashbook', '_parent_cashbook.currency')
def on_change_with_currency(self, name=None):
""" currency of cashbook
@ -566,6 +591,31 @@ class Line(Workflow, ModelSQL, ModelView):
break
return balance
@classmethod
def clear_by_bookingtype(cls, values, line=None):
""" clear some fields by value of bookingtype
"""
values2 = {}
values2.update(values)
bookingtype = values2.get('bookingtype', getattr(line, 'bookingtype', None))
if (bookingtype in ['in', 'out', 'mvin', 'mvout']) and \
('splitlines' not in values2.keys()):
if line:
if len(line.splitlines) > 0:
values2['splitlines'] = [('delete', [x.id for x in line.splitlines])]
if bookingtype in ['in', 'out']:
values2['booktransf'] = None
if bookingtype in ['spin', 'spout']:
values2['category'] = None
values2['booktransf'] = None
if bookingtype in ['mvin', 'mvout']:
values2['category'] = None
return values2
@classmethod
def get_debit_credit(cls, values):
""" compute debit/credit from amount
@ -593,11 +643,33 @@ class Line(Workflow, ModelSQL, ModelView):
raise ValueError('invalid "bookingtype"')
return {}
@classmethod
def update_amount_by_splitlines(cls, lines):
""" update amounts from split-lines
"""
Line2 = Pool().get('cashbook.line')
to_write = []
for line in lines:
to_write.extend([
[line],
{
'amount': sum([x.amount for x in line.splitlines]),
}])
if len(to_write) > 0:
Line2.write(*to_write)
@classmethod
def validate(cls, lines):
""" deny date before 'start_date' of cashbook
"""
super(Line, cls).validate(lines)
types = {
'in': ['in', 'mvin', 'spin'],
'out': ['out', 'mvout', 'spout'],
}
for line in lines:
if line.date < line.cashbook.start_date:
raise UserError(gettext(
@ -606,6 +678,77 @@ class Line(Workflow, ModelSQL, ModelView):
recname = line.rec_name,
))
# line: category <--> bookingtype?
if line.category:
if not line.bookingtype in types[line.category.cattype]:
raise UserError(gettext(
'cashbook.msg_line_invalid_category',
recname = line.rec_name,
booktype = line.bookingtype_string,
))
# splitline: category <--> bookingtype?
for spline in line.splitlines:
if not line.bookingtype in types[spline.category.cattype]:
raise UserError(gettext(
'cashbook.msg_line_split_invalid_category',
recname = line.rec_name,
splitrecname = spline.rec_name,
booktype = line.bookingtype_string,
))
@classmethod
def check_permission_write(cls, lines, values={}):
""" deny update if cashbook.line!='open',
"""
for line in lines:
# deny write if cashbook is not open
if line.cashbook.state != 'open':
raise UserError(gettext(
'cashbook.msg_book_deny_write',
bookname = line.cashbook.rec_name,
state_txt = line.cashbook.state_string,
))
# deny write if reconciliation is 'check' or 'done'
if line.reconciliation:
if line.reconciliation.state == 'done':
raise UserError(gettext(
'cashbook.msg_line_deny_write_by_reconciliation',
recname = line.rec_name,
reconame = line.reconciliation.rec_name,
))
# deny write if line is not 'Edit'
if line.state != 'edit':
# allow state-update, if its the only action
if not ((len(set({'state', 'reconciliation'}).intersection(values.keys())) > 0) \
and (len(values.keys()) == 1)):
raise UserError(gettext(
'cashbook.msg_line_deny_write',
recname = line.rec_name,
state_txt = line.state_string,
))
@classmethod
def check_permission_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,
))
@classmethod
def copy(cls, lines, default=None):
""" reset values
@ -625,6 +768,7 @@ class Line(Workflow, ModelSQL, ModelView):
vlist = [x.copy() for x in vlist]
for values in vlist:
values.update(cls.get_debit_credit(values))
values.update(cls.clear_by_bookingtype(values))
# deny add to reconciliation if state is not 'check' or 'done'
if values.get('reconciliation', None):
@ -650,14 +794,9 @@ class Line(Workflow, ModelSQL, ModelView):
actions = iter(args)
to_write = []
for lines, values in zip(actions, actions):
cls.check_permission_write(lines, values)
for line in lines:
# deny write if cashbook is not open
if line.cashbook.state != 'open':
raise UserError(gettext(
'cashbook.msg_book_deny_write',
bookname = line.cashbook.rec_name,
state_txt = line.cashbook.state_string,
))
if line.reconciliation:
# deny state-change to 'edit' if line is linked to reconciliation
if values.get('state', '-') == 'edit':
@ -666,24 +805,6 @@ class Line(Workflow, ModelSQL, ModelView):
recname = line.rec_name,
))
# deny write if reconciliation is 'check' or 'done'
if line.reconciliation.state == 'done':
raise UserError(gettext(
'cashbook.msg_line_deny_write_by_reconciliation',
recname = line.rec_name,
reconame = line.reconciliation.rec_name,
))
# deny write if line is not 'Edit'
if line.state != 'edit':
# allow state-update, if its the only action
if not ((len(set({'state', 'reconciliation'}).intersection(values.keys())) > 0) \
and (len(values.keys()) == 1)):
raise UserError(gettext(
'cashbook.msg_line_deny_write',
recname = line.rec_name,
state_txt = line.state_string,
))
# deny add to reconciliation if state is not 'check' or 'done'
if values.get('reconciliation', None):
for line in lines:
@ -698,6 +819,7 @@ class Line(Workflow, ModelSQL, ModelView):
for line in lines:
values2 = {}
values2.update(values)
values2.update(cls.clear_by_bookingtype(values, line))
values2.update(cls.get_debit_credit({
x:values.get(x, getattr(line, x)) for x in ['amount', 'bookingtype']
}))
@ -710,21 +832,7 @@ class Line(Workflow, ModelSQL, ModelView):
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,
))
cls.check_permission_delete(lines)
return super(Line, cls).delete(lines)
# end Line

View file

@ -142,6 +142,14 @@ msgctxt "model:ir.message,text:msg_line_date_before_book"
msgid "The date of the cashbook line '%(recname)s' cannot be earlier than the start date '%(datebook)s' of the cashbook."
msgstr "Das Datum der Kassenbuchzeile '%(recname)s' kann nicht vor dem Anfangsdatum '%(datebook)s' des Kassenbuchs liegen."
msgctxt "model:ir.message,text:msg_line_split_invalid_category"
msgid "The category of the split booking line '%(splitrecname)s' does not match the posting type '%(booktype)s' of the line '%(recname)s'."
msgstr "Die Kategorie der Splitbuchungszeile '%(splitrecname)s' paßt nicht zum Buchungstyp '%(booktype)s' der Zeile '%(recname)s'."
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'."
msgstr "Die Kategorie der Buchungszeile '%(recname)s' paßt nicht zum Buchungstyp '%(booktype)s'."
#############
# res.group #
@ -482,6 +490,10 @@ msgctxt "model:cashbook.split,name:"
msgid "Split booking line"
msgstr "Splitbuchungszeile"
msgctxt "view:cashbook.split:"
msgid "Description"
msgstr "Beschreibung"
msgctxt "field:cashbook.split,line:"
msgid "Line"
msgstr "Zeile"
@ -494,6 +506,10 @@ msgctxt "field:cashbook.split,category:"
msgid "Category"
msgstr "Kategorie"
msgctxt "field:cashbook.split,category_view:"
msgid "Category"
msgstr "Kategorie"
msgctxt "field:cashbook.split,amount:"
msgid "Amount"
msgstr "Betrag"
@ -582,6 +598,14 @@ msgctxt "view:cashbook.line:"
msgid "State"
msgstr "Status"
msgctxt "view:cashbook.line:"
msgid "Split booking lines"
msgstr "Splitbuchungszeilen"
msgctxt "view:cashbook.line:"
msgid "References"
msgstr "Referenzen"
msgctxt "field:cashbook.line,cashbook:"
msgid "Cashbook"
msgstr "Kassenbuch"
@ -734,6 +758,14 @@ msgctxt "field:cashbook.line,number:"
msgid "Number"
msgstr "Nummer"
msgctxt "field:cashbook.line,splitlines:"
msgid "Split booking lines"
msgstr "Splitbuchungszeilen"
msgctxt "help:cashbook.line,splitlines:"
msgid "Rows with different categories form the total sum of the booking"
msgstr "Zeilen mit unterschiedlichen Kategorien bilden die Gesamtsumme der Buchung"
#################
# cashbook.type #

View file

@ -106,10 +106,18 @@ msgctxt "model:ir.message,text:msg_line_bookingtype_in"
msgid "Rev"
msgstr "Rev"
msgctxt "model:ir.message,text:msg_line_bookingtype_spin"
msgid "Rev/Sp"
msgstr "Rev/Sp"
msgctxt "model:ir.message,text:msg_line_bookingtype_out"
msgid "Exp"
msgstr "Exp"
msgctxt "model:ir.message,text:msg_line_bookingtype_spout"
msgid "Exp/Sp"
msgstr "Exp/Sp"
msgctxt "model:ir.message,text:msg_line_bookingtype_mvin"
msgid "from"
msgstr "from"
@ -130,6 +138,14 @@ msgctxt "model:ir.message,text:msg_line_date_before_book"
msgid "The date of the cashbook line '%(recname)s' cannot be earlier than the start date '%(datebook)s' of the cashbook."
msgstr "The date of the cashbook line '%(recname)s' cannot be earlier than the start date '%(datebook)s' of the cashbook."
msgctxt "model:ir.message,text:msg_line_split_invalid_category"
msgid "The category of the split booking line '%(splitrecname)s' does not match the posting type '%(booktype)s' of the line '%(recname)s'."
msgstr "The category of the split booking line '%(splitrecname)s' does not match the posting type '%(booktype)s' of the line '%(recname)s'."
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'."
msgstr "The category of the booking line '%(recname)s' does not match the posting type '%(booktype)s'."
msgctxt "model:res.group,name:group_cashbook"
msgid "Cashbook"
msgstr "Cashbook"
@ -154,6 +170,10 @@ msgctxt "model:ir.rule.group,name:rg_book_read_nonowner"
msgid "Observers and Reviewers: Cashbook read"
msgstr "Observers and Reviewers: Cashbook read"
msgctxt "model:ir.rule.group,name:rg_split_write_adm"
msgid "Administrators: Splitbooking line read/write"
msgstr "Administrators: Splitbooking line read/write"
msgctxt "model:ir.rule.group,name:rg_book_write_adm"
msgid "Administrators: Cashbook read/write"
msgstr "Administrators: Cashbook read/write"
@ -162,10 +182,18 @@ msgctxt "model:ir.rule.group,name:rg_line_write_adm"
msgid "Administrators: Cashbook line read/write"
msgstr "Administrators: Cashbook line read/write"
msgctxt "model:ir.rule.group,name:rg_split_write"
msgid "Owners and reviewers: Splitbooking line write"
msgstr "Owners and reviewers: Splitbooking line write"
msgctxt "model:ir.rule.group,name:rg_line_write"
msgid "Owners and reviewers: Cashbook line write"
msgstr "Owners and reviewers: Cashbook line write"
msgctxt "model:ir.rule.group,name:rg_split_read"
msgid "Observer: Splitbooking line read"
msgstr "Observer: Splitbooking line read"
msgctxt "model:ir.rule.group,name:rg_line_read"
msgid "Observer: Cashbook line read"
msgstr "Observer: Cashbook line read"
@ -174,6 +202,10 @@ msgctxt "model:ir.rule.group,name:rg_line_read"
msgid "User in companies"
msgstr "User in companies"
msgctxt "model:ir.rule.group,name:rg_split_companies"
msgid "User in companies"
msgstr "User in companies"
msgctxt "model:ir.rule.group,name:rg_type_companies"
msgid "User in companies"
msgstr "User in companies"
@ -414,6 +446,90 @@ 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."
msgstr "The numbering of the lines is done in the step Check. If the check mark is inactive, this happens with Done."
msgctxt "model:cashbook.split,name:"
msgid "Split booking line"
msgstr "Split booking line"
msgctxt "view:cashbook.split:"
msgid "Description"
msgstr "Description"
msgctxt "field:cashbook.split,line:"
msgid "Line"
msgstr "Line"
msgctxt "field:cashbook.split,description:"
msgid "Description"
msgstr "Description"
msgctxt "field:cashbook.split,category:"
msgid "Category"
msgstr "Category"
msgctxt "field:cashbook.split,category_view:"
msgid "Category"
msgstr "Category"
msgctxt "field:cashbook.split,amount:"
msgid "Amount"
msgstr "Amount"
msgctxt "field:cashbook.split,currency:"
msgid "Currency"
msgstr "Currency"
msgctxt "field:cashbook.split,currency_digits:"
msgid "Currency Digits"
msgstr "Currency Digits"
msgctxt "field:cashbook.split,bookingtype:"
msgid "Type"
msgstr "Type"
msgctxt "selection:cashbook.split,bookingtype:"
msgid "Revenue"
msgstr "Revenue"
msgctxt "selection:cashbook.split,bookingtype:"
msgid "Revenue Splitbooking"
msgstr "Revenue Splitbooking"
msgctxt "selection:cashbook.split,bookingtype:"
msgid "Expense"
msgstr "Expense"
msgctxt "selection:cashbook.split,bookingtype:"
msgid "Expense Splitbooking"
msgstr "Expense Splitbooking"
msgctxt "selection:cashbook.split,bookingtype:"
msgid "Transfer from"
msgstr "Transfer from"
msgctxt "selection:cashbook.split,bookingtype:"
msgid "Transfer to"
msgstr "Transfer to"
msgctxt "field:cashbook.split,state:"
msgid "State"
msgstr "State"
msgctxt "selection:cashbook.split,state:"
msgid "Edit"
msgstr "Edit"
msgctxt "selection:cashbook.split,state:"
msgid "Checked"
msgstr "Checked"
msgctxt "selection:cashbook.split,state:"
msgid "Done"
msgstr "Done"
msgctxt "field:cashbook.split,state_cashbook:"
msgid "State of Cashbook"
msgstr "State of Cashbook"
msgctxt "model:cashbook.line,name:"
msgid "Cashbook Line"
msgstr "Cashbook Line"
@ -438,6 +554,14 @@ msgctxt "view:cashbook.line:"
msgid "State"
msgstr "State"
msgctxt "view:cashbook.line:"
msgid "Split booking lines"
msgstr "Split booking lines"
msgctxt "view:cashbook.line:"
msgid "References"
msgstr "References"
msgctxt "field:cashbook.line,cashbook:"
msgid "Cashbook"
msgstr "Cashbook"
@ -490,10 +614,18 @@ msgctxt "selection:cashbook.line,bookingtype:"
msgid "Revenue"
msgstr "Revenue"
msgctxt "selection:cashbook.line,bookingtype:"
msgid "Revenue Splitbooking"
msgstr "Revenue Splitbooking"
msgctxt "selection:cashbook.line,bookingtype:"
msgid "Expense"
msgstr "Expense"
msgctxt "selection:cashbook.line,bookingtype:"
msgid "Expense Splitbooking"
msgstr "Expense Splitbooking"
msgctxt "selection:cashbook.line,bookingtype:"
msgid "Transfer from"
msgstr "Transfer from"
@ -582,6 +714,14 @@ msgctxt "field:cashbook.line,number:"
msgid "Number"
msgstr "Number"
msgctxt "field:cashbook.line,splitlines:"
msgid "Split booking lines"
msgstr "Split booking lines"
msgctxt "help:cashbook.line,splitlines:"
msgid "Rows with different categories form the total sum of the booking"
msgstr "Rows with different categories form the total sum of the booking"
msgctxt "model:cashbook.type,name:"
msgid "Cashbook Type"
msgstr "Cashbook Type"

View file

@ -107,6 +107,12 @@ full copyright notices and license terms. -->
<record model="ir.message" id="msg_line_date_before_book">
<field name="text">The date of the cashbook line '%(recname)s' cannot be earlier than the start date '%(datebook)s' of the cashbook.</field>
</record>
<record model="ir.message" id="msg_line_split_invalid_category">
<field name="text">The category of the split booking line '%(splitrecname)s' does not match the posting type '%(booktype)s' of the line '%(recname)s'.</field>
</record>
<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>
</record>
</data>
</tryton>

View file

@ -7,6 +7,9 @@
from trytond.model import ModelView, ModelSQL, Workflow, fields, Check
from trytond.pool import Pool
from trytond.pyson import Eval, If
from trytond.report import Report
from trytond.i18n import gettext
from trytond.transaction import Transaction
from .line import sel_linetype, sel_bookingtype, STATES, DEPENDS
from .book import sel_state_book
@ -22,28 +25,74 @@ class SplitLine(ModelSQL, ModelView):
states=STATES, depends=DEPENDS)
category = fields.Many2One(string='Category',
model_name='cashbook.category', ondelete='RESTRICT',
states=STATES, depends=DEPENDS+['bookingtype'],
required=True,
states=STATES, depends=DEPENDS+['bookingtype'], required=True,
domain=[
If(
Eval('bookingtype', '').in_(['in', 'mvin']),
Eval('bookingtype', '') == 'spin',
('cattype', '=', 'in'),
('cattype', '=', 'out'),
)])
category_view = fields.Function(fields.Char(string='Category', readonly=True),
'on_change_with_category_view')
amount = fields.Numeric(string='Amount', digits=(16, Eval('currency_digits', 2)),
required=True, states=STATES, depends=DEPENDS+['currency_digits'])
currency = fields.Function(fields.Many2One(model_name='currency.currency',
string="Currency"), 'on_change_with_currency')
currency_digits = fields.Function(fields.Integer(string='Currency Digits'),
'on_change_with_currency_digits')
string="Currency", readonly=True), 'on_change_with_currency')
currency_digits = fields.Function(fields.Integer(string='Currency Digits',
readonly=True), 'on_change_with_currency_digits')
bookingtype = fields.Function(fields.Selection(string='Type', readonly=True,
selection=sel_bookingtype), 'on_change_with_bookingtype')
state = fields.Function(fields.Selection(string='State', readonly=True,
selection=sel_linetype), 'on_change_with_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')
'on_change_with_state_cashbook')
def get_rec_name(self, name):
""" short + name
"""
return '%(type)s|%(amount)s %(symbol)s|%(desc)s [%(category)s]' % {
'desc': (self.description or '-')[:40],
'amount': Report.format_number(self.amount, None),
'symbol': getattr(self.currency, 'symbol', '-'),
'category': self.category_view,
'type': gettext('cashbook.msg_line_bookingtype_%s' % self.line.bookingtype),
}
def get_amount_by_second_currency(self, to_currency):
""" get amount, calculate credit/debit from currency of current
cashbook to 'to_currency'
"""
Currency = Pool().get('currency.currency')
values = {
'amount': self.amount,
}
if to_currency.id != self.line.cashbook.currency.id:
with Transaction().set_context({
'date': self.line.date,
}):
values['amount'] = Currency.compute(
self.line.cashbook.currency,
self.amount,
to_currency)
return values
@fields.depends('category')
def on_change_with_category_view(self, name=None):
""" show optimizef form of category for list-view
"""
Configuration = Pool().get('cashbook.configuration')
if self.category:
cfg1 = Configuration.get_singleton()
if getattr(cfg1, 'catnamelong', True) == True:
return self.category.rec_name
else :
return self.category.name
@fields.depends('line', '_parent_line.state')
def on_change_with_state(self, name=None):
@ -82,4 +131,51 @@ class SplitLine(ModelSQL, ModelView):
else:
return 2
@classmethod
def create(cls, vlist):
""" add debit/credit
"""
Line2 = Pool().get('cashbook.line')
vlist = [x.copy() for x in vlist]
records = super(SplitLine, cls).create(vlist)
to_update_line = []
for record in records:
if not record.line in to_update_line:
to_update_line.append(record.line)
if len(to_update_line) > 0:
Line2.update_amount_by_splitlines(to_update_line)
@classmethod
def write(cls, *args):
""" deny update if cashbook.line!='open',
add or update debit/credit
"""
Line2 = Pool().get('cashbook.line')
actions = iter(args)
to_update_line = []
for records, values in zip(actions, actions):
Line2.check_permission_write([x.line for x in records])
if 'amount' in values.keys():
for record in records:
if not record.line in to_update_line:
to_update_line.append(record.line)
super(SplitLine, cls).write(*args)
if len(to_update_line) > 0:
Line2.update_amount_by_splitlines(to_update_line)
@classmethod
def delete(cls, splitlines):
""" deny delete if book is not 'open' or wf is not 'edit'
"""
Line2 = Pool().get('cashbook.line')
Line2.check_permission_delete([x.line for x in splitlines])
return super(SplitLine, cls).delete(splitlines)
# end SplitLine

View file

@ -7,6 +7,7 @@ import unittest
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
from trytond.modules.cashbook.tests.test_splitline import SplitLineTestCase
from trytond.modules.cashbook.tests.test_config import ConfigTestCase
from trytond.modules.cashbook.tests.test_category import CategoryTestCase
from trytond.modules.cashbook.tests.test_reconciliation import ReconTestCase
@ -19,6 +20,7 @@ class CashbookTestCase(\
CategoryTestCase,\
ConfigTestCase,\
LineTestCase,
SplitLineTestCase,
BookTestCase,
TypeTestCase,
):

109
tests/test_splitline.py Normal file
View file

@ -0,0 +1,109 @@
# -*- 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 unittest.mock import MagicMock
from decimal import Decimal
class SplitLineTestCase(ModuleTestCase):
'Test split line module'
module = 'cashbook'
@with_transaction()
def test_splitline_check_clear_by_bookingtype(self):
""" add book, line, category, set line to 'in',
then update to 'spin'
"""
pool = Pool()
Book = pool.get('cashbook.book')
Lines = pool.get('cashbook.line')
types = self.prep_type()
category1 = self.prep_category(cattype='in')
category2 = self.prep_category(name='Cat2', 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': 'Text 1',
'category': category1.id,
'bookingtype': 'in',
'amount': Decimal('1.0'),
'party': party.id,
}])],
}])
self.assertEqual(len(book.lines), 1)
self.assertEqual(book.lines[0].rec_name, '05/01/2022|Rev|1.00 usd|Text 1 [Cat1]')
self.assertEqual(book.lines[0].amount, Decimal('1.0'))
self.assertEqual(book.lines[0].category.rec_name, 'Cat1')
Lines.write(*[
[book.lines[0]],
{
'bookingtype': 'spin',
'splitlines': [('create', [{
'amount': Decimal('5.0'),
'category': category1.id,
'description': 'line 1'
}, {
'amount': Decimal('2.0'),
'category': category2.id,
'description': 'line 2',
}])],
}])
self.assertEqual(book.lines[0].rec_name, '05/01/2022|Rev/Sp|7.00 usd|Text 1 [-]')
self.assertEqual(book.lines[0].amount, Decimal('7.0'))
self.assertEqual(book.lines[0].category, None)
self.assertEqual(len(book.lines[0].splitlines), 2)
self.assertEqual(book.lines[0].splitlines[0].amount, Decimal('5.0'))
self.assertEqual(book.lines[0].splitlines[0].category.rec_name, 'Cat1')
self.assertEqual(book.lines[0].splitlines[0].description, 'line 1')
self.assertEqual(book.lines[0].splitlines[0].rec_name, 'Rev/Sp|5.00 usd|line 1 [Cat1]')
self.assertEqual(book.lines[0].splitlines[1].amount, Decimal('2.0'))
self.assertEqual(book.lines[0].splitlines[1].category.rec_name, 'Cat2')
self.assertEqual(book.lines[0].splitlines[1].description, 'line 2')
self.assertEqual(book.lines[0].splitlines[1].rec_name, 'Rev/Sp|2.00 usd|line 2 [Cat2]')
Lines.write(*[
[book.lines[0]],
{
'splitlines': [('write',
[book.lines[0].splitlines[0]],
{
'amount': Decimal('3.5'),
})],
}])
self.assertEqual(book.lines[0].splitlines[0].amount, Decimal('3.5'))
self.assertEqual(book.lines[0].splitlines[1].amount, Decimal('2.0'))
self.assertEqual(book.lines[0].amount, Decimal('5.5'))
Lines.write(*[
[book.lines[0]],
{
'bookingtype': 'in',
'amount': Decimal('7.5'),
'category': category2.id,
}])
self.assertEqual(book.lines[0].rec_name, '05/01/2022|Rev|7.50 usd|Text 1 [Cat2]')
self.assertEqual(book.lines[0].category.rec_name, 'Cat2')
self.assertEqual(len(book.lines[0].splitlines), 0)
# end SplitLineTestCase

View file

@ -34,20 +34,21 @@ full copyright notices and license terms. -->
<label name="category"/>
<field name="category"/>
<newline/>
<label name="booktransf"/>
<field name="booktransf"/>
<newline/>
<group name="description" colspan="4" col="1" string="Description" yexpand="1">
<field name="description" yfill="1"/>
</group>
<newline/>
<field name="splitlines" colspan="4" yexpand="1"/>
<newline/>
<field name="references" colspan="4" yexpand="1"/>
<newline/>
<notebook colspan="6">
<page name="splitlines" col="1" string="Split booking lines">
<field name="splitlines"/>
</page>
<page name="description" col="1" string="Description">
<field name="description"/>
</page>
<page name="references" col="1" string="References">
<field name="references"/>
</page>
</notebook>
<field name="owner_cashbook"/>
</form>

View file

@ -2,9 +2,9 @@
<!-- 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>
<tree editable="1">
<field name="line" tree_invisible="1"/>
<field name="category_view"/>
<field name="category"/>
<field name="description" expand="1"/>
<field name="amount" sum="Amount"/>
<field name="currency"/>