mirror of
https://github.com/OCA/account-reconcile.git
synced 2025-01-20 12:27:39 +02:00
Add module account_statement_operation_rule
This module allows to automatically apply Statement Operations (or "presets") on the statement lines reconciliations according to rules, for instance based on the balance.
This commit is contained in:
101
account_statement_operation_rule/README.rst
Normal file
101
account_statement_operation_rule/README.rst
Normal file
@@ -0,0 +1,101 @@
|
||||
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
|
||||
:alt: License
|
||||
|
||||
Bank Statement Operation Rules
|
||||
==============================
|
||||
|
||||
This module complements the Reconciliation of the bank statements. When
|
||||
the bank statement matches one or more journal entry for a line and
|
||||
there is a remaining balance, Odoo proposes you to click on buttons that
|
||||
will generate write-off entries according to pre-configured *Statement
|
||||
Operation Templates*. The aim of this module is to automatically click
|
||||
for you on these buttons (i.e. create the write-off journal entries)
|
||||
when some rules are respected, rules that you can configure.
|
||||
|
||||
It contains 2 types of rules (but can be extended with additional rules),
|
||||
described below:
|
||||
|
||||
Roundings
|
||||
The most basic rule: when the remaining balance is within a range, 1
|
||||
or more operations are applied.
|
||||
|
||||
Currencies
|
||||
When the remaining balance is within a range and the currency of all
|
||||
the lines is the same but different from the company's, and the amount
|
||||
currency is the same, 1 or more operations are applied.
|
||||
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
As this module aims to automatize the ``Statement Operation Templates``,
|
||||
you first want to ensure that you have at least one operation configured.
|
||||
You can find them in ``Invoicing > Configuration > Miscellaneous >
|
||||
Statement Operation Templates``. An example of a common operation is:
|
||||
|
||||
=================== ========================== ======= ========
|
||||
Account Amount Type Amount Label
|
||||
=================== ========================== ======= ========
|
||||
Depends of the l10n Percentage of open balance 100.0 % Rounding
|
||||
=================== ========================== ======= ========
|
||||
|
||||
The configuration of the rules themselves happens in ``Invoicing >
|
||||
Configuration > Miscellaneous > Statement Operation Rules``. Refer to
|
||||
the description of the types of rules above in case of doubt. The form
|
||||
is divided in 2 parts: **Rule** and **Result**. The rule part is where
|
||||
you will set the conditions and the result part is what operations will
|
||||
be done if the conditions are valid.
|
||||
|
||||
For the **Roundings** rules, you will set a min. and a max. amount. It
|
||||
can be negative or positive. The amount is compared to the remaining
|
||||
balance when lines are matched in the bank statement. Example: if you
|
||||
want to create a move line in a loss account when you received 1.- not
|
||||
enough, you can create a rule with an min. amount of -1.0 and a max.
|
||||
amount of 0.0.
|
||||
|
||||
For the **Currencies** rules, the min. and max. amount have the same
|
||||
properties, but you will also set the currencies for which the rule
|
||||
applies. Setting the currency allows to configure different amounts
|
||||
according to the currencies.
|
||||
|
||||
Only the first rule matching the current situation is used, so if you
|
||||
have several rules overlapping for some reason, be sure to order them
|
||||
appropriately in the list view.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
When you use the *Reconcile* button of a bank statement, Odoo
|
||||
automatically proposes you matching journal entries for each statement
|
||||
line. This module automatically adds journal entries generated from the
|
||||
*Statement Operation Templates* if a rule matches with the current
|
||||
situation, so there is nothing special to do once the rules are
|
||||
configured.
|
||||
|
||||
.. 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
|
||||
|
||||
Credits
|
||||
=======
|
||||
|
||||
Contributors
|
||||
------------
|
||||
|
||||
* Guewen Baconnier <guewen.baconnier@camptocamp.com>
|
||||
|
||||
Maintainer
|
||||
----------
|
||||
|
||||
.. image:: http://odoo-community.org/logo.png
|
||||
:alt: Odoo Community Association
|
||||
:target: http://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
|
||||
http://odoo-community.org.
|
||||
3
account_statement_operation_rule/__init__.py
Normal file
3
account_statement_operation_rule/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import model
|
||||
36
account_statement_operation_rule/__openerp__.py
Normal file
36
account_statement_operation_rule/__openerp__.py
Normal file
@@ -0,0 +1,36 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# Author: Guewen Baconnier
|
||||
# Copyright 2014 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
{'name': 'Bank Statement Operation Rules',
|
||||
'version': '8.0.1.0.0',
|
||||
'author': 'Camptocamp',
|
||||
'maintainer': 'Camptocamp',
|
||||
'license': 'AGPL-3',
|
||||
'category': 'Accounting & Finance',
|
||||
'depends': ['account',
|
||||
],
|
||||
'website': 'http://www.camptocamp.com',
|
||||
'data': ['view/account_statement_operation_rule.xml',
|
||||
'view/account_statement_operation_rule_view.xml',
|
||||
],
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
}
|
||||
3
account_statement_operation_rule/model/__init__.py
Normal file
3
account_statement_operation_rule/model/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import account_statement_operation_rule
|
||||
@@ -0,0 +1,138 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# Author: Guewen Baconnier
|
||||
# Copyright 2014 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp import models, fields, api
|
||||
from openerp.addons import decimal_precision as dp
|
||||
|
||||
|
||||
class AccountStatementOperationRule(models.Model):
|
||||
_name = 'account.statement.operation.rule'
|
||||
|
||||
_order = 'sequence ASC, id ASC'
|
||||
|
||||
name = fields.Char()
|
||||
rule_type = fields.Selection(
|
||||
selection=[('balance', 'Balance'),
|
||||
('currency', 'Currencies')],
|
||||
string='Type',
|
||||
default='balance',
|
||||
required=True,
|
||||
)
|
||||
operations = fields.Many2many(
|
||||
comodel_name='account.statement.operation.template',
|
||||
relation='account_statement_oper_rule_rel',
|
||||
)
|
||||
amount_min = fields.Float(
|
||||
string='Min. Amount',
|
||||
digits=dp.get_precision('Account'),
|
||||
)
|
||||
amount_max = fields.Float(
|
||||
string='Max. Amount',
|
||||
digits=dp.get_precision('Account'),
|
||||
)
|
||||
sequence = fields.Integer(
|
||||
default=20,
|
||||
help="If several rules match, the first one is used.",
|
||||
)
|
||||
|
||||
@api.multi
|
||||
def _is_valid_balance(self, statement_line, move_lines, balance):
|
||||
# TODO use float compare
|
||||
return self.amount_min <= balance <= self.amount_max
|
||||
|
||||
@api.multi
|
||||
def _is_valid_multicurrency(self, statement_line, move_lines, balance):
|
||||
# FIXME: surely wrong
|
||||
if statement_line.currency_id == statement_line.company_id.currency_id:
|
||||
# not multicurrency
|
||||
return False
|
||||
amount_currency = statement_line.amount_currency
|
||||
for move_line in move_lines:
|
||||
if move_line.currency_id != statement_line.currency_id:
|
||||
# use case not supported, no rule found
|
||||
return self.browse()
|
||||
amount_currency -= move_line.amount_currency
|
||||
|
||||
# amount in currency is the same, so the balance is
|
||||
# a difference due to currency rates
|
||||
if statement_line.currency_id.is_zero(amount_currency):
|
||||
return self._is_valid_balance(statement_line, move_lines, balance)
|
||||
return False
|
||||
|
||||
@api.multi
|
||||
def is_valid(self, statement_line, move_lines, balance):
|
||||
""" Returns True if a rule applies to a group of statement_line +
|
||||
move lines.
|
||||
|
||||
This is the public method where the rule is evaluated whatever
|
||||
its type is. When a rule returns True, it means that it is a
|
||||
candidate for the current reconciliation. The rule with the lowest
|
||||
number in the ``sequence`` field is chosen.
|
||||
|
||||
:param statement_line: the line to reconcile
|
||||
:param move_lines: the selected move lines for reconciliation
|
||||
:param balance: the balance between the statement_line and the
|
||||
move_lines. It could be computed here but it is
|
||||
computed before to avoid to compute it for each
|
||||
rule when called on multiple rules.
|
||||
"""
|
||||
self.ensure_one()
|
||||
if self.rule_type == 'balance':
|
||||
return self._is_valid_balance(statement_line, move_lines, balance)
|
||||
elif self.rule_type == 'currency':
|
||||
return self._is_valid_multicurrency(statement_line,
|
||||
move_lines,
|
||||
balance)
|
||||
|
||||
@api.model
|
||||
def find_first_rule(self, statement_line, move_lines):
|
||||
""" Find the rules that apply to a statement line and
|
||||
a selection of move lines.
|
||||
|
||||
:param statement_line: the line to reconcile
|
||||
:param move_lines: the selected move lines for reconciliation
|
||||
"""
|
||||
balance = statement_line.amount
|
||||
for move_line in move_lines:
|
||||
balance += move_line.credit - move_line.debit
|
||||
|
||||
rules = self.search([])
|
||||
# return the first applicable rule
|
||||
for rule in rules:
|
||||
if rule.is_valid(statement_line, move_lines, balance):
|
||||
return rule
|
||||
return self.browse()
|
||||
|
||||
@api.model
|
||||
@api.returns('account.statement.operation.template')
|
||||
def operations_for_reconciliation(self, statement_line_id, move_line_ids):
|
||||
""" Find the rule for the current reconciliation and returns the
|
||||
``account.statement.operation.template`` of the found rule.
|
||||
|
||||
Called from the javascript reconciliation view.
|
||||
|
||||
"""
|
||||
line_obj = self.env['account.bank.statement.line']
|
||||
move_line_obj = self.env['account.move.line']
|
||||
statement_line = line_obj.browse(statement_line_id)
|
||||
move_lines = move_line_obj.browse(move_line_ids)
|
||||
rules = self.find_first_rule(statement_line, move_lines)
|
||||
return rules.operations
|
||||
@@ -0,0 +1,37 @@
|
||||
openerp.account_statement_operation_rule = function (instance) {
|
||||
|
||||
var _t = instance.web._t,
|
||||
_lt = instance.web._lt;
|
||||
var QWeb = instance.web.qweb;
|
||||
|
||||
instance.web.account_statement_operation_rule = instance.web.account_statement_operation_rule || {};
|
||||
|
||||
instance.web.account.bankStatementReconciliationLine.include({
|
||||
operation_rules: function() {
|
||||
var self = this;
|
||||
var model_operation_rule = new instance.web.Model("account.statement.operation.rule");
|
||||
model_operation_rule.call("operations_for_reconciliation",
|
||||
[self.st_line.id,
|
||||
_.pluck(self.get("mv_lines_selected"), 'id')])
|
||||
.then(function (operations) {
|
||||
_.each(operations, function(operation_id) {
|
||||
preset_btn = self.$("button.preset[data-presetid='" + operation_id + "']");
|
||||
preset_btn.click();
|
||||
self.addLineBeingEdited();
|
||||
});
|
||||
});
|
||||
},
|
||||
render: function() {
|
||||
deferred = this._super();
|
||||
if (deferred) {
|
||||
deferred.done(this.operation_rules());
|
||||
}
|
||||
return deferred;
|
||||
},
|
||||
restart: function() {
|
||||
deferred = this._super();
|
||||
deferred.done(this.operation_rules());
|
||||
return deferred;
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<template id="assets_backend" name="account assets" inherit_id="account.assets_backend">
|
||||
<xpath expr="." position="inside">
|
||||
<script type="text/javascript" src="/account_statement_operation_rule/static/src/js/account_widgets.js"></script>
|
||||
</xpath>
|
||||
</template>
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data noupdate="0">
|
||||
|
||||
<record id="view_account_statement_operation_rule_form" model="ir.ui.view">
|
||||
<field name="name">account.statement.operation.rule.form</field>
|
||||
<field name="model">account.statement.operation.rule</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Statement Operation Rule">
|
||||
<sheet>
|
||||
<div class="oe_title">
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1>
|
||||
<field name="name"/>
|
||||
</h1>
|
||||
</div>
|
||||
<group name="options">
|
||||
<field name="rule_type"/>
|
||||
</group>
|
||||
<group name="amount" string="Balance" attrs="{'invisible': [('rule_type', 'not in', ('balance', 'currency'))]}">
|
||||
<label for="amount" string="When the balance is between"/>
|
||||
<div>
|
||||
<field name="amount_min" class="oe_inline" /> And
|
||||
<field name="amount_max" class="oe_inline" />
|
||||
</div>
|
||||
</group>
|
||||
<group name="operations">
|
||||
<label string="Then the following operations will be applied:" colspan="2"/>
|
||||
<field name="operations" nolabel="1"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record id="view_account_statement_operation_rule_tree" model="ir.ui.view">
|
||||
<field name="name">account.statement.operation.rule.tree</field>
|
||||
<field name="model">account.statement.operation.rule</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Statement Operation Rules">
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="name"/>
|
||||
<field name="rule_type"/>
|
||||
<field name="amount_min"/>
|
||||
<field name="amount_max"/>
|
||||
<field name="operations"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<record id="view_account_statement_operation_rule_search" model="ir.ui.view">
|
||||
<field name="name">account.statement.operation.rule.search</field>
|
||||
<field name="model">account.statement.operation.rule</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Statement Operation Rules">
|
||||
<field name="name"/>
|
||||
<field name="rule_type"/>
|
||||
<field name="amount_min"/>
|
||||
<field name="amount_max"/>
|
||||
<field name="operations"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
<record id="action_account_statement_operation_rule" model="ir.actions.act_window">
|
||||
<field name="name">Statement Operation Rules</field>
|
||||
<field name="res_model">account.statement.operation.rule</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="search_view_id" ref="view_account_statement_operation_rule_search"/>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to create a statement operation rule.
|
||||
</p><p>
|
||||
Those can be used to automatically create a move line when reconciling
|
||||
your bank statements.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
<menuitem action="action_account_statement_operation_rule"
|
||||
id="menu_action_account_statement_operation_rule"
|
||||
parent="account.menu_configuration_misc"
|
||||
name="Statement Operation Rules"
|
||||
sequence="22"/>
|
||||
</data>
|
||||
</openerp>
|
||||
Reference in New Issue
Block a user