# -*- 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 Workflow, ModelView, ModelSQL, fields, Check from trytond.pyson import Eval, Or, Bool from trytond.exceptions import UserError from trytond.i18n import gettext from trytond.transaction import Transaction from trytond.pool import Pool from trytond.report import Report from decimal import Decimal from sql.aggregate import Sum from sql.conditionals import Case STATES = { 'readonly': Eval('state', '') != 'open', } DEPENDS=['state'] sel_state_book = [ ('open', 'Open'), ('closed', 'Closed'), ('archive', 'Archive'), ] class Book(Workflow, ModelSQL, ModelView): 'Cashbook' __name__ = 'cashbook.book' company = fields.Many2One(string='Company', model_name='company.company', required=True, ondelete="RESTRICT") name = fields.Char(string='Name', required=True, states=STATES, depends=DEPENDS) btype = fields.Many2One(string='Type', required=True, model_name='cashbook.type', ondelete='RESTRICT', states=STATES, depends=DEPENDS) owner = fields.Many2One(string='Owner', required=True, select=True, model_name='res.user', ondelete='SET NULL') reviewer = fields.Many2One(string='Reviewer', select=True, help='Group of users who have write access to the cashbook.', model_name='res.group', ondelete='SET NULL') observer = fields.Many2One(string='Observer', select=True, help='Group of users who have read-only access to the cashbook.', model_name='res.group', ondelete='SET NULL') lines = fields.One2Many(string='Lines', field='cashbook', model_name='cashbook.line', states=STATES, depends=DEPENDS) account = fields.Many2One(string='Account', select=True, model_name='account.account', ondelete='RESTRICT', states=STATES, depends=DEPENDS) start_balance = fields.Numeric(string='Initial Amount', required=True, states={ 'readonly': Or( STATES['readonly'], Bool(Eval('lines')), ), }, depends=DEPENDS+['lines']) balance = fields.Function(fields.Numeric(string='Balance', readonly=True), 'on_change_with_balance') currency = fields.Many2One(string='Currency', required=True, model_name='currency.currency', states={ 'readonly': Or( STATES['readonly'], Bool(Eval('lines', [])), ), }, depends=DEPENDS+['lines']) state = fields.Selection(string='State', required=True, readonly=True, selection=sel_state_book) state_string = state.translated('state') @classmethod def __setup__(cls): super(Book, cls).__setup__() cls._order.insert(0, ('name', 'ASC')) cls._order.insert(0, ('state', 'ASC')) 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'], }, }) @classmethod def default_start_balance(cls): """ zero """ return Decimal('0.0') @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 @classmethod def default_state(cls): return 'open' @classmethod def default_owner(cls): """ default: current user """ return Transaction().user @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 """ return '%(name)s | %(balance)s %(symbol)s | %(state)s' % { 'name': self.name or '-', 'balance': Report.format_number(self.balance or 0.0, None), 'symbol': getattr(self.currency, 'symbol', '-'), 'state': self.state_string, } @fields.depends('id', 'start_balance') def on_change_with_balance(self, name=None): """ compute balance """ Line = Pool().get('cashbook.line') tab_line = Line.__table__() cursor = Transaction().connection.cursor() query = tab_line.select( Sum(tab_line.credit - tab_line.debit).as_('balance'), group_by=[tab_line.cashbook], where=tab_line.cashbook == self.id ) if self.id: if self.start_balance is not None: balance = self.start_balance cursor.execute(*query) result = cursor.fetchone() if result: balance += result[0] return balance @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' """ actions = iter(args) for books, values in zip(actions, actions): for book in books: if 'start_balance' in values.keys(): if len(book.lines) > 0: raise UserError(gettext( 'cashbook.msg_book_err_startamount_with_lines', bookname = book.rec_name, )) 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, )) super(Book, cls).write(*args) @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) # end Book