# -*- coding: utf-8 -*-
##############################################################################
#
#    Copyright (C) 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 datetime import datetime
from openerp.tests.common import TransactionCase
from openerp import workflow


class TestPaymentRoundtrip(TransactionCase):

    def assert_payment_order_state(self, expected):
        """
        Check that the state of our payment order is
        equal to the 'expected' parameter
        """
        state = self.registry('payment.order').read(
            self.cr, self.uid, self.payment_order_id, ['state'])['state']
        assert state == expected, \
            'Payment order does not go into state \'%s\'.' % expected

    def assert_invoices_state(self, expected):
        """
        Check that the state of our invoices is
        equal to the 'expected' parameter
        """
        for invoice in self.registry('account.invoice').read(
                self.cr, self.uid, self.invoice_ids, ['state']):
            assert invoice['state'] == expected, \
                'Invoice does not go into state \'%s\'' % expected

    def setup_company(self, reg, cr, uid):
        """
        Set up a company with a bank account and configure the
        current user to work with that company
        """
        data_model = reg('ir.model.data')
        self.country_id = data_model.get_object_reference(
            cr, uid, 'base', 'nl')[1]
        self.currency_id = data_model.get_object_reference(
            cr, uid, 'base', 'EUR')[1]
        self.bank_id = reg('res.bank').create(
            cr, uid, {
                'name': 'ING Bank',
                'bic': 'INGBNL2A',
                'country': self.country_id,
                })
        self.company_id = reg('res.company').create(
            cr, uid, {
                'name': '_banking_addons_test_company',
                'currency_id': self.currency_id,
                'country_id': self.country_id,
                })
        self.partner_id = reg('res.company').read(
            cr, uid, self.company_id, ['partner_id'])['partner_id'][0]
        self.partner_bank_id = reg('res.partner.bank').create(
            cr, uid, {
                'state': 'iban',
                'acc_number': 'NL08INGB0000000555',
                'bank': self.bank_id,
                'bank_bic': 'INGBNL2A',
                'partner_id': self.partner_id,
                'company_id': self.company_id,
                })
        reg('res.users').write(
            cr, uid, [uid], {
                'company_ids': [(4, self.company_id)]})
        reg('res.users').write(
            cr, uid, [uid], {
                'company_id': self.company_id})

    def setup_chart(self, reg, cr, uid):
        """
        Set up the configurable chart of accounts and create periods
        """
        data_model = reg('ir.model.data')
        chart_setup_model = reg('wizard.multi.charts.accounts')
        chart_template_id = data_model.get_object_reference(
            cr, uid, 'account', 'configurable_chart_template')[1]
        chart_values = {
            'company_id': self.company_id,
            'currency_id': self.currency_id,
            'chart_template_id': chart_template_id}
        chart_values.update(
            chart_setup_model.onchange_chart_template_id(
                cr, uid, [], 1)['value'])
        chart_setup_id = chart_setup_model.create(
            cr, uid, chart_values)
        chart_setup_model.execute(
            cr, uid, [chart_setup_id])
        year = datetime.now().strftime('%Y')
        fiscalyear_id = reg('account.fiscalyear').create(
            cr, uid, {
                'name': year,
                'code': year,
                'company_id': self.company_id,
                'date_start': '%s-01-01' % year,
                'date_stop': '%s-12-31' % year,
                })
        reg('account.fiscalyear').create_period(
            cr, uid, [fiscalyear_id])

    def setup_payables(self, reg, cr, uid, context=None):
        """
        Set up suppliers and invoice them. Check that the invoices
        can be validated properly.
        """
        partner_model = reg('res.partner')
        self.supplier1 = partner_model.create(
            cr, uid, {
                'name': 'Supplier 1',
                'supplier': True,
                'country_id': self.country_id,
                'bank_ids': [
                    (0, False, {
                        'state': 'iban',
                        'acc_number': 'NL42INGB0000454000',
                        'bank': self.bank_id,
                        'bank_bic': 'INGBNL2A',
                    })
                ],
            }, context=context)
        self.supplier2 = partner_model.create(
            cr, uid, {
                'name': 'Supplier 2',
                'supplier': True,
                'country_id': self.country_id,
                'bank_ids': [
                    (0, False, {
                        'state': 'iban',
                        'acc_number': 'NL86INGB0002445588',
                        'bank': self.bank_id,
                        'bank_bic': 'INGBNL2A',
                    })
                ],
            }, context=context)
        self.payable_id = reg('account.account').search(
            cr, uid, [
                ('company_id', '=', self.company_id),
                ('code', '=', '120000')])[0]
        expense_id = reg('account.account').search(
            cr, uid, [
                ('company_id', '=', self.company_id),
                ('code', '=', '123000')])[0]
        invoice_model = reg('account.invoice')
        values = {
            'type': 'in_invoice',
            'partner_id': self.supplier1,
            'account_id': self.payable_id,
            'invoice_line': [
                (0, False, {
                    'name': 'Purchase 1',
                    'price_unit': 100.0,
                    'quantity': 1,
                    'account_id': expense_id,
                })
            ],
            'reference_type': 'none',
            'supplier_invoice_number': 'INV1',
        }
        self.invoice_ids = [
            invoice_model.create(
                cr, uid, values, context={
                    'type': 'in_invoice',
                    })]
        values.update({
            'partner_id': self.supplier2,
            'name': 'Purchase 2',
            'reference_type': 'structured',
            'supplier_invoice_number': 'INV2',
            'reference': 'STR2',
        })
        self.invoice_ids.append(
            invoice_model.create(
                cr, uid, values, context={
                    'type': 'in_invoice'}))
        for invoice_id in self.invoice_ids:
            workflow.trg_validate(
                uid, 'account.invoice', invoice_id, 'invoice_open', cr)
        self.assert_invoices_state('open')

    def setup_payment_config(self, reg, cr, uid,
                             transfer_move_option='line'):
        """
        Configure an additional account and journal for payments
        in transit and configure a payment mode with them.
        """
        account_parent_id = reg('account.account').search(
            cr, uid, [
                ('company_id', '=', self.company_id),
                ('parent_id', '=', False)])[0]
        user_type = reg('ir.model.data').get_object_reference(
            cr, uid, 'account', 'data_account_type_liability')[1]
        transfer_account_id = reg('account.account').create(
            cr, uid, {
                'company_id': self.company_id,
                'parent_id': account_parent_id,
                'code': 'TRANS',
                'name': 'Transfer account',
                'type': 'other',
                'user_type': user_type,
                'reconcile': True,
                })
        transfer_journal_id = reg('account.journal').search(
            cr, uid, [
                ('company_id', '=', self.company_id),
                ('code', '=', 'MISC')])[0]
        self.bank_journal_id = reg('account.journal').search(
            cr, uid, [
                ('company_id', '=', self.company_id),
                ('type', '=', 'bank')])[0]
        payment_mode_type_id = reg('ir.model.data').get_object_reference(
            cr, uid, 'account_banking_sepa_credit_transfer',
            'export_sepa_sct_001_001_03')[1]
        self.payment_mode_id = reg('payment.mode').create(
            cr, uid, {
                'name': 'SEPA Mode',
                'bank_id': self.partner_bank_id,
                'journal': self.bank_journal_id,
                'company_id': self.company_id,
                'transfer_account_id': transfer_account_id,
                'transfer_journal_id': transfer_journal_id,
                'transfer_move_option': transfer_move_option,
                'type': payment_mode_type_id,
                })

    def setup_payment(self, reg, cr, uid):
        """
        Create a payment order with the invoices' payable move lines.
        Check that the payment order can be confirmed.

        date_preferred is set to 'now', to ensure one transfer move
        when transfer_move_option = 'date'.
        """
        self.payment_order_id = reg('payment.order').create(
            cr, uid, {
                'reference': 'PAY001',
                'mode': self.payment_mode_id,
                'date_prefered': 'now',
                })
        context = {
            'active_id': self.payment_order_id,
            'active_model': 'payment.order',
            }
        entries = reg('account.move.line').search(
            cr, uid, [
                ('company_id', '=', self.company_id),
                ('account_id', '=', self.payable_id),
                ])
        self.payment_order_create_id = reg('payment.order.create').create(
            cr, uid, {
                'entries': [(6, 0, entries)],
                }, context=context)
        reg('payment.order.create').create_payment(
            cr, uid, [self.payment_order_create_id], context=context)

        # Check if payment lines are created with the correct reference
        self.assertTrue(
            reg('payment.line').search(
                cr, uid, [
                    ('move_line_id.invoice', '=', self.invoice_ids[0]),
                    ('communication', '=', 'INV1'),
                    ('state', '=', 'normal'),
                    ], context=context),
            'No payment line created from invoice 1 or with the wrong '
            'communication')
        self.assertTrue(
            reg('payment.line').search(
                cr, uid, [
                    ('move_line_id.invoice', '=', self.invoice_ids[1]),
                    ('communication', '=', 'STR2'),
                    ('state', '=', 'structured'),
                    ], context=context),
            'No payment line created from invoice 2 or with the wrong '
            'communication')

        workflow.trg_validate(
            uid, 'payment.order', self.payment_order_id, 'open', cr)
        self.assert_payment_order_state('open')

    def export_payment(self, reg, cr, uid):
        """
        Call the SEPA export wizard on the payment order
        and check that the payment order and related invoices'
        states are moved forward afterwards
        """
        export_model = reg('banking.export.sepa.wizard')
        export_id = export_model.create(
            cr, uid, {}, context={'active_ids': [self.payment_order_id]})
        export_model.create_sepa(
            cr, uid, [export_id])
        export_model.save_sepa(
            cr, uid, [export_id])
        self.assert_payment_order_state('sent')
        self.assert_invoices_state('paid')

    def setup_bank_statement(self, reg, cr, uid):
        """
        Create a bank statement with a one line for each
        payment order line. Call the reconciliation
        wizard to match the line with the open payment order. Confirm the
        bank statement. Check if the payment order is done.
        """
        statement_model = reg('account.bank.statement')
        line_model = reg('account.bank.statement.line')
        statement_id = statement_model.create(
            cr, uid, {
                'name': 'Statement',
                'journal_id': self.bank_journal_id,
                'balance_end_real': -200.0,
                'period_id': reg('account.period').find(cr, uid)[0]
                })
        line1_id = line_model.create(
            cr, uid, {
                'name': 'Statement line',
                'statement_id': statement_id,
                'amount': -100.0,
                'account_id': self.payable_id,
                'partner_id': self.supplier1,
                })
        line1 = line_model.browse(cr, uid, line1_id)
        rec_line1 = line_model.\
            get_reconciliation_proposition(cr, uid, line1)[0]
        line_model.process_reconciliation(cr, uid, line1_id, [
            {'counterpart_move_line_id': rec_line1['id'],
             'debit': rec_line1['credit'],
             'credit': rec_line1['debit']}])
        line2_id = line_model.create(
            cr, uid, {
                'name': 'Statement line',
                'statement_id': statement_id,
                'amount': -100.0,
                'account_id': self.payable_id,
                'partner_id': self.supplier2,
                })
        line2 = line_model.browse(cr, uid, line2_id)
        rec_line2 = line_model.\
            get_reconciliation_proposition(cr, uid, line2)[0]
        line_model.process_reconciliation(cr, uid, line2_id, [
            {'counterpart_move_line_id': rec_line2['id'],
             'debit': rec_line2['credit'],
             'credit': rec_line2['debit']}])
        self.assert_payment_order_state('done')

    def setup_bank_statement_one_move(self, reg, cr, uid):
        """
        Create a bank statement with a single line. Call the reconciliation
        wizard to match the line with the open payment order. Confirm the
        bank statement. Check if the payment order is done.
        """
        statement_model = reg('account.bank.statement')
        line_model = reg('account.bank.statement.line')
        statement_id = statement_model.create(
            cr, uid, {
                'name': 'Statement',
                'journal_id': self.bank_journal_id,
                'balance_end_real': -200.0,
                'period_id': reg('account.period').find(cr, uid)[0]
                })
        line1_id = line_model.create(
            cr, uid, {
                'name': 'Statement line',
                'statement_id': statement_id,
                'amount': -200.0,
                'account_id': self.payable_id,
                })
        line1 = line_model.browse(cr, uid, line1_id)
        rec_line1 = line_model.\
            get_reconciliation_proposition(cr, uid, line1)[0]
        line_model.process_reconciliation(cr, uid, line1_id, [
            {'counterpart_move_line_id': rec_line1['id'],
             'debit': rec_line1['credit'],
             'credit': rec_line1['debit']}])
        self.assert_payment_order_state('done')

    def check_reconciliations(self, reg, cr, uid):
        """
        Check if the payment order has any lines and that
        the transit move lines of those payment lines are
        reconciled by now.

        The transit move line is the line that pays the invoice
        so it is reconciled as soon as the payment order
        is sent.
        """
        payment_order = reg('payment.order').browse(
            cr, uid, self.payment_order_id)
        assert payment_order.line_ids, 'Payment order has no payment lines'
        for line in payment_order.bank_line_ids:
            assert line.transit_move_line_id, \
                'Payment order has no transfer move line'
            assert line.transit_move_line_id.reconcile_id, \
                'Transit move line on payment line is not reconciled'

    def check_reconciliations_after_bank_statement(self, reg, cr, uid):
        """
        Check if the payment order has any lines and that
        the transfer move lines of those payment lines are
        reconciled by now.
        """
        payment_order = reg('payment.order').browse(
            cr, uid, self.payment_order_id)
        assert payment_order.line_ids, 'Payment order has no payment lines'
        for line in payment_order.bank_line_ids:
            assert line.transfer_move_line_id, \
                'Payment order has no transfer move line'
            assert line.transfer_move_line_id.reconcile_id, \
                'Transfer move line on payment line is not reconciled'

    def test_payment_roundtrip(self):
        """ Payment round trip using transfer account,
            with one move per payment order line on the transfer account
        """
        reg, cr, uid, = self.registry, self.cr, self.uid
        self.setup_company(reg, cr, uid)
        self.setup_chart(reg, cr, uid)
        self.setup_payables(reg, cr, uid)
        self.setup_payment_config(reg, cr, uid)
        self.setup_payment(reg, cr, uid)
        self.export_payment(reg, cr, uid)
        self.check_reconciliations(reg, cr, uid)
        self.setup_bank_statement(reg, cr, uid)
        self.check_reconciliations_after_bank_statement(reg, cr, uid)

    def test_payment_roundtrip_one_move(self):
        """ Payment round trip using transfer account,
            with one move per payment order on the transfer account
        """
        reg, cr, uid, = self.registry, self.cr, self.uid
        self.setup_company(reg, cr, uid)
        self.setup_chart(reg, cr, uid)
        self.setup_payables(reg, cr, uid)
        self.setup_payment_config(reg, cr, uid, transfer_move_option='date')
        self.setup_payment(reg, cr, uid)
        self.export_payment(reg, cr, uid)
        self.check_reconciliations(reg, cr, uid)
        self.setup_bank_statement_one_move(reg, cr, uid)
        self.check_reconciliations_after_bank_statement(reg, cr, uid)