#This file is part of Tryton. The COPYRIGHT file at the top level of this repository contains the full copyright notices and license terms. "Statement" from trytond.osv import fields, OSV from trytond.netsvc import LocalService from decimal import Decimal import datetime _STATES = {'readonly': 'state != "draft"'} class Statement(OSV): 'Account Statement' _name = 'account.statement' _description = __doc__ journal = fields.Many2One('account.statement.journal', 'Journal', required=True, states=_STATES, on_change=['journal'], select=1) date = fields.Date('Date', required=True, states=_STATES, select=1) start_balance = fields.Numeric('Start Balance', digits=(16, 2), states=_STATES) end_balance = fields.Numeric('End Balance', digits=(16, 2), states=_STATES) lines = fields.One2Many('account.statement.line', 'statement', 'Transactions', states=_STATES) state = fields.Selection([ ('draft', 'Draft'), ('validated', 'Validated'), ('cancel', 'Cancel'), ('posted', 'Posted'), ], 'State', readonly=True, select=1) move_lines = fields.Function('get_move_lines', type='one2many', relation='account.move.line', string='Move Lines') def __init__(self): super(Statement, self).__init__() self._rpc_allowed += [ 'draft_workflow', ] self._order[0] = ('id', 'DESC') self._error_messages.update({ 'wrong_end_balance': 'Wrong End balance:\n' \ ' * Expected: %s\n' \ ' * Computed: %s', }) def default_state(self, cursor, user, context=None): return 'draft' def default_date(self, cursor, user, context=None): return datetime.date.today() def on_change_journal(self, cursor, user, ids, value, context=None): res = {} if not value.get('journal'): return res statement_ids = self.search(cursor, user, [ ('journal', '=', value['journal']), ], order=[ ('date', 'DESC'), ], limit=1, context=context) if not statement_ids: return res statement = self.browse(cursor, user, statement_ids[0], context=context) res['start_balance'] = statement.end_balance return res def get_move_lines(self, cursor, user, ids, name, args, context=None): ''' Return the move lines that have been generated by the statements. ''' res = {} for statement in self.browse(cursor, user, ids, context=context): res[statement.id] = [] for line in statement.lines: if not line.move: continue for move_line in line.move.lines: res[statement.id].append(move_line.id) return res def get_end_balance(self, cursor, user, ids, name, arg, context=None): statements = self.browse(cursor, user, ids, context=context) res = {} for statement in statements: res[statement.id] = statement.start_balance for line in statement.lines: res[statement.id] += line.amount return res def set_state_validated(self, cursor, user, statement_id, context=None): statement_line_obj = self.pool.get('account.statement.line') statement = self.browse(cursor, user, statement_id, context=context) computed_end_balance = statement.start_balance for line in statement.lines: computed_end_balance += line.amount if computed_end_balance != statement.end_balance: self.raise_user_error(cursor, 'wrong_end_balance', (statement.end_balance, computed_end_balance), context=context) for line in statement.lines: statement_line_obj.create_move(cursor, user, line, context=context) self.write(cursor, user, statement_id, { 'state':'validated', }, context=context) def set_state_posted(self, cursor, user, statement_id, context=None): statement_line_obj = self.pool.get('account.statement.line') statement = self.browse(cursor, user, statement_id, context=context) statement_line_obj.post_move(cursor, user, statement.lines, context=context) self.write(cursor, user, statement_id, { 'state':'posted', }, context=context) def set_state_cancel(self, cursor, user, statement_id, context=None): statement_line_obj = self.pool.get('account.statement.line') statement = self.browse(cursor, user, statement_id, context=context) statement_line_obj.delete_move(cursor, user, statement.lines, context=context) self.write(cursor, user, statement_id, { 'state':'cancel', }, context=context) def draft_workflow(self, cursor, user, ids, context=None): workflow_service = LocalService('workflow') for statement in self.browse(cursor, user, ids, context=context): workflow_service.trg_create(user, self._name, statement.id, cursor) self.write(cursor, user, statement.id, { 'state': 'draft', }, context=context) return True Statement() class Line(OSV): 'Account Statement Line' _name = 'account.statement.line' _description = __doc__ statement = fields.Many2One('account.statement', 'Statement', required=True, ondelete='CASCADE') date = fields.Date('Date', required=True) amount = fields.Numeric('Amount', digits=(16,2), required=True, on_change=['amount', 'party', 'account']) party = fields.Many2One('relationship.party', 'Party', on_change=['amount', 'party']) account = fields.Many2One('account.account', 'Account', required=True) description = fields.Char('Description') move = fields.Many2One('account.move', 'Account Move', readonly=True) def __init__(self): super(Line, self).__init__() self._error_messages.update({ 'debit_credit_account_bank_journal': 'Please provide debit and ' \ 'credit account on bank journal.', }) def on_change_party(self, cursor, user, ids, value, context=None): res = {} if not (value.get('party') and value.get('amount')): return res party_obj = self.pool.get('relationship.party') account_obj = self.pool.get('account.account') party = party_obj.browse(cursor, user, value['party'], context=context) if value['amount'] > Decimal("0.0"): account = party.account_receivable else: account = party.account_payable res['account'] = account_obj.name_get(cursor, user, account.id, context=context)[0] return res def on_change_amount(self, cursor, user, ids, value, context=None): res = {} if not (value.get('party') and value.get('amount')): return res party_obj = self.pool.get('relationship.party') account_obj = self.pool.get('account.account') party = party_obj.browse(cursor, user, value['party'], context=context) if value.get('account') and value['account'] not in ( party.account_receivable.id, party.account_payable.id): # The user has entered a non-default value, we keep it. return res if value['amount'] > Decimal("0.0"): account = party.account_receivable else: account = party.account_payable res['account'] = account_obj.name_get(cursor, user, account.id, context=context)[0] return res def create_move(self, cursor, user, line, context=None): move_obj = self.pool.get('account.move') period_obj = self.pool.get('account.period') period_id = period_obj.find(cursor, user, line.statement.journal.company.id, date=line.date, context=context) move_lines = self.get_move_lines(cursor, user, line, context=context) move_id = move_obj.create(cursor, user, { 'name': line.date, 'period': period_id, 'journal': line.statement.journal.journal.id, 'date': line.date, 'lines': [('create', x) for x in move_lines], }, context=context) self.write(cursor, user, line.id, { 'move': move_id, }, context=context) def post_move(self, cursor, user, lines, context=None): move_obj = self.pool.get('account.move') move_obj.post(cursor, user, [l.move.id for l in lines], context=context) def delete_move(self, cursor, user, lines, context=None): move_obj = self.pool.get('account.move') move_obj.delete(cursor, user, [l.move.id for l in lines], context=context) def get_move_lines(self, cursor, user, statement_line, context=None): currency_obj = self.pool.get('currency.currency') zero = Decimal("0.0") amount = currency_obj.compute( cursor, user, statement_line.statement.journal.currency, statement_line.amount, statement_line.statement.journal.company.currency, context=context) if statement_line.statement.journal.currency.id != \ statement_line.statement.journal.company.currency.id: second_currency = statement_line.statement.journal.currency.id amount_second_currency = abs(statement_line.amount) else: amount_second_currency = False second_currency = None vals = [] vals.append({ 'name': statement_line.date, 'debit': amount < zero and -amount or zero, 'credit': amount >= zero and amount or zero, 'account': statement_line.account.id, 'party': statement_line.party and statement_line.party.id, 'second_currency': second_currency, 'amount_second_currency': amount_second_currency, }) journal = statement_line.statement.journal.journal if statement_line.amount >= zero: account = journal.credit_account else: account = journal.debit_account if not account: self.raise_user_error(cursor, 'debit_credit_account_bank_journal', context=context) vals.append({ 'name': statement_line.date, 'debit': amount >= zero and amount or zero, 'credit': amount < zero and -amount or zero, 'account': account.id, 'party': statement_line.party and statement_line.party.id, 'second_currency': second_currency, 'amount_second_currency': amount_second_currency, }) return vals Line()