# -*- 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 decimal import Decimal from sql.aggregate import Sum from trytond.model import ModelView, ModelSQL, fields, Check from trytond.pyson import Eval, Or, Bool, Id, Len from trytond.transaction import Transaction from trytond.i18n import gettext from trytond.exceptions import UserError from trytond.pool import Pool from .colors import sel_color as sel_bgcolor from .templates import template_view_graph, template_view_line sel_etype = [ ('cashbooks', 'Cashbooks'), ('types', 'Types of Cashbooks'), ('currencies', 'Currencys'), #('category', 'Category'), ] sel_chart = [ ('vbar', 'Vertical Bars'), ('hbar', 'Horizontal Bars'), ('pie', 'Pie'), ('line', 'Line'), ] sel_maincolor = [ ('default', 'Default'), ('red', 'Red'), ('green', 'Green'), ('grey', 'Grey'), ('black', 'Black'), ('darkcyan', 'Dark Cyan'), ] class Evaluation(ModelSQL, ModelView): 'Evaluation' __name__ = 'cashbook_report.evaluation' company = fields.Many2One(string='Company', model_name='company.company', required=True, ondelete="RESTRICT") name = fields.Char(string='Name', required=True) dtype = fields.Selection(string='Data type', required=True, sort=False, selection=sel_etype, help='Type of data displayed') chart = fields.Selection(string='Chart type', required=True, sort=False, selection=sel_chart, help='Type of graphical presentation.') legend = fields.Boolean(string='Legend') maincolor = fields.Selection(string='Color scheme', required=True, help='The color scheme determines the hue of all components of the chart.', selection=sel_maincolor, sort=False) bgcolor = fields.Selection(string='Background Color', required=True, help='Background color of the chart area.', sort=False, selection=sel_bgcolor) posted = fields.Boolean(string='Posted', help='Posted amounts only.') currency = fields.Many2One(string='Currency', ondelete='RESTRICT', model_name='currency.currency') cashbooks = fields.Many2Many(string='Cashbooks', relation_name='cashbook_report.eval_book', origin='evaluation', target='cashbook', states={ 'invisible': Eval('dtype', '') != 'cashbooks', }, depends=['dtype']) types = fields.Many2Many(string='Types', relation_name='cashbook_report.eval_type', origin='evaluation', target='dtype', states={ 'invisible': Eval('dtype', '') != 'types', }, depends=['dtype']) currencies = fields.Many2Many(string='Currencies', relation_name='cashbook_report.eval_currency', origin='evaluation', target='currency', filter=[('cashbook_hasbookings', '=', True)], states={ 'invisible': Eval('dtype', '') != 'currencies', }, depends=['dtype']) cashbook_values = fields.One2Many(string='Cashbook Values', field='evaluation', readonly=True, model_name='cashbook_report.eval_book') currency_values = fields.One2Many(string='Currency Values', field='evaluation', readonly=True, model_name='cashbook_report.eval_currency') type_values = fields.One2Many(string='Type Values', field='evaluation', readonly=True, model_name='cashbook_report.eval_type') ui_view_chart = fields.Many2One(string='UI View Point', model_name='ir.ui.view', ondelete='SET NULL') @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_posted(cls): """ default: False """ return False @classmethod def default_bgcolor(cls): """ default: Yellow 5 """ return '#ffffc0' @classmethod def default_maincolor(cls): """ default: 'default' """ return 'default' @classmethod def default_legend(cls): """ default True """ return True @classmethod def default_dtype(cls): """ default 'book' """ return 'cashbooks' @classmethod def default_chart(cls): """ default 'pie' """ return 'pie' @classmethod def uiview_delete(cls, evaluations): """ delete action view from evalualtion """ pool = Pool() UiView = pool.get('ir.ui.view') to_delete_uiview = [] for evaluation in evaluations: if evaluation.ui_view_chart: to_delete_uiview.append(evaluation.ui_view_chart) if len(to_delete_uiview) > 0: UiView.delete(to_delete_uiview) @classmethod def uiview_create(cls, evaluations): """ create ui view for current setup of evaluation """ pool = Pool() UiView = pool.get('ir.ui.view') Evaluation2 = pool.get('cashbook_report.evaluation') cls.uiview_delete(evaluations) to_write_eval = [] for evaluation in evaluations: if evaluation.dtype: # skip if no data to show if len(getattr(evaluation, evaluation.dtype, [])) == 0: continue view_graph, = UiView.create([{ 'model': 'cashbook_report.%s' % { 'cashbooks': 'eval_book', 'types': 'eval_type', 'currencies': 'eval_currency', }[evaluation.dtype], 'module': 'cashbook_report', 'priority': 10, 'type': 'graph', 'data': template_view_graph % { 'bgcol': '' if evaluation.bgcolor == 'default' \ else 'background="%s"' % evaluation.bgcolor, 'legend': '1' if evaluation.legend == True else '0', 'type': evaluation.chart, 'colscheme': '' if evaluation.maincolor == 'default' \ else 'color="%s"' % evaluation.maincolor, 'lines': template_view_line % { 'fill': '1', 'string': evaluation.dtype, }, }, }]) to_write_eval.extend([ [evaluation], { 'ui_view_chart': view_graph.id, }]) if len(to_write_eval) > 0: Evaluation2.write(*to_write_eval) @classmethod def create(cls, vlist): """ add chart """ records = super(Evaluation, cls).create(vlist) cls.uiview_create(records) return records @classmethod def write(cls, *args): """ unlink records if dtype changes """ to_write = [] actions = iter(args) to_update_uiview = [] for evaluations, values in zip(actions, actions): # update ui-view if related fields change if len(set({'name', 'dtype', 'bgcolor', 'maincolor', 'legend', 'chart'}).intersection(values.keys())) > 0: to_update_uiview.extend(evaluations) if 'dtype' in values.keys(): for evaluation in evaluations: if evaluation.dtype == values['dtype']: continue if (values['dtype'] != 'cashbooks') and \ (len(evaluation.cashbooks) > 0): to_write.extend([ [evaluation], { 'cashbooks': [ ('remove', [x.id for x in evaluation.cashbooks]) ], }]) if (values['dtype'] != 'types') and (len(evaluation.types) > 0): to_write.extend([ [evaluation], { 'types': [ ('remove', [x.id for x in evaluation.types]) ], }]) if (values['dtype'] != 'currencies') and (len(evaluation.currencies) > 0): to_write.extend([ [evaluation], { 'currencies': [ ('remove', [x.id for x in evaluation.currencies]) ], }]) args = list(args) args.extend(to_write) super(Evaluation, cls).write(*args) if len(to_update_uiview) > 0: cls.uiview_create(to_update_uiview) @classmethod def delete(cls, evaluations): """ delete views """ cls.uiview_delete(evaluations) super(Evaluation, cls).delete(evaluations) # end Evaluation class RelFieldsMixin(object): """ common fields """ evaluation = fields.Many2One(string='Evaluation', required=True, select=True, ondelete='CASCADE', model_name='cashbook_report.evaluation') eval_currency = fields.Function(fields.Many2One(model_name='currency.currency', string="Currency", readonly=True), 'on_change_with_eval_currency') currency_digits = fields.Function(fields.Integer(string='Currency Digits', readonly=True), 'on_change_with_currency_digits') @fields.depends('evaluation', '_parent_evaluation.currency') def on_change_with_eval_currency(self, name=None): """ currency of cashbook """ if self.evaluation: return self.evaluation.currency.id @fields.depends('evaluation', '_parent_evaluation.currency') def on_change_with_currency_digits(self, name=None): """ currency of cashbook """ if self.evaluation: return self.evaluation.currency.digits else: return 2 # end RelFieldsMixin class EvaluationCashbookRel(RelFieldsMixin, ModelSQL): 'Evaluation Cashbook Relation' __name__ = 'cashbook_report.eval_book' cashbook = fields.Many2One(string='Cashbook', required=True, select=True, ondelete='CASCADE', model_name='cashbook.book') name = fields.Function(fields.Char(string='Name', readonly=True), 'on_change_with_name') balance = fields.Function(fields.Numeric(string='Balance', readonly=True, digits=(16, Eval('currency_digits', 2)), depends=['currency_digits']), 'on_change_with_balance') @classmethod def validate(cls, records): """ check parent record """ super(EvaluationCashbookRel, cls).validate(records) for record in records: if record.evaluation.dtype != 'cashbooks': raise UserError(gettext( 'cashbook_report.msg_invalid_dtype', typename = gettext('cashbook_report.msg_dtype_cashbook'), )) @fields.depends('cashbook') def on_change_with_name(self, name=None): """ get name of Type """ if self.cashbook: return self.cashbook.rec_name @fields.depends('cashbook', '_parent_cashbook.currency', \ '_parent_cashbook.balance', 'eval_currency', 'currency_digits') def on_change_with_balance(self, name=None): """ balance of cashbook """ Currency = Pool().get('currency.currency') if self.cashbook: exp = Decimal(Decimal(1) / 10 ** self.currency_digits) return Currency.compute( self.cashbook.currency, self.cashbook.balance, self.eval_currency, ).quantize(exp) # end EvaluationCashbookRel class EvaluationTypeRel(RelFieldsMixin, ModelSQL): 'Evaluation Type Relation' __name__ = 'cashbook_report.eval_type' dtype = fields.Many2One(string='Type', required=True, select=True, ondelete='CASCADE', model_name='cashbook.type') name = fields.Function(fields.Char(string='Name', readonly=True), 'on_change_with_name') balance = fields.Function(fields.Numeric(string='Balance', readonly=True, digits=(16, Eval('currency_digits', 2)), depends=['currency_digits']), 'on_change_with_balance') @classmethod def validate(cls, records): """ check parent record """ super(EvaluationTypeRel, cls).validate(records) for record in records: if record.evaluation.dtype != 'types': raise UserError(gettext( 'cashbook_report.msg_invalid_dtype', typename = gettext('cashbook_report.msg_dtype_type'), )) @fields.depends('dtype') def on_change_with_name(self, name=None): """ get name of Type """ if self.dtype: return self.dtype.rec_name @fields.depends('evaluation', 'eval_currency', 'currency_digits', 'dtype') def on_change_with_balance(self, name=None): """ get balance of bookings in cashbooks by 'type', converted to currency of evaluation """ pool = Pool() Lines = pool.get('cashbook.line') Cashbook = pool.get('cashbook.book') Currency = pool.get('currency.currency') tab_line = Lines.__table__() tab_book = Cashbook.__table__() cursor = Transaction().connection.cursor() if (self.evaluation is None) or (self.dtype is None) or \ (self.eval_currency is None) or (self.currency_digits is None): return None lines = Lines.search([ ('cashbook.btype.id', '=', self.dtype.id), ('cashbook.state', '=', 'open'), ('cashbook.owner.id', '=', Transaction().user), ], query=True) query = lines.join(tab_line, condition=lines.id==tab_line.id, ).join(tab_book, condition=tab_book.id==tab_line.cashbook, ).select( tab_book.currency, Sum(tab_line.credit - tab_line.debit).as_('balance'), group_by=[tab_book.currency], ) cursor.execute(*query) balances = cursor.fetchall() total_amount = Decimal('0.0') for balance in balances: (id_currency, bal1) = balance total_amount += Currency.compute( Currency(id_currency), bal1, self.eval_currency, ) exp = Decimal(Decimal(1) / 10 ** self.currency_digits) return total_amount.quantize(exp) # end EvaluationTypeRel class EvaluationCurrencyRel(RelFieldsMixin, ModelSQL): 'Evaluation Currency Relation' __name__ = 'cashbook_report.eval_currency' currency = fields.Many2One(string='Currency', required=True, select=True, ondelete='CASCADE', model_name='currency.currency') name = fields.Function(fields.Char(string='Name', readonly=True), 'on_change_with_name') balance = fields.Function(fields.Numeric(string='Balance', readonly=True, digits=(16, Eval('currency_digits', 2)), depends=['currency_digits']), 'on_change_with_balance') @classmethod def validate(cls, records): """ check parent record """ super(EvaluationCurrencyRel, cls).validate(records) for record in records: if record.evaluation.dtype != 'currencies': raise UserError(gettext( 'cashbook_report.msg_invalid_dtype', typename = gettext('cashbook_report.msg_dtype_currency'), )) @fields.depends('currency') def on_change_with_name(self, name=None): """ get name of Type """ if self.currency: return self.currency.rec_name @fields.depends('evaluation', 'eval_currency', 'currency_digits', 'currency') def on_change_with_balance(self, name=None): """ get balance of bookings in cashbooks by 'currency', converted to currency of evaluation """ pool = Pool() Lines = pool.get('cashbook.line') Currency = pool.get('currency.currency') tab_line = Lines.__table__() cursor = Transaction().connection.cursor() if (self.evaluation is None) or (self.currency is None) or \ (self.eval_currency is None) or (self.currency_digits is None): return None lines = Lines.search([ ('cashbook.currency.id', '=', self.currency.id), ('cashbook.state', '=', 'open'), ('cashbook.owner.id', '=', Transaction().user), ], query=True) query = lines.join(tab_line, condition=lines.id==tab_line.id, ).select( Sum(tab_line.credit - tab_line.debit).as_('balance'), ) cursor.execute(*query) balances = cursor.fetchall() total_amount = Decimal('0.0') for balance in balances: (bal1,) = balance total_amount += Currency.compute( self.currency, bal1, self.eval_currency, ) exp = Decimal(Decimal(1) / 10 ** self.currency_digits) return total_amount.quantize(exp) # end EvaluationCurrencyRel