cashbook/line.py

952 lines
34 KiB
Python
Raw Normal View History

# -*- 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.
2022-08-08 12:31:42 +00:00
from trytond.model import ModelView, ModelSQL, Workflow, fields, Check
from trytond.pool import Pool
from trytond.pyson import Eval, If, Or, Bool
from trytond.transaction import Transaction
2022-08-08 12:31:42 +00:00
from trytond.report import Report
from trytond.exceptions import UserError
from trytond.i18n import gettext
from decimal import Decimal
from sql import Cast, Literal
from sql.functions import DatePart
from sql.conditionals import Case
2022-08-08 12:31:42 +00:00
from .book import sel_state_book
2022-08-09 13:08:41 +00:00
sel_payee = [
('cashbook.book', 'Cashbook'),
('party.party', 'Party')
]
sel_linetype = [
('edit', 'Edit'),
('check', 'Checked'),
('done', 'Done'),
]
sel_bookingtype = [
('in', 'Revenue'),
('out', 'Expense'),
2022-08-24 15:12:32 +00:00
('spin', 'Revenue Splitbooking'),
('spout', 'Expense Splitbooking'),
('mvin', 'Transfer from'),
('mvout', 'Transfer to'),
]
STATES = {
2022-08-08 12:31:42 +00:00
'readonly': Or(
Eval('state', '') != 'edit',
Eval('state_cashbook', '') != 'open',
),
}
2022-08-08 12:31:42 +00:00
DEPENDS=['state', 'state_cashbook']
class Line(Workflow, ModelSQL, ModelView):
2022-08-08 12:31:42 +00:00
'Cashbook Line'
__name__ = 'cashbook.line'
2022-08-08 12:31:42 +00:00
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)
month = fields.Function(fields.Integer(string='Month', readonly=True),
'on_change_with_month', searcher='search_month')
number = fields.Char(string='Number', readonly=True)
description = fields.Text(string='Description',
states=STATES, depends=DEPENDS)
category = fields.Many2One(string='Category',
model_name='cashbook.category', ondelete='RESTRICT',
states={
'readonly': Or(
STATES['readonly'],
Bool(Eval('bookingtype')) == False,
),
'required': Eval('bookingtype', '').in_(['in', 'out']),
'invisible': ~Eval('bookingtype', '').in_(['in', 'out']),
}, depends=DEPENDS+['bookingtype'],
domain=[
If(
Eval('bookingtype', '').in_(['in', 'mvin']),
('cattype', '=', 'in'),
('cattype', '=', 'out'),
)])
category_view = fields.Function(fields.Char(string='Category', readonly=True),
'on_change_with_category_view', searcher='search_category_view')
bookingtype = fields.Selection(string='Type', required=True,
help='Type of Booking', selection=sel_bookingtype,
states=STATES, depends=DEPENDS)
2022-08-25 13:55:24 +00:00
bookingtype_string = bookingtype.translated('bookingtype')
amount = fields.Numeric(string='Amount', digits=(16, Eval('currency_digits', 2)),
2022-08-25 13:55:24 +00:00
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)),
required=True, readonly=True, depends=['currency_digits'])
# party or cashbook as counterpart
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('bookingtype', '').in_(['mvin', 'mvout']),
'required': Eval('bookingtype', '').in_(['mvin', 'mvout']),
}, depends=DEPENDS+['bookingtype', 'owner_cashbook', 'cashbook'])
party = fields.Many2One(string='Party', model_name='party.party',
ondelete='RESTRICT',
states={
'readonly': STATES['readonly'],
2022-08-24 15:12:32 +00:00
'invisible': ~Eval('bookingtype', '').in_(['in', 'out', 'spin', 'spout']),
}, depends=DEPENDS+['bookingtype'])
payee = fields.Function(fields.Reference(string='Payee', readonly=True,
selection=sel_payee), 'on_change_with_payee', searcher='search_payee')
# link to lines created by this record
reference = fields.Many2One(string='Reference', readonly=True, select=True,
states={
'invisible': ~Bool(Eval('reference')),
}, model_name='cashbook.line', ondelete='CASCADE',
help='The current row was created by and is controlled by the reference row.')
2022-08-24 15:12:32 +00:00
references = fields.One2Many(string='References',
model_name='cashbook.line',
help='The rows are created and managed by the current record.',
states={
'invisible': ~Bool(Eval('references')),
}, field='reference', readonly=True)
2022-08-24 15:12:32 +00:00
splitlines = fields.One2Many(string='Split booking lines',
model_name='cashbook.split',
2022-08-25 13:55:24 +00:00
help='Rows with different categories form the total sum of the booking',
2022-08-24 15:12:32 +00:00
states={
2022-08-25 13:55:24 +00:00
'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'])
2022-08-11 13:00:35 +00:00
reconciliation = fields.Many2One(string='Reconciliation', readonly=True,
model_name='cashbook.recon', ondelete='SET NULL',
domain=[('cashbook.id', '=', Eval('cashbook'))],
depends=['cashbook'],
states={
'invisible': ~Bool(Eval('reconciliation')),
})
balance = fields.Function(fields.Numeric(string='Balance',
digits=(16, Eval('currency_digits', 2)),
help='Balance of the cash book up to the current line, if the default sorting applies.',
readonly=True, depends=['currency_digits']),
'on_change_with_balance')
currency = fields.Function(fields.Many2One(model_name='currency.currency',
2022-08-25 13:55:24 +00:00
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)
2022-08-08 12:31:42 +00:00
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')
owner_cashbook = fields.Function(fields.Many2One(string='Owner', readonly=True,
states={'invisible': True}, model_name='res.user'),
'on_change_with_owner_cashbook')
2022-08-09 13:08:41 +00:00
#image = fields.Binary...
@classmethod
def __setup__(cls):
super(Line, cls).__setup__()
cls._order.insert(0, ('date', 'ASC'))
cls._order.insert(0, ('state', 'ASC'))
2022-08-08 12:31:42 +00:00
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'),
('check', 'edit'),
))
cls._buttons.update({
'wfedit': {
'invisible': Eval('state', '') != 'check',
'readonly': Bool(Eval('reference')),
'depends': ['state', 'reference'],
},
'wfcheck': {
'invisible': Eval('state') != 'edit',
'depends': ['state'],
},
'wfdone': {
'invisible': Eval('state') != 'check',
'depends': ['state'],
},
})
@classmethod
@ModelView.button
@Workflow.transition('edit')
def wfedit(cls, lines):
""" edit line
"""
pool = Pool()
Line2 = pool.get('cashbook.line')
to_delete_line = []
for line in lines:
if line.reference:
if Transaction().context.get('line.allow.wfedit', False) == False:
raise UserError(gettext(
'cashbook.msg_line_denywf_by_reference',
recname = line.reference.rec_name,
cbook = line.reference.cashbook.rec_name,
))
# delete references
to_delete_line.extend(list(line.references))
if len(to_delete_line) > 0:
with Transaction().set_context({
'line.allow.wfedit': True,
}):
Line2.wfedit(to_delete_line)
Line2.delete(to_delete_line)
@classmethod
@ModelView.button
@Workflow.transition('check')
def wfcheck(cls, lines):
""" line is checked
"""
pool = Pool()
Recon = pool.get('cashbook.recon')
Line2 = pool.get('cashbook.line')
to_create_line = []
to_write_line = []
for line in lines:
# deny if date is in range of existing reconciliation
# allow cashbook-line at range-limits
if Recon.search_count([
('state', 'in', ['check', 'done']),
('cashbook.id', '=', line.cashbook.id),
('date_from', '<', line.date),
('date_to', '>', line.date),
]) > 0:
raise UserError(gettext(
'cashbook.msg_line_err_write_to_reconciled',
datetxt = Report.format_date(line.date),
))
# deny if date is at reconciliation limits and two
# reconciliations exist
if Recon.search_count([
('state', 'in', ['check', 'done']),
('cashbook.id', '=', line.cashbook.id),
['OR',
('date_from', '=', line.date),
('date_to', '=', line.date),
]
]) > 1:
raise UserError(gettext(
'cashbook.msg_line_err_write_to_reconciled',
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):
values = {
'cashbook': line.booktransf.id,
'bookingtype': 'mvin' if line.bookingtype == 'mvout' else 'mvout',
'date': line.date,
'description': line.description,
'booktransf': line.cashbook.id,
'reference': line.id,
}
values.update(line.get_amount_by_second_currency(line.booktransf.currency))
values.update(cls.get_debit_credit(values))
to_create_line.append(values)
# add number to line
if line.cashbook.number_atcheck == True:
if len(line.number or '') == 0:
to_write_line.extend([
[line],
{
'number': line.cashbook.number_sequ.get()
}])
if len(to_write_line) > 0:
Line2.write(*to_write_line)
if len(to_create_line) > 0:
new_lines = Line2.create(to_create_line)
Line2.wfcheck(new_lines)
@classmethod
@ModelView.button
@Workflow.transition('done')
def wfdone(cls, lines):
""" line is done
"""
Line2 = Pool().get('cashbook.line')
to_write_line = []
for line in lines:
# add number to line
if len(line.number or '') == 0:
to_write_line.extend([
[line],
{
'number': line.cashbook.number_sequ.get()
}])
if len(to_write_line) > 0:
Line2.write(*to_write_line)
@classmethod
def default_state(cls):
""" default: edit
"""
return 'edit'
@classmethod
def default_date(cls):
""" default: today
"""
IrDate = Pool().get('ir.date')
return IrDate.today()
@classmethod
2022-08-08 12:31:42 +00:00
def default_cashbook(cls):
""" get default from context
"""
context = Transaction().context
2022-08-08 12:31:42 +00:00
return context.get('cashbook', None)
@classmethod
def search_rec_name(cls, name, clause):
""" search in description +...
"""
return [('description',) + tuple(clause[1:])]
2022-08-08 12:31:42 +00:00
def get_rec_name(self, name):
""" short + name
"""
credit = self.credit if self.credit is not None else Decimal('0.0')
debit = self.debit if self.debit is not None else Decimal('0.0')
return '%(date)s|%(type)s|%(amount)s %(symbol)s|%(desc)s [%(category)s]' % {
2022-08-08 12:31:42 +00:00
'date': Report.format_date(self.date),
'desc': (self.description or '-')[:40],
'amount': Report.format_number(credit - debit, None),
'symbol': getattr(self.currency, 'symbol', '-'),
'category': self.category_view \
if self.bookingtype in ['in', 'out'] \
else getattr(self.booktransf, 'rec_name', '-'),
'type': gettext('cashbook.msg_line_bookingtype_%s' % self.bookingtype),
2022-08-08 12:31:42 +00:00
}
2022-08-25 13:55:24 +00:00
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 = {
2022-08-25 13:55:24 +00:00
'amount': amount if amount is not None else self.amount,
}
if to_currency.id != self.cashbook.currency.id:
with Transaction().set_context({
'date': self.date,
}):
values['amount'] = Currency.compute(
self.cashbook.currency,
self.amount,
to_currency)
return values
@staticmethod
def order_state(tables):
""" edit = 0, check/done = 1
"""
Line = Pool().get('cashbook.line')
tab_line = Line.__table__()
table, _ = tables[None]
query = tab_line.select(
Case(
(tab_line.state == 'edit', 1),
(tab_line.state.in_(['check', 'done']), 0),
else_ = 2),
where=tab_line.id==table.id
)
return [query]
@staticmethod
def order_category_view(tables):
""" order: name
"""
table, _ = tables[None]
Category = Pool().get('cashbook.category')
tab_cat = Category.__table__()
tab2 = tab_cat.select(tab_cat.name,
where=tab_cat.id==table.category
)
return [tab2]
@classmethod
def search_payee(cls, names, clause):
""" search in payee for party or cashbook
"""
return ['OR',
('party.rec_name',) + tuple(clause[1:]),
('booktransf.rec_name',) + tuple(clause[1:]),
]
2022-08-08 12:31:42 +00:00
@classmethod
def search_category_view(cls, name, clause):
""" search in category
2022-08-08 12:31:42 +00:00
"""
return [('category.rec_name',) + tuple(clause[1:])]
2022-08-08 12:31:42 +00:00
@classmethod
def search_month(cls, names, clause):
""" search in month
"""
pool = Pool()
Line = pool.get('cashbook.line')
IrDate = pool.get('ir.date')
tab_line = Line.__table__()
Operator = fields.SQL_OPERATORS[clause[1]]
dt1 = IrDate.today()
query = tab_line.select(tab_line.id,
where=Operator(
Literal(12 * dt1.year + dt1.month) - \
(Literal(12) * DatePart('year', tab_line.date) + DatePart('month', tab_line.date)),
clause[2]),
)
return [('id', 'in', query)]
2022-08-08 12:31:42 +00:00
@classmethod
def search_state_cashbook(cls, names, clause):
""" search in state of cashbook
"""
return [('cashbook.state',) + tuple(clause[1:])]
2022-08-25 13:55:24 +00:00
@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 = {
2022-08-24 15:12:32 +00:00
'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
2022-08-25 13:55:24 +00:00
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
"""
if self.cashbook:
return self.cashbook.owner.id
@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
@fields.depends('cashbook', '_parent_cashbook.currency')
def on_change_with_currency(self, name=None):
""" currency of cashbook
"""
if self.cashbook:
return self.cashbook.currency.id
@fields.depends('cashbook', '_parent_cashbook.currency')
def on_change_with_currency_digits(self, name=None):
""" currency of cashbook
"""
if self.cashbook:
return self.cashbook.currency.digits
else:
return 2
@fields.depends('id', 'date', 'cashbook', \
'_parent_cashbook.start_balance', '_parent_cashbook.id',\
2022-09-02 14:04:31 +00:00
'reconciliation', '_parent_reconciliation.start_amount',
'_parent_reconciliation.state')
def on_change_with_balance(self, name=None):
""" compute balance until current line, with current sort order,
try to use a reconciliation as start to speed up calculation
"""
pool = Pool()
Reconciliation = pool.get('cashbook.recon')
Line = pool.get('cashbook.line')
2022-09-02 14:04:31 +00:00
def get_from_last_recon(line2):
""" search last reconciliation in state 'done',
generate query
"""
query2 = []
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]),
],
])
return (query2, recons[0].end_amount)
from datetime import datetime
dt1 = datetime.now()
print('\n## on_change_with_balance', dt1)
if self.cashbook:
query = [
('cashbook.id', '=', self.cashbook.id),
]
balance = self.cashbook.start_balance
# get existing reconciliation, starting before current line
# this will speed up calculation of by-line-balance
if self.date is not None:
2022-09-02 14:04:31 +00:00
if self.reconciliation:
if self.reconciliation.state == 'done':
query.append(
('reconciliation.id', '=', self.reconciliation.id),
)
balance = self.reconciliation.start_amount
else :
(query2, balance) = get_from_last_recon(self)
query.extend(query2)
else :
(query2, balance) = get_from_last_recon(self)
query.extend(query2)
print('-- 1:', (datetime.now() - dt1))
print('-- 2:', (datetime.now() - dt1), ', query:', query)
lines = Line.search(query)
2022-09-02 14:04:31 +00:00
print('-- 3:', (datetime.now() - dt1), ', lines:',len(lines))
for line in lines:
balance += line.credit - line.debit
if line.id == self.id:
break
2022-09-02 14:04:31 +00:00
print('-- 4:', (datetime.now() - dt1))
return balance
2022-08-25 13:55:24 +00:00
@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
"""
if isinstance(values, dict):
type_ = values.get('bookingtype', None)
amount = values.get('amount', None)
else :
type_ = getattr(values, 'bookingtype', None)
amount = getattr(values, 'amount', None)
if type_:
if amount is not None:
2022-08-24 15:12:32 +00:00
if type_ in ['in', 'mvin', 'spin']:
return {
'debit': Decimal('0.0'),
'credit': amount,
}
2022-08-24 15:12:32 +00:00
elif type_ in ['out', 'mvout', 'spout']:
return {
'debit': amount,
'credit': Decimal('0.0'),
}
else :
raise ValueError('invalid "bookingtype"')
return {}
2022-08-25 13:55:24 +00:00
@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)
2022-08-25 13:55:24 +00:00
types = {
'in': ['in', 'mvin', 'spin'],
'out': ['out', 'mvout', 'spout'],
}
for line in lines:
if line.date < line.cashbook.start_date:
raise UserError(gettext(
'cashbook.msg_line_date_before_book',
datebook = Report.format_date(line.cashbook.start_date),
recname = line.rec_name,
))
2022-08-25 13:55:24 +00:00
# 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', 'number'}).intersection(values.keys())) > 0) \
2022-08-25 13:55:24 +00:00
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
"""
if default is None:
default = {}
else:
default = default.copy()
default.setdefault('number', None)
default.setdefault('state', cls.default_state())
return super(Line, cls).copy(moves, default=default)
@classmethod
def create(cls, vlist):
""" add debit/credit
"""
vlist = [x.copy() for x in vlist]
for values in vlist:
values.update(cls.get_debit_credit(values))
2022-08-25 13:55:24 +00:00
values.update(cls.clear_by_bookingtype(values))
# deny add to reconciliation if state is not 'check' or 'done'
if values.get('reconciliation', None):
if not values.get('state', '-') in ['check', 'done']:
date_txt = '-'
if values.get('date', None):
date_txt = Report.format_date(values.get('date', None))
raise UserError(gettext(
'cashbook.msg_line_deny_recon_by_state',
recname = '%(date)s|%(descr)s' % {
'date': date_txt,
'descr': values.get('description', '-'),
},
))
return super(Line, cls).create(vlist)
@classmethod
def write(cls, *args):
""" deny update if cashbook.line!='open',
add or update debit/credit
"""
actions = iter(args)
to_write = []
for lines, values in zip(actions, actions):
2022-08-25 13:55:24 +00:00
cls.check_permission_write(lines, values)
for line in lines:
if line.reconciliation:
# deny state-change to 'edit' if line is linked to reconciliation
if values.get('state', '-') == 'edit':
raise UserError(gettext(
'cashbook.msg_line_deny_stateedit_with_recon',
recname = line.rec_name,
))
# deny add to reconciliation if state is not 'check' or 'done'
if values.get('reconciliation', None):
for line in lines:
if not line.state in ['check', 'done']:
raise UserError(gettext(
'cashbook.msg_line_deny_recon_by_state',
recname = line.rec_name
))
# update debit / credit
if len(set(values.keys()).intersection(set({'amount', 'bookingtype'}))) > 0:
for line in lines:
values2 = {}
values2.update(values)
2022-08-25 13:55:24 +00:00
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']
}))
to_write.extend([lines, values2])
else :
to_write.extend([lines, values])
super(Line, cls).write(*to_write)
2022-08-08 12:31:42 +00:00
@classmethod
def delete(cls, lines):
""" deny delete if book is not 'open' or wf is not 'edit'
"""
2022-08-25 13:55:24 +00:00
cls.check_permission_delete(lines)
2022-08-08 12:31:42 +00:00
return super(Line, cls).delete(lines)
# end Line
class LineContext(ModelView):
'Line Context'
__name__ = 'cashbook.line.context'
cashbook = fields.Many2One(string='Cashbook', required=True,
model_name='cashbook.book',
states={
'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'),
('date_from', '<=', Eval('date_to')),
()),
])
date_to = fields.Date(string='End Date', depends=['date_from'],
domain=[
If(Eval('date_to') & Eval('date_from'),
('date_from', '<=', Eval('date_to')),
()),
])
checked = fields.Boolean(string='Checked',
help='Show account lines in Checked-state.')
done = fields.Boolean(string='Done',
help='Show account lines in Done-state.')
num_cashbook = fields.Function(fields.Integer(string='Number of Cashbook',
readonly=True, states={'invisible': True}),
'on_change_with_num_cashbook')
@classmethod
def default_cashbook(cls):
""" get default from context
"""
context = Transaction().context
return context.get('cashbook', None)
@classmethod
def default_date_from(cls):
""" get default from context
"""
context = Transaction().context
return context.get('date_from', None)
@classmethod
def default_date_to(cls):
""" get default from context
"""
context = Transaction().context
return context.get('date_to', None)
@classmethod
def default_checked(cls):
""" get default from context
"""
context = Transaction().context
return context.get('checked', False)
@classmethod
def default_num_cashbook(cls):
""" get default from context
"""
CashBook = Pool().get('cashbook.book')
with Transaction().set_context({
'_check_access': True,
}):
return CashBook.search_count([])
@classmethod
def default_done(cls):
""" get default from context
"""
context = Transaction().context
return context.get('done', False)
def on_change_with_num_cashbook(self, name=None):
""" get number of accessible cashbooks,
depends on user-permissions
"""
LineContext = Pool().get('cashbook.line.context')
return LineContext.default_num_cashbook()
# end LineContext