account_voucher_instant.py 13.2 KB
# -*- coding: utf-8 -*-
##############################################################################
#
#    OpenERP, Open Source Management Solution
#    This module copyright (C) 2012 - 2013 Therp BV (<http://therp.nl>).
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU Affero General Public License as
#    published by the Free Software Foundation, either version 3 of the
#    License, or (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU Affero General Public License for more details.
#
#    You should have received a copy of the GNU Affero General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################

from openerp.osv import orm, fields
from openerp.tools.translate import _
from openerp.addons.decimal_precision import decimal_precision as dp


class instant_voucher(orm.TransientModel):
    _name = 'account.voucher.instant'
    _description = 'Instant Voucher'

    def cancel(self, cr, uid, ids, context=None):
        """
        Delete the voucher and close window
        """
        assert len(ids) == 1, "Will only take one resource id"
        instant = self.browse(cr, uid, ids[0], context=context)
        if instant.voucher_id:
            self.pool.get('account.voucher').cancel_voucher(
                cr, uid, [instant.voucher_id.id], context=context)
            self.pool.get('account.voucher').unlink(
                cr, uid, [instant.voucher_id.id], context=context)
        return {'type': 'ir.actions.act_window_close'}

    def get_voucher_defaults(self, cr, uid, vals, context=None):
        """
        Gather conditional defaults based on given key, value pairs

        :param vals: dictionary of key, value pairs
        :returns: dictionary of default values for fields not in vals
        """
        values_pool = self.pool.get('ir.values')
        voucher_pool = self.pool.get('account.voucher')
        res = {}
        for (key, val) in vals.iteritems():
            if val and voucher_pool._all_columns[key].column.change_default:
                for default in values_pool.get_defaults(
                        cr, uid, 'account.voucher', '%s=%s' % (key, val)):
                    if default[1] not in vals:
                        res[default[1]] = default[2]
        return res

    def create_voucher(self, cr, uid, ids, context=None):
        """
        Create a fully fledged voucher counterpart for the
        statement line. User only needs to process taxes and may
        adapt cost/income account.
        """
        assert len(ids) == 1, "Will only take one resource id"
        voucher_pool = self.pool.get('account.voucher')
        period_pool = self.pool.get('account.period')
        instant = self.browse(cr, uid, ids[0], context=context)
        line = instant.statement_line_id
        voucher_type = line.amount < 0 and 'purchase' or 'sale'
        journal_ids = self.pool.get('account.journal').search(
            cr, uid, [('company_id', '=', line.company_id.id),
                      ('type', '=', voucher_type)])
        if not journal_ids:
            orm.exept_orm(
                _('Error'),
                _('No %s journal defined') % voucher_type)

        journal = self.pool.get('account.journal').browse(
            cr, uid, journal_ids[0], context=context)
        if journal.type in ('sale', 'sale_refund'):
            line_account_id = (
                journal.default_credit_account_id and
                journal.default_credit_account_id.id or False
            )
        elif journal.type in ('purchase', 'expense', 'purchase_refund'):
            line_account_id = (
                journal.default_debit_account_id and
                journal.default_debit_account_id.id or False
            )
        vals = {
            'name': (_('Voucher for statement line %s.%s') %
                     (line.statement_id.name, line.name)),
            'reference': line.ref or False,
            'company_id': line.company_id.id,
            'partner_id': instant.partner_id.id,
            'date': line.date or False,
            'account_id': line.account_id.id,
            'type': voucher_type,
            'line_ids': [(0, 0, {'amount': abs(line.amount),
                                 'account_id': line_account_id,
                                 'type': line.amount < 0 and 'dr' or 'cr',
                                 'name': line.ref or False,
                                 })],
            'amount': line.amount and abs(line.amount) or False,
            'journal_id': journal_ids[0],
            }
        if vals['date']:
            period_ids = period_pool.find(
                cr, uid, vals['date'], context=context
            )
            if period_ids:
                vals['period_id'] = period_ids[0]
        vals.update(self.get_voucher_defaults(cr, uid, vals, context=context))

        voucher_id = voucher_pool.create(
            cr, uid, vals, context=context)
        self.write(
            cr, uid, ids[0],
            {'voucher_id': voucher_id,
             'state': 'ready',
             'type': voucher_type,
             }, context=context)
        return {
            'name': self._description,
            'view_type': 'form',
            'view_mode': 'form',
            'res_model': self._name,
            'domain': [],
            'context': context,
            'type': 'ir.actions.act_window',
            'target': 'new',
            'res_id': ids[0],
            'nodestroy': False,
            }

    def dummy(self, cr, uid, ids, context=None):
        return {
            'name': self._description,
            'view_type': 'form',
            'view_mode': 'form',
            'res_model': self._name,
            'domain': [],
            'context': context,
            'type': 'ir.actions.act_window',
            'target': 'new',
            'res_id': ids[0],
            'nodestroy': False,
            }

    def default_get(self, cr, uid, fields_list, context=None):
        """
        Gather sane default values from the originating statement line
        """
        res = super(instant_voucher, self).default_get(
            cr, uid, fields_list, context=context)
        if 'statement_line_id' in fields_list:
            res['statement_line_id'] = (
                context.get('active_id') or
                context.get('active_ids') and context.get('active_ids')[0])
            if not res['statement_line_id']:
                raise orm.except_orm(
                    _('Error'),
                    _('Cannot determine statement line'))
            line = self.pool.get('account.bank.statement.line').browse(
                cr, uid, res['statement_line_id'], context=context)
            if 'balance' in fields_list:
                res['balance'] = line.amount
            if 'ref' in fields_list:
                res['ref'] = line.ref
            if 'partner_id' in fields_list:
                if line.partner_id:
                    res['partner_id'] = line.partner_id.id
        return res

    def _get_balance(self, cr, uid, ids, field_name, args, context=None):
        """
        Compute the expected residual
        TODO: currency conversion
        """
        res = {}
        for instant in self.browse(cr, uid, ids, context=context):
            if instant.voucher_id and instant.voucher_id.state == 'posted':
                amount = instant.statement_line_id.amount
                counteramount = 0.0
                statement_account_id = instant.statement_line_id.account_id.id
                for line in instant.voucher_id.move_ids:
                    if line.account_id.id == statement_account_id:
                        counteramount = line.debit - line.credit
                for line in instant.voucher_id.move_ids:
                    if line.account_id.id == statement_account_id:
                        counteramount = line.debit - line.credit
            else:
                amount = abs(instant.statement_line_id.amount)
                counteramount = abs(instant.voucher_id and
                                    instant.voucher_id.amount or 0.0)
            res[instant.id] = amount - counteramount
        return res

    def confirm(self, cr, uid, ids, context=None):
        """
        Post the voucher if necessary
        Post the voucher's move lines if necessary
        Sanity checks on currency and residual = 0.0

        If the account_banking module is installed, perform matching
        and reconciliation. If not, the user is left to manual
        reconciliation of OpenERP.
        """
        assert len(ids) == 1, "Will only take one resource id"
        statement_line_obj = self.pool.get('account.bank.statement.line')
        voucher_obj = self.pool.get('account.voucher')
        move_obj = self.pool.get('account.move')
        instant = self.browse(cr, uid, ids[0], context=context)
        statement_line = instant.statement_line_id
        voucher_currency = (instant.voucher_id.currency_id and
                            instant.voucher_id.currency_id or
                            instant.voucher_id.company_id.currency_id)
        if (statement_line.statement_id.currency.id != voucher_currency.id):
            raise orm.except_orm(
                _("Error"),
                _("Currency on the bank statement line needs to be the "
                  "same as on the voucher. Currency conversion is not yet "
                  "supported."))
        if instant.voucher_id.state != 'posted':
            voucher_obj.proforma_voucher(
                cr, uid, [instant.voucher_id.id], context=context)
            instant.refresh()
            if instant.voucher_id.state != 'posted':
                raise orm.except_orm(
                    _("Error"),
                    _("The voucher could not be posted."))
        if instant.voucher_id.move_id.state != 'posted':
            move_obj.post(
                cr, uid, [instant.voucher_id.move_id.id], context=context)
            instant.refresh()
            if instant.voucher_id.move_id.state != 'posted':
                raise orm.except_orm(
                    _("Error"),
                    _("The voucher's move line could not be posted."))
        if not self.pool.get('res.currency').is_zero(
                cr, uid, voucher_currency, instant.balance):
            raise orm.except_orm(
                _("Error"),
                _("The amount on the bank statement line needs to be the "
                  "same as on the voucher. Write-off is not yet "
                  "supported."))
        # Banking Addons integration:
        # Gather the info needed to match the bank statement line
        # and trigger its posting and reconciliation.
        if 'import_transaction_id' in statement_line_obj._columns:
            if instant.statement_line_id.state == 'confirmed':
                raise orm.except_orm(
                    _("Error"),
                    _("Cannot match a confirmed statement line"))
            if not statement_line.import_transaction_id:
                statement_line_obj.create_instant_transaction(
                    cr, uid, statement_line.id, context=context)
                statement_line.refresh()
            for line in instant.voucher_id.move_ids:
                if line.account_id.id == statement_line.account_id.id:
                    self.pool.get('banking.import.transaction').write(
                        cr,
                        uid,
                        statement_line.import_transaction_id.id,
                        {
                            'move_line_id': line.id,
                            'move_line_ids': [(6, 0, [line.id])],
                            'match_type': 'move',
                            'invoice_id': False,
                            'invoice_ids': [(6, 0, [])],
                            },
                        context=context
                    )

                    statement_line_obj.confirm(
                        cr, uid, [statement_line.id], context=context
                    )
                    break
        return {'type': 'ir.actions.act_window_close'}

    _columns = {
        'balance': fields.function(
            _get_balance,
            type='float',
            digits_compute=dp.get_precision('Account'),
            string="Balance",),
        'partner_id': fields.many2one(
            'res.partner',
            'Partner',
            required=True),
        'statement_line_id': fields.many2one(
            'account.bank.statement.line',
            'Bank statement line',
            readonly=True),
        'ref': fields.related(
            'statement_line_id', 'ref',
            type="char", size="48",
            readonly=True,
            string="Reference"),
        'voucher_id': fields.many2one(
            'account.voucher',
            'Voucher',
            readonly=True),
        'state': fields.selection(
            [('init', 'init'),
             ('ready', 'ready'),
             ('confirm', 'confirm')],
            'State'),
        'type': fields.selection(
            [('sale', 'Sale'),
             ('purchase', 'Purchase')],
            'Voucher type'),
        }

    _defaults = {'state': 'init'}