Browse Source

Merge fe7375c981 into 09ea7e6e78

pull/99/merge
Holger Brunn 5 years ago
committed by GitHub
parent
commit
8c20959e31
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 88
      account_bank_statement_import_auto_reconcile/README.rst
  2. 4
      account_bank_statement_import_auto_reconcile/__init__.py
  3. 29
      account_bank_statement_import_auto_reconcile/__openerp__.py
  4. 15
      account_bank_statement_import_auto_reconcile/demo/account_bank_statement_import_auto_reconcile_rule.xml
  5. 10
      account_bank_statement_import_auto_reconcile/models/__init__.py
  6. 47
      account_bank_statement_import_auto_reconcile/models/account_bank_statement_import.py
  7. 74
      account_bank_statement_import_auto_reconcile/models/account_bank_statement_import_auto_reconcile.py
  8. 75
      account_bank_statement_import_auto_reconcile/models/account_bank_statement_import_auto_reconcile_exact_amount.py
  9. 20
      account_bank_statement_import_auto_reconcile/models/account_bank_statement_import_auto_reconcile_odoo.py
  10. 156
      account_bank_statement_import_auto_reconcile/models/account_bank_statement_import_auto_reconcile_rule.py
  11. 37
      account_bank_statement_import_auto_reconcile/models/account_bank_statement_import_reapply_rules.py
  12. 15
      account_bank_statement_import_auto_reconcile/models/account_journal.py
  13. 3
      account_bank_statement_import_auto_reconcile/security/ir.model.access.csv
  14. BIN
      account_bank_statement_import_auto_reconcile/static/description/icon.png
  15. 4
      account_bank_statement_import_auto_reconcile/tests/__init__.py
  16. 103
      account_bank_statement_import_auto_reconcile/tests/test_account_bank_statement_import_auto_reconcile.py
  17. 13
      account_bank_statement_import_auto_reconcile/views/account_bank_statement.xml
  18. 15
      account_bank_statement_import_auto_reconcile/views/account_bank_statement_import.xml
  19. 26
      account_bank_statement_import_auto_reconcile/views/account_bank_statement_import_auto_reconcile_exact_amount.xml
  20. 24
      account_bank_statement_import_auto_reconcile/views/account_bank_statement_import_auto_reconcile_rule.xml
  21. 18
      account_bank_statement_import_auto_reconcile/views/account_bank_statement_import_reapply_rules.xml
  22. 14
      account_bank_statement_import_auto_reconcile/views/account_journal.xml
  23. 5
      oca_dependencies.txt

88
account_bank_statement_import_auto_reconcile/README.rst

@ -0,0 +1,88 @@
.. 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
=====================================
Automatic reconciliation after import
=====================================
This addon allows you have Odoo reconcile transactions from a bank statement import automatically in cases where the matching transaction can be determined unambigously.
Configuration
=============
To configure this module, you need to:
#. go to the journal your bank account uses
#. in the field ``Automatic reconciliation rules``, add at least one rule
Usage
=====
After a journal is configured for automatic reconciliations, it simply happens during an import on this journal. If there were automatic reconciliations, you'll see a notification about that and the lines in question will also show up as reconciled.
Reconciliation rules
--------------------
Odoo standard
Do exactly what Odoo does when proposing reconciliations. This searches for an exact match on amount and reference first, but falls back to less exact matches if none are found before. If there's only one match, do the reconciliation
Exact amount and reference
Strictly only match if we have the same partner, amount and reference. Check at least one field one the statement and one field on the move lines to tell the rule which fields to match with each other.
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/174/8.0
Background
==========
Mainly, this module is a framework for conveniently (for programmers) adding new custom automatic reconciliation rules. To do this, study the provided AbstractModels.
Known issues / Roadmap
======================
* add more matching rules:
* AmountDiffuse (let the user configure the threshold)
* SameCompany (if A from company C bought it, but B from the same company/organization pays)
* AmountTransposedDigits (reconcile if only two digits are swapped. Dangerous and a special case of AmountDiffuse)
* whatever else we can think of
* add some helpers/examples for using the options field
* allow to fiddle with the parameters of configured rules during a specific import
Bug Tracker
===========
Bugs are tracked on `GitHub Issues
<https://github.com/OCA/bank-statement-import/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.
Credits
=======
Images
------
* Odoo Community Association: `Icon <https://github.com/OCA/maintainer-tools/blob/master/template/module/static/description/icon.svg>`_.
Contributors
------------
* Holger Brunn <hbrunn@therp.nl>
Do not contact contributors directly about help with questions or problems concerning this addon, but use the `community mailing list <mailto:community@mail.odoo.com>`_ or the `appropriate specialized mailinglist <https://odoo-community.org/groups>`_ for help, and the bug tracker linked in `Bug Tracker`_ above for technical issues.
Maintainer
----------
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
This module is maintained by the OCA.
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
To contribute to this module, please visit https://odoo-community.org.

4
account_bank_statement_import_auto_reconcile/__init__.py

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# © 2017 Therp BV <http://therp.nl>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import models

29
account_bank_statement_import_auto_reconcile/__openerp__.py

@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
# © 2017 Therp BV <http://therp.nl>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
{
"name": "Automatic reconciliation after import",
"version": "8.0.1.0.0",
"author": "Therp BV,Odoo Community Association (OCA)",
"license": "AGPL-3",
"category": 'Banking addons',
"summary": "This module allows you to define automatic "
"reconciliation rules to be run after a bank statement is imported",
"depends": [
'account_bank_statement_import',
'web_widget_one2many_tags',
'base_domain_operator',
],
"demo": [
"demo/account_bank_statement_import_auto_reconcile_rule.xml",
],
"data": [
"views/account_bank_statement_import_reapply_rules.xml",
"views/account_bank_statement.xml",
"views/account_bank_statement_import_auto_reconcile_exact_amount.xml",
"views/account_bank_statement_import.xml",
"views/account_journal.xml",
"views/account_bank_statement_import_auto_reconcile_rule.xml",
'security/ir.model.access.csv',
],
}

15
account_bank_statement_import_auto_reconcile/demo/account_bank_statement_import_auto_reconcile_rule.xml

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<record id="rule_amount_exact" model="account.bank.statement.import.auto.reconcile.rule">
<field name="journal_id" ref="account.bank_journal" />
<field name="rule_type">account.bank.statement.import.auto.reconcile.exact.amount</field>
<field name="match_st_name" eval="True" />
<field name="match_st_ref" eval="True" />
<field name="match_move_name" eval="True" />
<field name="match_move_ref" eval="True" />
<field name="match_line_name" eval="True" />
<field name="match_line_ref" eval="True" />
</record>
</data>
</openerp>

10
account_bank_statement_import_auto_reconcile/models/__init__.py

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# © 2017 Therp BV <http://therp.nl>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import account_bank_statement_import_auto_reconcile
from . import account_bank_statement_import_auto_reconcile_rule
from . import account_journal
from . import account_bank_statement_import_auto_reconcile_odoo
from . import account_bank_statement_import
from . import account_bank_statement_import_auto_reconcile_exact_amount
from . import account_bank_statement_import_reapply_rules

47
account_bank_statement_import_auto_reconcile/models/account_bank_statement_import.py

@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
# © 2017 Therp BV <http://therp.nl>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp import _, api, fields, models
# pylint: disable=R7980
class AccountBankStatementImport(models.TransientModel):
_inherit = 'account.bank.statement.import'
auto_reconcile = fields.Boolean('Auto reconcile', default=True)
@api.model
def _create_bank_statement(self, stmt_vals):
statement_id, notifications = super(
AccountBankStatementImport, self
)._create_bank_statement(stmt_vals)
if not statement_id:
return statement_id, notifications
statement = self.env['account.bank.statement'].browse(statement_id)
if (
not statement.journal_id
.statement_import_auto_reconcile_rule_ids or
not self.auto_reconcile
):
return statement_id, notifications
reconcile_rules = statement.journal_id\
.statement_import_auto_reconcile_rule_ids.get_rules()
auto_reconciled_ids = []
for line in statement.line_ids:
for rule in reconcile_rules:
if rule.reconcile(line):
auto_reconciled_ids.append(line.id)
break
if auto_reconciled_ids:
notifications.append({
'type': 'warning',
'message':
_("%d transactions were reconciled automatically.") %
len(auto_reconciled_ids),
'details': {
'name': _('Automatically reconciled'),
'model': 'account.bank.statement.line',
'ids': auto_reconciled_ids,
},
})
return statement_id, notifications

74
account_bank_statement_import_auto_reconcile/models/account_bank_statement_import_auto_reconcile.py

@ -0,0 +1,74 @@
# -*- coding: utf-8 -*-
# © 2017 Therp BV <http://therp.nl>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp.tools import float_compare, float_round
from openerp import api, fields, models
class AccountBankStatementImportAutoReconcile(models.AbstractModel):
"""Inherit from this class and implement the reconcile function"""
_name = 'account.bank.statement.import.auto.reconcile'
_description = 'Base class for automatic reconciliation rules'
wizard_id = fields.Many2one('account.bank.statement.import', required=True)
# this is meant as a field to save options in and/or to carry state
# between different reconciliations
options = fields.Serialized('Options')
@property
def _digits(self):
try:
return self.__digits
except:
self.__digits = self.env['decimal.precision'].precision_get(
'Account'
)
return self.__digits
@api.model
def _round(self, value):
return float_round(value, precision_digits=self._digits)
@api.model
def _matches_amount(self, statement_line, debit, credit):
"""helper to compare if an amount matches some move line data"""
return (
float_compare(
debit, statement_line.amount,
precision_digits=self._digits
) == 0 or
float_compare(
-credit, statement_line.amount,
precision_digits=self._digits
) == 0
)
@api.model
def _reconcile_move_line(self, statement_line, move_line_id):
"""Helper to reconcile some move line with a bank statement.
This will create a move to reconcile with and assigns journal_entry_id
"""
move = self.env['account.move'].create(
self.env['account.bank.statement']._prepare_move(
statement_line,
(
statement_line.statement_id.name or statement_line.name
) + "/" + str(statement_line.sequence or '')
)
)
move_line_dict = self.env['account.bank.statement']\
._prepare_bank_move_line(
statement_line, move.id, -statement_line.amount,
statement_line.statement_id.company_id.currency_id.id,
)
move_line_dict['counterpart_move_line_id'] = move_line_id
statement_line.process_reconciliation([move_line_dict])
@api.multi
def reconcile(self, statement_line):
"""Will be called on your model, with wizard_id pointing
to the currently open statement import wizard. If your rule consumes
any options or similar, get the values from there.
Return True if you reconciled this line, something Falsy otherwise"""
raise NotImplementedError()

75
account_bank_statement_import_auto_reconcile/models/account_bank_statement_import_auto_reconcile_exact_amount.py

@ -0,0 +1,75 @@
# -*- coding: utf-8 -*-
# © 2017 Therp BV <http://therp.nl>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp import api, fields, models
# pylint: disable=R7980
class AccountBankStatementImportAutoReconcileExactAmount(models.AbstractModel):
_inherit = 'account.bank.statement.import.auto.reconcile'
_name = 'account.bank.statement.import.auto.reconcile.exact.amount'
_description = 'Exact partner, amount and reference'
substring_match = fields.Boolean('Match for substrings', default=False)
case_sensitive = fields.Boolean('Case sensitive matching', default=False)
match_st_ref = fields.Boolean('Reference from st. line', default=True)
match_st_name = fields.Boolean('Name from st. line', default=True)
match_move_ref = fields.Boolean('Move reference', default=True)
match_move_name = fields.Boolean('Move name', default=True)
match_line_ref = fields.Boolean('Move line reference', default=True)
match_line_name = fields.Boolean('Move line name', default=True)
@api.multi
def reconcile(self, statement_line):
if not statement_line.partner_id or (
not statement_line.ref and not statement_line.name
):
return
operator = '=ilike'
if self.substring_match:
operator = 'substring_of'
elif self.case_sensitive:
operator = '=like'
amount_field = 'debit'
sign = 1
if statement_line.currency_id or statement_line.journal_id.currency:
amount_field = 'amount_currency'
elif statement_line.amount < 0:
amount_field = 'credit'
sign = -1
statement_fields = filter(None, [
self.match_st_name and 'name' or None,
self.match_st_ref and 'ref' or None,
])
move_line_fields = filter(None, [
self.match_move_ref and 'move_id.ref' or None,
self.match_move_name and 'move_id.name' or None,
self.match_line_ref and 'ref' or None,
self.match_line_name and 'name' or None,
])
domain = [
('reconcile_id', '=', False),
('state', '=', 'valid'),
('account_id.reconcile', '=', True),
('partner_id', '=', statement_line.partner_id.id),
(amount_field, '=', self._round(sign * statement_line.amount)),
]
domain += (
len(statement_fields) * len(move_line_fields) - 1
) * ['|']
for move_line_field in move_line_fields:
for statement_field in statement_fields:
value = statement_line[statement_field]
domain.append((move_line_field, operator, value))
move_lines = self.env['account.move.line'].search(domain, limit=2)
if move_lines and len(move_lines) == 1:
self._reconcile_move_line(statement_line, move_lines.id)
return True

20
account_bank_statement_import_auto_reconcile/models/account_bank_statement_import_auto_reconcile_odoo.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# © 2017 Therp BV <http://therp.nl>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp import api, models
class AccountBankStatementImportAutoReconcileOdoo(models.AbstractModel):
_inherit = 'account.bank.statement.import.auto.reconcile'
_name = 'account.bank.statement.import.auto.reconcile.odoo'
_description = 'Odoo standard'
@api.multi
def reconcile(self, statement_line):
"""Find an open invoice for the statement line's partner"""
matches = statement_line.get_reconciliation_proposition(statement_line)
if len(matches) == 1 and self._matches_amount(
statement_line, matches[0]['debit'], -matches[0]['credit'],
):
self._reconcile_move_line(statement_line, matches[0]['id'])
return True

156
account_bank_statement_import_auto_reconcile/models/account_bank_statement_import_auto_reconcile_rule.py

@ -0,0 +1,156 @@
# -*- coding: utf-8 -*-
# © 2017 Therp BV <http://therp.nl>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from lxml import etree
from openerp import _, api, exceptions, fields, models, tools
from .account_bank_statement_import_auto_reconcile import\
AccountBankStatementImportAutoReconcile as auto_reconcile_base
class AccountBankStatementImportAutoReconcileRule(models.Model):
_name = 'account.bank.statement.import.auto.reconcile.rule'
_description = 'Automatic reconciliation rule'
rule_type = fields.Selection('_sel_rule_type', required=True)
journal_id = fields.Many2one('account.journal', 'Journal', required=True)
options = fields.Serialized('Options')
@api.model
@tools.ormcache()
def _get_model_names(self):
return [
model for model in self.env.registry
if self.env[model]._name != auto_reconcile_base._name and
issubclass(self.env[model].__class__, auto_reconcile_base)
]
@api.model
def _sel_rule_type(self):
return self.env['ir.model'].search([
('model', 'in', self._get_model_names()),
]).mapped(lambda x: (x.model, x.name))
@api.constrains('rule_type')
def _check_rule_type(self):
for this in self:
if this.mapped(
'journal_id.statement_import_auto_reconcile_rule_ids'
).filtered(lambda x: x != this and x.rule_type == this.rule_type):
raise exceptions.ValidationError(
_('Reconciliation rules must be unique per journal')
)
@api.model
def create(self, values):
self._options_from_values(values)
return super(AccountBankStatementImportAutoReconcileRule, self).create(
values
)
@api.multi
def write(self, values):
self._options_from_values(values)
return super(AccountBankStatementImportAutoReconcileRule, self).write(
values
)
@api.multi
def read(self, fields=None, load='_classic_read'):
rule_type_fields = []
self_fields = []
for field in fields or []:
if field in self._fields:
self_fields.append(field)
else:
rule_type_fields.append(field)
if self_fields and rule_type_fields and 'options' not in self_fields:
self_fields.append('options')
result = super(AccountBankStatementImportAutoReconcileRule, self)\
.read(fields=self_fields or None, load=load)
if not rule_type_fields:
return result
defaults = {}
for model_name in self._get_model_names():
defaults.update(self.env[model_name].default_get(rule_type_fields))
for res in result:
for field in rule_type_fields:
res[field] = res['options'].get(field, defaults.get(field))
return result
@api.model
def fields_view_get(self, view_id=None, view_type='form', toolbar=False,
submenu=False):
"""Carve a view such that we can inject every field the view of the
currently selected matching rule into our form"""
# TODO: at a certain point, we'll have to namespace field names in
# order to avoid clashes
result = super(AccountBankStatementImportAutoReconcileRule, self)\
.fields_view_get(view_id=view_id, view_type=view_type,
toolbar=toolbar, submenu=submenu)
standard_fields = set(self.env[auto_reconcile_base._name]._fields)
arch = etree.fromstring(result['arch'])
container = arch.xpath('//div[@name="rule_options"]')[0]
for model_name in self._get_model_names():
fields_view = self.env[model_name].fields_view_get()
if set(fields_view['fields']).issubset(standard_fields):
# this is an autogenerated form
continue
group = etree.SubElement(
container,
'div',
modifiers='{"invisible": [["rule_type", "!=", "%s"]]}' % (
model_name,
)
)
form = etree.fromstring(fields_view['arch'])
for element in form:
group.append(element)
for field in group.xpath('descendant::field[@modifiers]'):
# TODO: merging modifiers would be better
del field.attrib['modifiers']
for key, value in fields_view['fields'].iteritems():
result['fields'][key] = dict(value, readonly=False)
result['arch'] = etree.tostring(arch)
return result
@api.multi
def name_get(self):
return [
(this.id, self.env[this.rule_type]._description)
for this in self
]
@api.multi
def _options_from_values(self, values):
"""Write values we got from the user into options dict"""
if 'options' in values:
return
rule = values.get('rule_type', self and self[:1].rule_type or None)
if not rule or rule not in self.env.registry:
return
rule_model = self.env[rule]
options = self and self[:1].options or {}
for field_name in rule_model._fields:
if field_name in values:
options[field_name] = values.pop(field_name)
values['options'] = options
@api.multi
def get_rules(self):
"""Return a NewId object for the configured rule"""
rules = self.mapped(
lambda x: self.env[x.rule_type].new(dict(
self.env[x.rule_type].default_get(
self.env[x.rule_type]._fields.keys()
),
wizard_id=self.id,
options=x.options,
))
if x else None
)
for rule in rules:
rule.update(rule.options)
return rules

37
account_bank_statement_import_auto_reconcile/models/account_bank_statement_import_reapply_rules.py

@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
# © 2017 Therp BV <http://therp.nl>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp import _, api, fields, models
from openerp.exceptions import Warning as UserError
class AccountBankStatementImportReapplyRules(models.TransientModel):
_inherit = 'account.bank.statement.import'
_name = 'account.bank.statement.import.reapply.rules'
data_file = fields.Binary(required=False)
@api.multi
def action_reapply_rules(self):
statements = self.env['account.bank.statement'].browse(
self.env.context.get('active_ids', [])
)
journal = statements.mapped('journal_id')
if len(journal) != 1:
raise UserError(_(
'You can only reapply rules on statements with the same '
'journal!'
))
self.write({'journal_id': journal.id})
reconcile_rules = journal.statement_import_auto_reconcile_rule_ids\
.get_rules()
for line in self.env['account.bank.statement.line'].search([
('statement_id', 'in', statements.ids),
('journal_entry_id', '=', False),
]):
for rule in reconcile_rules:
if rule.reconcile(line):
break
return {'type': 'ir.actions.act_window_close'}

15
account_bank_statement_import_auto_reconcile/models/account_journal.py

@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
# © 2017 Therp BV <http://therp.nl>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp import fields, models
class AccountJournal(models.Model):
_inherit = 'account.journal'
statement_import_auto_reconcile_rule_ids = fields.One2many(
'account.bank.statement.import.auto.reconcile.rule',
'journal_id', string='Automatic reconciliation rules',
help='When importing a bank statement into this journal ,'
'apply the following rules for automatic reconciliation',
)

3
account_bank_statement_import_auto_reconcile/security/ir.model.access.csv

@ -0,0 +1,3 @@
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
account_bank_statement_import_auto_reconcile_rule,account.bank.statement.import.auto.reconcile.rule manager,model_account_bank_statement_import_auto_reconcile_rule,account.group_account_user,1,0,0,0
account_bank_statement_import_auto_reconcile_rule_manager,account.bank.statement.import.auto.reconcile.rule manager,model_account_bank_statement_import_auto_reconcile_rule,account.group_account_manager,1,1,1,1

BIN
account_bank_statement_import_auto_reconcile/static/description/icon.png

After

Width: 128  |  Height: 128  |  Size: 9.2 KiB

4
account_bank_statement_import_auto_reconcile/tests/__init__.py

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# © 2017 Therp BV <http://therp.nl>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import test_account_bank_statement_import_auto_reconcile

103
account_bank_statement_import_auto_reconcile/tests/test_account_bank_statement_import_auto_reconcile.py

@ -0,0 +1,103 @@
# -*- coding: utf-8 -*-
# © 2017 Therp BV <http://therp.nl>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import base64
from datetime import timedelta
from openerp import fields
from openerp.tests.common import TransactionCase
from openerp.addons.account_bank_statement_import.models\
.account_bank_statement_import import AccountBankStatementImport
class TestAccountBankStatementImportAutoReconcile(TransactionCase):
def setUp(self):
super(TestAccountBankStatementImportAutoReconcile, self).setUp()
# we don't really have something to import, so we patch the
# import routine to return what we want for our tests
self.original_parse_file = AccountBankStatementImport._parse_file
AccountBankStatementImport._parse_file = self._parse_file
self.invoice = self.env.ref('account.invoice_4')
self.rule = self.env.ref(
'account_bank_statement_import_auto_reconcile.rule_amount_exact'
)
def tearDown(self):
super(TestAccountBankStatementImportAutoReconcile, self).tearDown()
AccountBankStatementImport._parse_file = self.original_parse_file
def _parse_file(self, data):
date = self.invoice.date_invoice
return [
{
'currency_code': self.invoice.company_id.currency_id.name,
'account_number': False,
'name': 'Auto reconcile test',
'date': fields.Date.to_string(
fields.Date.from_string(date) + timedelta(days=5)
),
'transactions': [
{
'name': self.invoice.number,
'date': fields.Date.to_string(
fields.Date.from_string(date) + timedelta(days=5)
),
'amount': self.invoice.residual,
'unique_import_id': '42',
'account_number':
self.invoice.partner_id.bank_ids[:1].acc_number,
},
],
},
]
def test_account_bank_statement_import_auto_reconcile(self):
# first, we do an import with auto reconciliation turned off
action = self.env['account.bank.statement.import'].create({
'data_file': base64.b64encode('hello world'),
'journal_id': self.env.ref('account.bank_journal').id,
'auto_reconcile': False,
}).import_file()
# nothing should have happened
self.assertEqual(self.invoice.state, 'open')
self.env['account.bank.statement'].browse(
action['context']['statement_ids']
).unlink()
# now we do matching, but manipulate the matching to work on the
# ref field only which is empty in our example
self.rule.write({'match_st_name': False})
action = self.env['account.bank.statement.import'].create({
'data_file': base64.b64encode('hello world'),
'journal_id': self.env.ref('account.bank_journal').id,
'auto_reconcile': True,
}).import_file()
# nothing should have happened
self.assertEqual(self.invoice.state, 'open')
self.env['account.bank.statement'].browse(
action['context']['statement_ids']
).unlink()
# for exact amount matching, our first transaction should be matched
# to the invoice's move line, marking the invoice as paid,
# provided we allow matching by name
self.rule.write({'match_st_name': True})
action = self.env['account.bank.statement.import'].create({
'data_file': base64.b64encode('hello world'),
'journal_id': self.env.ref('account.bank_journal').id,
'auto_reconcile': True,
}).import_file()
self.assertEqual(self.invoice.state, 'paid')
def test_rule_options(self):
self.rule.unlink()
rule = self.env[
'account.bank.statement.import.auto.reconcile.rule'
].create({
'journal_id': self.env.ref('account.bank_journal').id,
'rule_type':
'account.bank.statement.import.auto.reconcile.exact.amount',
'match_st_name': False,
})
rules = rule.get_rules()
# explicitly written
self.assertFalse(rules.match_st_name)
# defaults must be used here too
self.assertTrue(rules.match_st_ref)

13
account_bank_statement_import_auto_reconcile/views/account_bank_statement.xml

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<act_window
id="action_reapply_rules"
src_model="account.bank.statement"
res_model="account.bank.statement.import.reapply.rules"
name="Reapply matching rules"
target="new"
view_id="account_bank_statement_import_reapply_rules_form"
/>
</data>
</openerp>

15
account_bank_statement_import_auto_reconcile/views/account_bank_statement_import.xml

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<record id="account_bank_statement_import_view" model="ir.ui.view">
<field name="model">account.bank.statement.import</field>
<field name="inherit_id" ref="account_bank_statement_import.account_bank_statement_import_view" />
<field name="arch" type="xml">
<field name="journal_id" position="after">
<field name="auto_reconcile" attrs="{'invisible': [('hide_journal_field', '=', True)]}" class="oe_inline" />
<label for="auto_reconcile" />
</field>
</field>
</record>
</data>
</openerp>

26
account_bank_statement_import_auto_reconcile/views/account_bank_statement_import_auto_reconcile_exact_amount.xml

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<record id="account_bank_statement_import_auto_reconcile_exact_amount_form" model="ir.ui.view">
<field name="model">account.bank.statement.import.auto.reconcile.exact.amount</field>
<field name="arch" type="xml">
<form>
<group name="statement_fields" string="On the statement, match...">
<field name="match_st_ref" />
<field name="match_st_name" />
</group>
<group name="move_line_fields" string="... with the following fields">
<field name="match_move_ref" />
<field name="match_move_name" />
<field name="match_line_ref" />
<field name="match_line_name" />
</group>
<group name="options" string="Options">
<field name="substring_match" />
<field name="case_sensitive" />
</group>
</form>
</field>
</record>
</data>
</openerp>

24
account_bank_statement_import_auto_reconcile/views/account_bank_statement_import_auto_reconcile_rule.xml

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<record id="account_bank_statement_import_auto_reconcile_rule_form" model="ir.ui.view">
<field name="model">account.bank.statement.import.auto.reconcile.rule</field>
<field name="arch" type="xml">
<form>
<group>
<field name="rule_type" />
</group>
<div name="rule_options" attrs="{'invisible': [('rule_type', '=', False)]}" />
</form>
</field>
</record>
<record id="account_bank_statement_import_auto_reconcile_rule_tree" model="ir.ui.view">
<field name="model">account.bank.statement.import.auto.reconcile.rule</field>
<field name="arch" type="xml">
<tree>
<field name="display_name" />
</tree>
</field>
</record>
</data>
</openerp>

18
account_bank_statement_import_auto_reconcile/views/account_bank_statement_import_reapply_rules.xml

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<record id="account_bank_statement_import_reapply_rules_form" model="ir.ui.view">
<field name="model">account.bank.statement.import.reapply.rules</field>
<field name="arch" type="xml">
<form>
<div>This wizard will reapply the journal's matching rules on the selected bank statement(s)</div>
<footer>
<button name="action_reapply_rules" string="Reapply rules" type="object" class="oe_highlight" />
or
<button special="cancel" string="Cancel" class="oe_link" />
</footer>
</form>
</field>
</record>
</data>
</openerp>

14
account_bank_statement_import_auto_reconcile/views/account_journal.xml

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<record id="view_account_journal_form" model="ir.ui.view">
<field name="model">account.journal</field>
<field name="inherit_id" ref="account.view_account_journal_form" />
<field name="arch" type="xml">
<field name="default_credit_account_id" position="after">
<field name="statement_import_auto_reconcile_rule_ids" widget="one2many_tags" />
</field>
</field>
</record>
</data>
</openerp>

5
oca_dependencies.txt

@ -0,0 +1,5 @@
# list the OCA project dependencies, one per line
# add a github url if you need a forked version
web
# TODO: change this back to server-tools once https://github.com/OCA/server-tools/pull/738 is merged
server-tools https://github.com/hbrunn/server-tools 8.0-base_domain_operator
Loading…
Cancel
Save