# -*- 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 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=STATES, depends=DEPENDS) 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']) 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, ('state', 'ASC')) cls._order.insert(0, ('date', '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) 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] @classmethod def search_category_view(cls, name, clause): """ search in category """ return [('category.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.get_long_recname(self.category.name) @classmethod def search_rec_name(cls, name, clause): """ search in description +... """ return [('description',) + 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('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 @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