cashbook/line.py
2022-08-11 15:00:35 +02:00

502 lines
16 KiB
Python

# -*- coding: utf-8 -*-
# This file is part of the cashbook-module from m-ds for Tryton.
# The COPYRIGHT file at the top level of this repository contains the
# full copyright notices and license terms.
from trytond.model import ModelView, ModelSQL, Workflow, fields, Check
from trytond.pool import Pool
from trytond.pyson import Eval, If, Or, Bool
from trytond.transaction import Transaction
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
from .book import sel_state_book
sel_linetype = [
('edit', 'Edit'),
('check', 'Checked'),
('done', 'Done'),
]
sel_bookingtype = [
('in', 'Revenue'),
('out', 'Expense'),
('mvin', 'Transfer from'),
('mvout', 'Transfer to'),
]
STATES = {
'readonly': Or(
Eval('state', '') != 'edit',
Eval('state_cashbook', '') != 'open',
),
}
DEPENDS=['state', 'state_cashbook']
class Line(Workflow, ModelSQL, ModelView):
'Cashbook Line'
__name__ = 'cashbook.line'
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')
description = fields.Text(string='Description',
states=STATES, depends=DEPENDS)
category = fields.Many2One(string='Category', required=True,
model_name='cashbook.category', ondelete='RESTRICT',
states={
'readonly': Or(
STATES['readonly'],
Bool(Eval('bookingtype')) == False,
),
}, 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)
amount = fields.Numeric(string='Amount', digits=(16, Eval('currency_digits', 2)),
required=True, states=STATES, depends=DEPENDS+['currency_digits'])
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'])
reconciliation = fields.Many2One(string='Reconciliation', readonly=True,
model_name='cashbook.recon', ondelete='SET NULL')
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',
string="Currency"), 'on_change_with_currency')
currency_digits = fields.Function(fields.Integer(string='Currency Digits'),
'on_change_with_currency_digits')
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')
#image = fields.Binary...
@classmethod
def __setup__(cls):
super(Line, cls).__setup__()
cls._order.insert(0, ('date', 'ASC'))
cls._order.insert(0, ('state', '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'),
('check', 'edit'),
))
cls._buttons.update({
'wfedit': {
'invisible': Eval('state', '') != 'check',
'depends': ['state'],
},
'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
"""
pass
@classmethod
@ModelView.button
@Workflow.transition('check')
def wfcheck(cls, lines):
""" line is checked
"""
pass
@classmethod
@ModelView.button
@Workflow.transition('done')
def wfdone(cls, lines):
""" line is done
"""
pass
@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
def default_cashbook(cls):
""" get default from context
"""
context = Transaction().context
return context.get('cashbook', None)
@classmethod
def search_rec_name(cls, name, clause):
""" search in description +...
"""
return [('description',) + tuple(clause[1:])]
def get_rec_name(self, name):
""" short + name
"""
return '%(date)s %(desc)s' % {
'date': Report.format_date(self.date),
'desc': (self.description or '-')[:40],
}
@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]
@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.get_long_recname(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
"""
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)]
@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:])]
@fields.depends('bookingtype', 'category')
def on_change_bookingtype(self):
""" clear category if not valid type
"""
types = {
'in': ['in', 'mvin'],
'out': ['out', 'mvout'],
}
if self.bookingtype:
if self.category:
if not self.bookingtype in types.get(self.category.cattype, ''):
self.category = None
@fields.depends('cashbook', '_parent_cashbook.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', 'cashbook', '_parent_cashbook.start_balance', '_parent_cashbook.id')
def on_change_with_balance(self, name=None):
""" compute balance until current line, with current sort order
"""
Line = Pool().get('cashbook.line')
if self.cashbook:
balance = self.cashbook.start_balance
lines = Line.search([
('cashbook.id', '=', self.cashbook.id),
])
for line in lines:
balance += line.credit - line.debit
if line.id == self.id:
break
return balance
@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:
if type_ in ['in', 'mvin']:
return {
'debit': Decimal('0.0'),
'credit': amount,
}
elif type_ in ['out', 'mvout']:
return {
'debit': amount,
'credit': Decimal('0.0'),
}
else :
raise ValueError('invalid "bookingtype"')
return {}
@classmethod
def create(cls, vlist):
""" add debit/credit
"""
vlist = [x.copy() for x in vlist]
for vals in vlist:
vals.update(cls.get_debit_credit(vals))
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):
for line in lines:
if line.cashbook.state != 'open':
raise UserError(gettext(
'cashbook.msg_book_deny_write',
bookname = line.cashbook.rec_name,
state_txt = line.cashbook.state_string,
))
# debit / credit
if len(set(values.keys()).intersection(set({'amount', 'bookingtype'}))) > 0:
for line in lines:
values2 = {}
values2.update(values)
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)
@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
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