2022-08-05 10:02:04 +00:00
|
|
|
# -*- 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-09-15 21:49:54 +00:00
|
|
|
from trytond.model import Workflow, ModelView, ModelSQL, fields, Check, tree
|
2022-08-16 14:24:07 +00:00
|
|
|
from trytond.pyson import Eval, Or, Bool, Id
|
2022-08-08 12:31:42 +00:00
|
|
|
from trytond.exceptions import UserError
|
|
|
|
from trytond.i18n import gettext
|
2022-08-08 15:31:16 +00:00
|
|
|
from trytond.transaction import Transaction
|
book, line, category, types: berechtigung für company-user + test,
book: währung neu,
line: buchungstyp, betrag, credit, debit, währung, sortierung
2022-08-10 14:30:08 +00:00
|
|
|
from trytond.pool import Pool
|
2022-08-11 11:01:53 +00:00
|
|
|
from trytond.report import Report
|
|
|
|
from decimal import Decimal
|
|
|
|
from sql.aggregate import Sum
|
|
|
|
from sql.conditionals import Case
|
2022-08-05 10:02:04 +00:00
|
|
|
|
|
|
|
|
2022-08-08 12:31:42 +00:00
|
|
|
STATES = {
|
|
|
|
'readonly': Eval('state', '') != 'open',
|
|
|
|
}
|
|
|
|
DEPENDS=['state']
|
|
|
|
|
2022-09-15 21:49:54 +00:00
|
|
|
# states in case of 'btype'!=None
|
|
|
|
STATES2 = {
|
|
|
|
'readonly': Or(
|
|
|
|
Eval('state', '') != 'open',
|
|
|
|
~Bool(Eval('btype')),
|
|
|
|
),
|
|
|
|
'invisible': ~Bool(Eval('btype')),
|
|
|
|
}
|
2022-09-21 15:01:14 +00:00
|
|
|
STATES3 = {}
|
|
|
|
STATES3.update(STATES2)
|
|
|
|
STATES3['required'] = ~STATES2['invisible']
|
2022-09-15 21:49:54 +00:00
|
|
|
DEPENDS2 = ['state', 'btype']
|
|
|
|
|
2022-08-08 12:31:42 +00:00
|
|
|
sel_state_book = [
|
|
|
|
('open', 'Open'),
|
|
|
|
('closed', 'Closed'),
|
|
|
|
('archive', 'Archive'),
|
|
|
|
]
|
|
|
|
|
|
|
|
|
2022-09-15 21:49:54 +00:00
|
|
|
class Book(tree(separator='/'), Workflow, ModelSQL, ModelView):
|
2022-08-08 12:31:42 +00:00
|
|
|
'Cashbook'
|
2022-08-05 10:02:04 +00:00
|
|
|
__name__ = 'cashbook.book'
|
|
|
|
|
book, line, category, types: berechtigung für company-user + test,
book: währung neu,
line: buchungstyp, betrag, credit, debit, währung, sortierung
2022-08-10 14:30:08 +00:00
|
|
|
company = fields.Many2One(string='Company', model_name='company.company',
|
|
|
|
required=True, ondelete="RESTRICT")
|
2022-08-08 12:31:42 +00:00
|
|
|
name = fields.Char(string='Name', required=True,
|
|
|
|
states=STATES, depends=DEPENDS)
|
2022-09-16 08:15:51 +00:00
|
|
|
description = fields.Text(string='Description',
|
|
|
|
states=STATES, depends=DEPENDS)
|
2022-09-15 21:49:54 +00:00
|
|
|
btype = fields.Many2One(string='Type',
|
|
|
|
help='A cash book with type can contain postings. Without type is a view.',
|
2022-08-08 12:31:42 +00:00
|
|
|
model_name='cashbook.type', ondelete='RESTRICT',
|
2022-09-15 21:49:54 +00:00
|
|
|
states={
|
|
|
|
'readonly': Or(
|
|
|
|
STATES['readonly'],
|
|
|
|
Bool(Eval('lines')),
|
|
|
|
),
|
|
|
|
}, depends=DEPENDS+['lines'])
|
2022-08-08 15:31:16 +00:00
|
|
|
owner = fields.Many2One(string='Owner', required=True, select=True,
|
2022-08-11 13:00:35 +00:00
|
|
|
model_name='res.user', ondelete='SET NULL',
|
|
|
|
states=STATES, depends=DEPENDS)
|
2022-08-08 15:31:16 +00:00
|
|
|
reviewer = fields.Many2One(string='Reviewer', select=True,
|
|
|
|
help='Group of users who have write access to the cashbook.',
|
2022-08-11 13:00:35 +00:00
|
|
|
model_name='res.group', ondelete='SET NULL',
|
|
|
|
states=STATES, depends=DEPENDS)
|
2022-08-08 15:31:16 +00:00
|
|
|
observer = fields.Many2One(string='Observer', select=True,
|
|
|
|
help='Group of users who have read-only access to the cashbook.',
|
2022-08-11 13:00:35 +00:00
|
|
|
model_name='res.group', ondelete='SET NULL',
|
|
|
|
states=STATES, depends=DEPENDS)
|
2022-08-08 12:31:42 +00:00
|
|
|
lines = fields.One2Many(string='Lines', field='cashbook',
|
|
|
|
model_name='cashbook.line',
|
|
|
|
states=STATES, depends=DEPENDS)
|
2022-08-11 13:00:35 +00:00
|
|
|
reconciliations = fields.One2Many(string='Reconciliations',
|
|
|
|
field='cashbook', model_name='cashbook.recon',
|
2022-09-15 21:49:54 +00:00
|
|
|
states=STATES2, depends=DEPENDS2)
|
|
|
|
number_sequ = fields.Many2One(string='Line numbering',
|
2022-08-16 14:24:07 +00:00
|
|
|
help='Number sequence for numbering of the cash book lines.',
|
|
|
|
model_name='ir.sequence',
|
|
|
|
domain=[
|
|
|
|
('sequence_type', '=', Id('cashbook', 'sequence_type_cashbook_line')),
|
|
|
|
['OR',
|
|
|
|
('company', '=', None),
|
|
|
|
('company', '=', Eval('company', -1)),
|
|
|
|
],
|
|
|
|
],
|
2022-09-21 15:01:14 +00:00
|
|
|
states=STATES3, depends=DEPENDS2+['company'])
|
2022-09-15 21:49:54 +00:00
|
|
|
number_atcheck = fields.Boolean(string="number when 'Checking'",
|
|
|
|
help="The numbering of the lines is done in the step Check. If the check mark is inactive, this happens with Done.",
|
|
|
|
states=STATES2, depends=DEPENDS2)
|
|
|
|
start_date = fields.Date(string='Initial Date',
|
2022-08-11 11:01:53 +00:00
|
|
|
states={
|
|
|
|
'readonly': Or(
|
2022-09-15 21:49:54 +00:00
|
|
|
STATES2['readonly'],
|
2022-08-11 11:01:53 +00:00
|
|
|
Bool(Eval('lines')),
|
|
|
|
),
|
2022-09-15 21:49:54 +00:00
|
|
|
'invisible': STATES2['invisible'],
|
2022-09-21 15:01:14 +00:00
|
|
|
'required': ~STATES2['invisible'],
|
2022-09-15 21:49:54 +00:00
|
|
|
}, depends=DEPENDS2+['lines'])
|
2022-08-31 08:17:29 +00:00
|
|
|
balance = fields.Function(fields.Numeric(string='Balance', readonly=True,
|
|
|
|
digits=(16, Eval('currency_digits', 2)),
|
|
|
|
depends=['currency_digits']), 'on_change_with_balance')
|
2022-09-15 21:49:54 +00:00
|
|
|
currency = fields.Many2One(string='Currency',
|
book, line, category, types: berechtigung für company-user + test,
book: währung neu,
line: buchungstyp, betrag, credit, debit, währung, sortierung
2022-08-10 14:30:08 +00:00
|
|
|
model_name='currency.currency',
|
|
|
|
states={
|
|
|
|
'readonly': Or(
|
2022-09-15 21:49:54 +00:00
|
|
|
STATES2['readonly'],
|
book, line, category, types: berechtigung für company-user + test,
book: währung neu,
line: buchungstyp, betrag, credit, debit, währung, sortierung
2022-08-10 14:30:08 +00:00
|
|
|
Bool(Eval('lines', [])),
|
|
|
|
),
|
2022-09-15 21:49:54 +00:00
|
|
|
'invisible': STATES2['invisible'],
|
2022-09-21 15:01:14 +00:00
|
|
|
'required': ~STATES2['invisible'],
|
2022-09-15 21:49:54 +00:00
|
|
|
}, depends=DEPENDS2+['lines'])
|
2022-08-31 08:17:29 +00:00
|
|
|
currency_digits = fields.Function(fields.Integer(string='Currency Digits',
|
|
|
|
readonly=True), 'on_change_with_currency_digits')
|
2022-08-08 12:31:42 +00:00
|
|
|
state = fields.Selection(string='State', required=True,
|
|
|
|
readonly=True, selection=sel_state_book)
|
|
|
|
state_string = state.translated('state')
|
2022-08-05 10:02:04 +00:00
|
|
|
|
2022-09-15 21:49:54 +00:00
|
|
|
parent = fields.Many2One(string="Parent",
|
|
|
|
model_name='cashbook.book', ondelete='RESTRICT',
|
|
|
|
left='left', right='right')
|
|
|
|
childs = fields.One2Many(string='Children', field='parent',
|
|
|
|
model_name='cashbook.book')
|
|
|
|
left = fields.Integer(string='Left', required=True, select=True)
|
|
|
|
right = fields.Integer(string='Right', required=True, select=True)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def __register__(cls, module_name):
|
|
|
|
super(Book, cls).__register__(module_name)
|
|
|
|
|
|
|
|
table = cls.__table_handler__(module_name)
|
|
|
|
table.drop_column('start_balance')
|
|
|
|
|
2022-08-05 10:02:04 +00:00
|
|
|
@classmethod
|
|
|
|
def __setup__(cls):
|
|
|
|
super(Book, cls).__setup__()
|
|
|
|
cls._order.insert(0, ('name', 'ASC'))
|
2022-08-11 11:01:53 +00:00
|
|
|
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_(['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'],
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
2022-09-15 21:49:54 +00:00
|
|
|
@staticmethod
|
|
|
|
def default_left():
|
|
|
|
return 0
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def default_right():
|
|
|
|
return 0
|
|
|
|
|
2022-08-16 14:24:07 +00:00
|
|
|
@classmethod
|
|
|
|
def default_number_atcheck(cls):
|
|
|
|
return True
|
|
|
|
|
book, line, category, types: berechtigung für company-user + test,
book: währung neu,
line: buchungstyp, betrag, credit, debit, währung, sortierung
2022-08-10 14:30:08 +00:00
|
|
|
@classmethod
|
|
|
|
def default_currency(cls):
|
|
|
|
""" currency of company
|
|
|
|
"""
|
|
|
|
Company = Pool().get('company.company')
|
|
|
|
|
|
|
|
company = cls.default_company()
|
|
|
|
if company:
|
|
|
|
company = Company(company)
|
|
|
|
if company.currency:
|
|
|
|
return company.currency.id
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def default_company():
|
|
|
|
return Transaction().context.get('company') or None
|
|
|
|
|
2022-08-18 09:58:05 +00:00
|
|
|
@classmethod
|
|
|
|
def default_start_date(cls):
|
|
|
|
""" today
|
|
|
|
"""
|
|
|
|
IrDate = Pool().get('ir.date')
|
|
|
|
return IrDate.today()
|
|
|
|
|
2022-08-08 12:31:42 +00:00
|
|
|
@classmethod
|
|
|
|
def default_state(cls):
|
|
|
|
return 'open'
|
|
|
|
|
2022-08-08 15:31:16 +00:00
|
|
|
@classmethod
|
|
|
|
def default_owner(cls):
|
|
|
|
""" default: current user
|
|
|
|
"""
|
|
|
|
return Transaction().user
|
|
|
|
|
2022-08-11 11:01:53 +00:00
|
|
|
@staticmethod
|
|
|
|
def order_state(tables):
|
|
|
|
""" edit = 0, check/done = 1
|
|
|
|
"""
|
|
|
|
Book2 = Pool().get('cashbook.book')
|
|
|
|
tab_book = Book2.__table__()
|
|
|
|
table, _ = tables[None]
|
|
|
|
|
|
|
|
query = tab_book.select(
|
|
|
|
Case(
|
|
|
|
(tab_book.state == 'open', 0),
|
|
|
|
else_ = 1),
|
|
|
|
where=tab_book.id==table.id
|
|
|
|
)
|
|
|
|
return [query]
|
|
|
|
|
|
|
|
def get_rec_name(self, name):
|
|
|
|
""" name, balance, state
|
|
|
|
"""
|
2022-09-15 21:49:54 +00:00
|
|
|
recname = super(Book, self).get_rec_name(name)
|
|
|
|
if self.btype:
|
|
|
|
return '%(name)s | %(balance)s %(symbol)s | %(state)s' % {
|
|
|
|
'name': recname or '-',
|
|
|
|
'balance': Report.format_number(self.balance or 0.0, None),
|
|
|
|
'symbol': getattr(self.currency, 'symbol', '-'),
|
|
|
|
'state': self.state_string,
|
|
|
|
}
|
|
|
|
return recname
|
2022-08-11 11:01:53 +00:00
|
|
|
|
2022-08-31 08:17:29 +00:00
|
|
|
@fields.depends('currency')
|
|
|
|
def on_change_with_currency_digits(self, name=None):
|
|
|
|
""" currency of cashbook
|
|
|
|
"""
|
|
|
|
if self.currency:
|
|
|
|
return self.currency.digits
|
|
|
|
else:
|
|
|
|
return 2
|
|
|
|
|
2022-09-15 21:49:54 +00:00
|
|
|
@fields.depends('id')
|
2022-08-11 11:01:53 +00:00
|
|
|
def on_change_with_balance(self, name=None):
|
|
|
|
""" compute balance
|
|
|
|
"""
|
2022-09-15 21:49:54 +00:00
|
|
|
pool = Pool()
|
|
|
|
Book2 = pool.get('cashbook.book')
|
|
|
|
Line = pool.get('cashbook.line')
|
2022-08-11 11:01:53 +00:00
|
|
|
tab_line = Line.__table__()
|
|
|
|
cursor = Transaction().connection.cursor()
|
|
|
|
|
2022-09-15 21:49:54 +00:00
|
|
|
line_query = Line.search([
|
|
|
|
('cashbook.id', 'in', Book2.search([
|
|
|
|
('parent', 'child_of', [self.id]),
|
|
|
|
], query=True)),
|
|
|
|
], query=True)
|
|
|
|
|
|
|
|
query = line_query.join(tab_line,
|
|
|
|
condition=tab_line.id==line_query.id,
|
|
|
|
).select(
|
2022-08-11 11:01:53 +00:00
|
|
|
Sum(tab_line.credit - tab_line.debit).as_('balance'),
|
|
|
|
)
|
|
|
|
|
|
|
|
if self.id:
|
2022-09-15 21:49:54 +00:00
|
|
|
balance = Decimal('0.0')
|
|
|
|
cursor.execute(*query)
|
|
|
|
result = cursor.fetchone()
|
|
|
|
if result:
|
|
|
|
if result[0] is not None:
|
2022-08-11 11:01:53 +00:00
|
|
|
balance += result[0]
|
2022-09-15 21:49:54 +00:00
|
|
|
return balance
|
2022-08-11 11:01:53 +00:00
|
|
|
|
2022-08-08 12:31:42 +00:00
|
|
|
@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'
|
|
|
|
"""
|
2022-09-18 10:52:31 +00:00
|
|
|
ConfigUser = Pool().get('cashbook.configuration_user')
|
|
|
|
|
2022-08-08 12:31:42 +00:00
|
|
|
actions = iter(args)
|
2022-09-18 10:52:31 +00:00
|
|
|
to_write_config = []
|
2022-08-08 12:31:42 +00:00
|
|
|
for books, values in zip(actions, actions):
|
|
|
|
for book in books:
|
2022-09-16 08:15:51 +00:00
|
|
|
# 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),
|
|
|
|
))
|
|
|
|
|
2022-08-08 12:31:42 +00:00
|
|
|
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,
|
|
|
|
))
|
2022-09-18 10:52:31 +00:00
|
|
|
|
|
|
|
# if owner changes, remove book from user-config
|
|
|
|
if 'owner' in values.keys():
|
|
|
|
if book.owner.id != values['owner']:
|
|
|
|
for x in ['defbook', 'book1', 'book2', 'book3',
|
|
|
|
'book4', 'book5']:
|
|
|
|
cfg1 = ConfigUser.search([
|
|
|
|
('iduser.id', '=', book.owner.id),
|
|
|
|
('%s.id' % x, '=', book.id),
|
|
|
|
])
|
|
|
|
if len(cfg1) > 0:
|
|
|
|
to_write_config.extend([ cfg1, {x: None} ])
|
2022-08-08 12:31:42 +00:00
|
|
|
super(Book, cls).write(*args)
|
|
|
|
|
2022-09-18 10:52:31 +00:00
|
|
|
if len(to_write_config) > 0:
|
|
|
|
ConfigUser.write(*to_write_config)
|
|
|
|
|
2022-08-08 12:31:42 +00:00
|
|
|
@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)
|
2022-08-05 10:02:04 +00:00
|
|
|
|
|
|
|
# end Book
|