diff --git a/account_easy_reconcile/README.rst b/account_easy_reconcile/README.rst index b603301a..1e4981b9 100644 --- a/account_easy_reconcile/README.rst +++ b/account_easy_reconcile/README.rst @@ -1,6 +1,8 @@ .. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 +============== Easy Reconcile ============== @@ -27,11 +29,21 @@ You may be interested to install also the This latter add more complex reconciliations, allows multiple lines and partial. +Usage +===== + +Go to 'Invoicing/Periodic Processing/Reconciliation/Easy Automatic Reconcile' to start a +new easy reconcile. + +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/98/8.0 + Bug Tracker =========== -Bugs are tracked on `GitHub Issues `_. +Bugs are tracked on `GitHub Issues `_. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us smashing it by providing a detailed and welcomed feedback `here `_. @@ -73,4 +85,3 @@ mission is to support the collaborative development of Odoo features and promote its widespread use. To contribute to this module, please visit http://odoo-community.org. - diff --git a/account_easy_reconcile/__init__.py b/account_easy_reconcile/__init__.py index 403b65d3..3ac78713 100755 --- a/account_easy_reconcile/__init__.py +++ b/account_easy_reconcile/__init__.py @@ -1,9 +1,8 @@ # -*- coding: utf-8 -*- ############################################################################## # -# Copyright 2012 Camptocamp SA (Guewen Baconnier) +# Copyright 2012, 2015 Camptocamp SA (Guewen Baconnier, Damien Crier) # Copyright (C) 2010 Sébastien Beau -# Copyright 2015 Camptocamp SA (Damien Crier) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as diff --git a/account_easy_reconcile/__openerp__.py b/account_easy_reconcile/__openerp__.py index 472b977d..886c78ce 100755 --- a/account_easy_reconcile/__openerp__.py +++ b/account_easy_reconcile/__openerp__.py @@ -1,9 +1,8 @@ # -*- coding: utf-8 -*- ############################################################################## # -# Copyright 2012 Camptocamp SA (Guewen Baconnier) +# Copyright 2012, 2015 Camptocamp SA (Guewen Baconnier, Damien Crier) # Copyright (C) 2010 Sébastien Beau -# Copyright 2015 Camptocamp SA (Damien Crier) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -22,7 +21,7 @@ { "name": "Easy Reconcile", - "version": "1.3.1", + "version": "8.0.1.3.1", "depends": ["account"], "author": "Akretion,Camptocamp,Odoo Community Association (OCA)", "website": "http://www.akretion.com/", @@ -33,6 +32,8 @@ "security/ir.model.access.csv", "res_config_view.xml", ], + "test": ['test/easy_reconcile.yml', + ], 'license': 'AGPL-3', "auto_install": False, 'installable': True, diff --git a/account_easy_reconcile/base_reconciliation.py b/account_easy_reconcile/base_reconciliation.py index 16843f77..83048334 100644 --- a/account_easy_reconcile/base_reconciliation.py +++ b/account_easy_reconcile/base_reconciliation.py @@ -1,9 +1,8 @@ # -*- coding: utf-8 -*- ############################################################################## # -# Copyright 2012-2013 Camptocamp SA (Guewen Baconnier) +# Copyright 2012-2013, 2015 Camptocamp SA (Guewen Baconnier, Damien Crier) # Copyright (C) 2010 Sébastien Beau -# Copyright 2015 Camptocamp SA (Damien Crier) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -21,6 +20,7 @@ ############################################################################## from openerp import models, api, fields +from openerp.tools.safe_eval import safe_eval from operator import itemgetter, attrgetter @@ -112,7 +112,7 @@ class EasyReconcileBase(models.AbstractModel): params = [] if self.filter: dummy, where, params = ml_obj._where_calc( - eval(self.filter)).get_sql() + safe_eval(self.filter)).get_sql() if where: where = " AND %s" % where return where, params diff --git a/account_easy_reconcile/easy_reconcile.py b/account_easy_reconcile/easy_reconcile.py index 579ed11f..7a7a1bf2 100644 --- a/account_easy_reconcile/easy_reconcile.py +++ b/account_easy_reconcile/easy_reconcile.py @@ -1,9 +1,8 @@ # -*- coding: utf-8 -*- ############################################################################## # -# Copyright 2012-2013 Camptocamp SA (Guewen Baconnier) +# Copyright 2012-2013, 2015 Camptocamp SA (Guewen Baconnier, Damien Crier) # Copyright (C) 2010 Sébastien Beau -# Copyright 2015 Camptocamp SA (Damien Crier) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -25,8 +24,6 @@ from openerp import models, api, fields, _ from openerp.exceptions import Warning from openerp import sql_db -# from openerp import pooler - import logging _logger = logging.getLogger(__name__) @@ -177,7 +174,7 @@ class AccountEasyReconcile(models.Model): readonly=True ) last_history = fields.Many2one('easy.reconcile.history', - string='readonly=True', + string='Last history', readonly=True, compute='_last_history', ) company_id = fields.Many2one('res.company', string='Company') @@ -225,75 +222,69 @@ class AccountEasyReconcile(models.Model): else: new_cr = self.env.cr - uid, context = self.env.uid, self.env.context - with api.Environment.manage(): - self.env = api.Environment(new_cr, uid, context) + try: + all_ml_rec_ids = [] + all_ml_partial_ids = [] - try: - all_ml_rec_ids = [] - all_ml_partial_ids = [] + for method in rec.reconcile_method: + rec_model = self.env[method.name] + auto_rec_id = rec_model.create( + self._prepare_run_transient(method) + ) - for method in rec.reconcile_method: - rec_model = self.env[method.name] - auto_rec_id = rec_model.create( - self._prepare_run_transient(method) - ) + ml_rec_ids, ml_partial_ids = ( + auto_rec_id.automatic_reconcile() + ) - ml_rec_ids, ml_partial_ids = ( - auto_rec_id.automatic_reconcile() - ) + all_ml_rec_ids += ml_rec_ids + all_ml_partial_ids += ml_partial_ids - all_ml_rec_ids += ml_rec_ids - all_ml_partial_ids += ml_partial_ids - - reconcile_ids = find_reconcile_ids( - 'reconcile_id', - all_ml_rec_ids - ) - partial_ids = find_reconcile_ids( - 'reconcile_partial_id', - all_ml_partial_ids - ) - - self.env['easy.reconcile.history'].create( - { - 'easy_reconcile_id': rec.id, - 'date': fields.datetime.now(), - 'reconcile_ids': [ - (4, rid) for rid in reconcile_ids - ], - 'reconcile_partial_ids': [ - (4, rid) for rid in partial_ids - ], - }) - except Exception as e: - # In case of error, we log it in the mail thread, log the - # stack trace and create an empty history line; otherwise, - # the cron will just loop on this reconcile task. - _logger.exception( - "The reconcile task %s had an exception: %s", - rec.name, e.message - ) - message = "There was an error during reconciliation : %s" \ - % e.message - rec.message_post(body=message) - self.env['easy.reconcile.history'].create( - { - 'easy_reconcile_id': rec.id, - 'date': fields.Datetime.now(), - 'reconcile_ids': [], - 'reconcile_partial_ids': [], - } - ) - finally: - if ctx['commit_every']: - new_cr.commit() - new_cr.close() + reconcile_ids = find_reconcile_ids( + 'reconcile_id', + all_ml_rec_ids + ) + partial_ids = find_reconcile_ids( + 'reconcile_partial_id', + all_ml_partial_ids + ) + self.env['easy.reconcile.history'].create( + { + 'easy_reconcile_id': rec.id, + 'date': fields.Datetime.now(), + 'reconcile_ids': [ + (4, rid) for rid in reconcile_ids + ], + 'reconcile_partial_ids': [ + (4, rid) for rid in partial_ids + ], + }) + except Exception as e: + # In case of error, we log it in the mail thread, log the + # stack trace and create an empty history line; otherwise, + # the cron will just loop on this reconcile task. + _logger.exception( + "The reconcile task %s had an exception: %s", + rec.name, e.message + ) + message = _("There was an error during reconciliation : %s") \ + % e.message + rec.message_post(body=message) + self.env['easy.reconcile.history'].create( + { + 'easy_reconcile_id': rec.id, + 'date': fields.Datetime.now(), + 'reconcile_ids': [], + 'reconcile_partial_ids': [], + } + ) + finally: + if ctx['commit_every']: + new_cr.commit() + new_cr.close() return True -# @api.model -# def _no_history(self, rec): + @api.multi def _no_history(self): """ Raise an `orm.except_orm` error, supposed to be called when there is no history on the reconciliation @@ -332,7 +323,8 @@ class AccountEasyReconcile(models.Model): @api.multi def open_partial_reconcile(self): - """ Open the view of move line with the unreconciled move lines""" + """ Open the view of move line with the partially + reconciled move lines""" self.ensure_one() obj_move_line = self.env['account.move.line'] lines = obj_move_line.search( diff --git a/account_easy_reconcile/easy_reconcile_history.py b/account_easy_reconcile/easy_reconcile_history.py index 53d64aa8..58904b3e 100644 --- a/account_easy_reconcile/easy_reconcile_history.py +++ b/account_easy_reconcile/easy_reconcile_history.py @@ -1,9 +1,8 @@ # -*- coding: utf-8 -*- ############################################################################## # -# Author: Guewen Baconnier -# Copyright 2012 Camptocamp SA -# Copyright 2015 Camptocamp SA (Damien Crier) +# Author: Guewen Baconnier, Damien Crier +# Copyright 2012, 2015 Camptocamp SA # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -33,7 +32,7 @@ class EasyReconcileHistory(models.Model): @api.one @api.depends('reconcile_ids', 'reconcile_partial_ids') - def _reconcile_line_ids(self): + def _get_reconcile_line_ids(self): move_line_ids = [] for reconcile in self.reconcile_ids: move_lines = reconcile.mapped('line_id') @@ -68,13 +67,13 @@ class EasyReconcileHistory(models.Model): comodel_name='account.move.line', relation='account_move_line_history_rel', string='Reconciled Items', - _compute='_reconcile_line_ids' + compute='_get_reconcile_line_ids' ) partial_line_ids = fields.Many2many( comodel_name='account.move.line', relation='account_move_line_history_partial_rel', string='Partially Reconciled Items', - _compute='_reconcile_line_ids' + compute='_get_reconcile_line_ids' ) company_id = fields.Many2one( 'res.company', @@ -97,16 +96,11 @@ class EasyReconcileHistory(models.Model): "rec_type must be 'full' or 'partial'" move_line_ids = [] if rec_type == 'full': - move_line_ids = [] - for reconcile in self.reconcile_ids: - move_lines = reconcile.mapped('line_id') - move_line_ids.extend(move_lines.ids) + move_line_ids = self.mapped('reconcile_ids.line_id').ids name = _('Reconciliations') else: - move_line_ids = [] - for reconcile in self.reconcile_partial_ids: - move_lines = reconcile.mapped('line_partial_ids') - move_line_ids.extend(move_lines.ids) + move_line_ids = self.mapped( + 'reconcile_partial_ids.line_partial_ids').ids name = _('Partial Reconciliations') return { 'name': name, diff --git a/account_easy_reconcile/res_config.py b/account_easy_reconcile/res_config.py index e4e16da0..090810d3 100644 --- a/account_easy_reconcile/res_config.py +++ b/account_easy_reconcile/res_config.py @@ -1,9 +1,8 @@ # -*- coding: utf-8 -*- ############################################################################## # -# Author: Leonardo Pistone -# Copyright 2014 Camptocamp SA -# Copyright 2015 Camptocamp SA (Damien Crier) +# Author: Leonardo Pistone, Damien Crier +# Copyright 2014, 2015 Camptocamp SA # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -30,7 +29,7 @@ class AccountConfigSettings(models.TransientModel): related="company_id.reconciliation_commit_every", string="How often to commit when performing automatic " "reconciliation.", - help="""Leave zero to commit only at the end of the process.""" + help="Leave zero to commit only at the end of the process." ) @api.multi diff --git a/account_easy_reconcile/simple_reconciliation.py b/account_easy_reconcile/simple_reconciliation.py index b54ffd03..41943517 100644 --- a/account_easy_reconcile/simple_reconciliation.py +++ b/account_easy_reconcile/simple_reconciliation.py @@ -1,9 +1,8 @@ # -*- coding: utf-8 -*- ############################################################################## # -# Copyright 2012-2013 Camptocamp SA (Guewen Baconnier) +# Copyright 2012-2013, 2015 Camptocamp SA (Guewen Baconnier, Damien Crier) # Copyright (C) 2010 Sébastien Beau -# Copyright 2015 Camptocamp SA (Damien Crier) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -32,7 +31,7 @@ class EasyReconcileSimple(models.AbstractModel): # field name used as key for matching the move lines _key_field = None - @api.model + @api.multi def rec_auto_lines_simple(self, lines): if self._key_field is None: raise ValueError("_key_field has to be defined") @@ -70,7 +69,7 @@ class EasyReconcileSimple(models.AbstractModel): def _action_rec(self): """Match only 2 move lines, do not allow partial reconcile""" - select = self._select(self) + select = self._select() select += ", account_move_line.%s " % self._key_field where, params = self._where() where += " AND account_move_line.%s IS NOT NULL " % self._key_field diff --git a/account_easy_reconcile/test/easy_reconcile.yml b/account_easy_reconcile/test/easy_reconcile.yml new file mode 100644 index 00000000..6142a597 --- /dev/null +++ b/account_easy_reconcile/test/easy_reconcile.yml @@ -0,0 +1,116 @@ +- + In order to test Confirm Draft Invoice wizard I create an invoice and confirm it with this wizard +- + !record {model: account.invoice, id: account_invoice_state2}: + account_id: account.a_recv + company_id: base.main_company + currency_id: base.EUR + invoice_line: + - account_id: account.a_sale + name: '[PCSC234] PC Assemble SC234' + price_unit: 1000.0 + quantity: 1.0 + product_id: product.product_product_3 + uos_id: product.product_uom_unit + journal_id: account.bank_journal + partner_id: base.res_partner_12 + reference_type: none +- + I called the "Confirm Draft Invoices" wizard +- + !record {model: account.invoice.confirm, id: account_invoice_confirm_0}: + {} +- + I clicked on Confirm Invoices Button +- + !python {model: account.invoice.confirm}: | + self.invoice_confirm(cr, uid, [ref("account_invoice_confirm_0")], {"lang": 'en_US', + "tz": False, "active_model": "account.invoice", "active_ids": [ref("account_invoice_state2")], + "type": "out_invoice", "active_id": ref("account_invoice_state2"), }) +- + I check that customer invoice state is "Open" +- + !assert {model: account.invoice, id: account_invoice_state2}: + - state == 'open' + + +- + In order to test Bank Statement feature of account I create a bank statement line and confirm it and check it's move created +- + I select the period and journal for the bank statement +- + !python {model: account.bank.statement}: | + import time + journal = self._default_journal_id(cr, uid, {'lang': u'en_US', 'tz': False, 'active_model': 'ir.ui.menu', + 'journal_type': 'bank', 'period_id': time.strftime('%m'), 'active_ids': [ref('account.menu_bank_statement_tree')], 'active_id': ref('account.menu_bank_statement_tree')}) + assert journal, 'Journal has not been selected' +- + I create a bank statement with Opening and Closing balance 0. +- + !record {model: account.bank.statement, id: account_bank_statement_0}: + balance_end_real: 0.0 + balance_start: 0.0 + date: !eval time.strftime('%Y-%m-%d') + journal_id: account.bank_journal +- + I create bank statement line +- + !python {model: account.bank.statement.line}: | + vals = { + 'amount': 1000.0, + 'partner_id': ref('base.res_partner_12'), + 'statement_id': ref('account_bank_statement_0'), + 'name': 'EXT001' + } + + line_id = self.create(cr, uid, vals) + assert line_id, "Account bank statement line has not been created" + +- + We process the reconciliation of the invoice line +- + !python {model: account.bank.statement}: | + line_id = None + invoice = self.pool.get('account.invoice').browse(cr, uid, ref("account_invoice_state2")) + for l in invoice.move_id.line_id: + if l.account_id.id == ref('account.a_recv'): + line_id = l + break + statement = self.browse(cr, uid, ref("account_bank_statement_0")) + for statement_line in statement.line_ids: + self.pool.get('account.bank.statement.line').process_reconciliation( + cr, uid, statement_line.id, + [ + { + 'counterpart_move_line_id': line_id.id, + 'credit': 1000.0, + 'debit': 0.0, + 'name': line_id.name + } + ] + ) +- + We unreconcile previous reconciliation so we can create an easy reconcile record to reconcile invoice +- + !python {model: account.move.line}: | + lines_to_unreconcile = self.search(cr, uid, [('reconcile_ref', '!=', False), ('statement_id', '=', ref("account_bank_statement_0"))]) + self._remove_move_reconcile(cr, uid, lines_to_unreconcile) + +- + We create the easy reconcile record +- + !record {model: account.easy.reconcile, id: account_easy_reconcile_0}: + name: 'easy reconcile 1' + account: account.a_recv + reconcile_method: + - name: 'easy.reconcile.simple.partner' +- + We call the automatic reconcilation method +- + !python {model: account.easy.reconcile}: | + self.run_reconcile(cr, uid, [ref('account_easy_reconcile_0')]) +- + I check that customer invoice state is "Paid" +- + !assert {model: account.invoice, id: account_invoice_state2}: + - state == 'paid' \ No newline at end of file diff --git a/account_easy_reconcile/tests/__init__.py b/account_easy_reconcile/tests/__init__.py index 15fad671..c2feb554 100644 --- a/account_easy_reconcile/tests/__init__.py +++ b/account_easy_reconcile/tests/__init__.py @@ -22,3 +22,4 @@ from . import test_onchange_company from . import test_reconcile_history from . import test_reconcile +from . import test_scenario_reconcile diff --git a/account_easy_reconcile/tests/test_scenario_reconcile.py b/account_easy_reconcile/tests/test_scenario_reconcile.py new file mode 100644 index 00000000..08da279f --- /dev/null +++ b/account_easy_reconcile/tests/test_scenario_reconcile.py @@ -0,0 +1,298 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Author: Damien Crier +# Copyright 2015 Camptocamp SA +# +# 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 . +# +############################################################################## + +from openerp.tests import common +import time + + +class testScenarioReconcile(common.TransactionCase): + + def setUp(self): + super(testScenarioReconcile, self).setUp() + self.rec_history_obj = self.registry('easy.reconcile.history') + self.easy_rec_obj = self.registry('account.easy.reconcile') + self.invoice_obj = self.registry('account.invoice') + self.bk_stmt_obj = self.registry('account.bank.statement') + self.bk_stmt_line_obj = self.registry('account.bank.statement.line') + self.acc_move_line_obj = self.registry('account.move.line') + self.easy_rec_method_obj = ( + self.registry('account.easy.reconcile.method') + ) + self.account_fx_income_id = self.ref("account.income_fx_income") + self.account_fx_expense_id = self.ref("account.income_fx_expense") + self.acs_model = self.registry('account.config.settings') + + acs_ids = self.acs_model.search( + self.cr, self.uid, + [('company_id', '=', self.ref("base.main_company"))] + ) + + values = {'group_multi_currency': True, + 'income_currency_exchange_account_id': + self.account_fx_income_id, + 'expense_currency_exchange_account_id': + self.account_fx_expense_id} + + if acs_ids: + self.acs_model.write(self.cr, self.uid, acs_ids, values) + else: + default_vals = self.acs_model.default_get(self.cr, self.uid, []) + default_vals.update(values) + default_vals['date_stop'] = time.strftime('%Y-12-31') + default_vals['date_start'] = time.strftime('%Y-%m-%d') + default_vals['period'] = 'month' + self.acs_model.create(self.cr, self.uid, default_vals) + + def test_scenario_reconcile(self): + # create invoice + inv_id = self.invoice_obj.create( + self.cr, + self.uid, + { + 'type': 'out_invoice', + 'account_id': self.ref('account.a_recv'), + 'company_id': self.ref('base.main_company'), + 'currency_id': self.ref('base.EUR'), + 'journal_id': self.ref('account.sales_journal'), + 'partner_id': self.ref('base.res_partner_12'), + 'invoice_line': [ + (0, 0, { + 'name': '[PCSC234] PC Assemble SC234', + 'price_unit': 1000.0, + 'quantity': 1.0, + 'product_id': self.ref('product.product_product_3'), + 'uos_id': self.ref('product.product_uom_unit'), + } + ) + ] + } + ) + # validate invoice + self.invoice_obj.signal_workflow( + self.cr, + self.uid, + [inv_id], + 'invoice_open' + ) + invoice_record = self.invoice_obj.browse(self.cr, self.uid, [inv_id]) + self.assertEqual('open', invoice_record.state) + + # create bank_statement + bk_stmt_id = self.bk_stmt_obj.create( + self.cr, + self.uid, + { + 'balance_end_real': 0.0, + 'balance_start': 0.0, + 'date': time.strftime('%Y-%m-%d'), + 'journal_id': self.ref('account.bank_journal'), + 'line_ids': [ + (0, 0, { + 'amount': 1000.0, + 'partner_id': self.ref('base.res_partner_12'), + 'name': invoice_record.number, + 'ref': invoice_record.number, + } + ) + ] + } + ) + + # reconcile + line_id = None + for l in invoice_record.move_id.line_id: + if l.account_id.id == self.ref('account.a_recv'): + line_id = l + break + + statement = self.bk_stmt_obj.browse(self.cr, self.uid, bk_stmt_id) + for statement_line in statement.line_ids: + self.bk_stmt_line_obj.process_reconciliation( + self.cr, self.uid, statement_line.id, + [ + { + 'counterpart_move_line_id': line_id.id, + 'credit': 1000.0, + 'debit': 0.0, + 'name': invoice_record.number, + } + ] + ) + # unreconcile journal item created by previous reconciliation + lines_to_unreconcile = self.acc_move_line_obj.search( + self.cr, + self.uid, + [('reconcile_ref', '!=', False), + ('statement_id', '=', bk_stmt_id)] + ) + self.acc_move_line_obj._remove_move_reconcile( + self.cr, + self.uid, + lines_to_unreconcile + ) + + # create the easy reconcile record + easy_rec_id = self.easy_rec_obj.create( + self.cr, + self.uid, + { + 'name': 'easy_reconcile_1', + 'account': self.ref('account.a_recv'), + 'reconcile_method': [ + (0, 0, { + 'name': 'easy.reconcile.simple.partner', + } + ) + ] + } + ) + # call the automatic reconcilation method + self.easy_rec_obj.run_reconcile( + self.cr, + self.uid, + [easy_rec_id] + ) + self.assertEqual( + 'paid', + self.invoice_obj.browse(self.cr, self.uid, inv_id).state + ) + + def test_scenario_reconcile_currency(self): + # create currency rate + self.registry('res.currency.rate').create(self.cr, self.uid, { + 'name': time.strftime('%Y-%m-%d') + ' 00:00:00', + 'currency_id': self.ref('base.USD'), + 'rate': 1.5, + }) + # create invoice + inv_id = self.invoice_obj.create( + self.cr, + self.uid, + { + 'type': 'out_invoice', + 'account_id': self.ref('account.a_recv'), + 'company_id': self.ref('base.main_company'), + 'currency_id': self.ref('base.USD'), + 'journal_id': self.ref('account.bank_journal_usd'), + 'partner_id': self.ref('base.res_partner_12'), + 'invoice_line': [ + (0, 0, { + 'name': '[PCSC234] PC Assemble SC234', + 'price_unit': 1000.0, + 'quantity': 1.0, + 'product_id': self.ref('product.product_product_3'), + 'uos_id': self.ref('product.product_uom_unit'), + } + ) + ] + } + ) + # validate invoice + self.invoice_obj.signal_workflow( + self.cr, + self.uid, + [inv_id], + 'invoice_open' + ) + invoice_record = self.invoice_obj.browse(self.cr, self.uid, [inv_id]) + self.assertEqual('open', invoice_record.state) + + # create bank_statement + bk_stmt_id = self.bk_stmt_obj.create( + self.cr, + self.uid, + { + 'balance_end_real': 0.0, + 'balance_start': 0.0, + 'date': time.strftime('%Y-%m-%d'), + 'journal_id': self.ref('account.bank_journal_usd'), + 'line_ids': [ + (0, 0, { + 'amount': 1000.0, + 'amount_currency': 1500.0, + 'currency_id': self.ref('base.USD'), + 'partner_id': self.ref('base.res_partner_12'), + 'name': invoice_record.number, + 'ref': invoice_record.number, + } + ) + ] + } + ) + + # reconcile + line_id = None + for l in invoice_record.move_id.line_id: + if l.account_id.id == self.ref('account.a_recv'): + line_id = l + break + + statement = self.bk_stmt_obj.browse(self.cr, self.uid, bk_stmt_id) + for statement_line in statement.line_ids: + self.bk_stmt_line_obj.process_reconciliation( + self.cr, self.uid, statement_line.id, + [ + { + 'counterpart_move_line_id': line_id.id, + 'credit': 1000.0, + 'debit': 0.0, + 'name': invoice_record.number, + } + ] + ) + # unreconcile journal item created by previous reconciliation + lines_to_unreconcile = self.acc_move_line_obj.search( + self.cr, + self.uid, + [('reconcile_ref', '!=', False), + ('statement_id', '=', bk_stmt_id)] + ) + self.acc_move_line_obj._remove_move_reconcile( + self.cr, + self.uid, + lines_to_unreconcile + ) + + # create the easy reconcile record + easy_rec_id = self.easy_rec_obj.create( + self.cr, + self.uid, + { + 'name': 'easy_reconcile_1', + 'account': self.ref('account.a_recv'), + 'reconcile_method': [ + (0, 0, { + 'name': 'easy.reconcile.simple.partner', + } + ) + ] + } + ) + # call the automatic reconcilation method + self.easy_rec_obj.run_reconcile( + self.cr, + self.uid, + [easy_rec_id] + ) + self.assertEqual( + 'paid', + self.invoice_obj.browse(self.cr, self.uid, inv_id).state + )