mirror of
https://github.com/OCA/account-reconcile.git
synced 2025-01-20 12:27:39 +02:00
Merge pull request #67 from guewen/8.0-account_statement_operation_rule
Add module account_statement_operation_rule
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
|
||||
37
account_statement_operation_rule/__openerp__.py
Normal file
37
account_statement_operation_rule/__openerp__.py
Normal file
@@ -0,0 +1,37 @@
|
||||
# -*- 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',
|
||||
'security/ir.model.access.csv',
|
||||
],
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * account_statement_operation_rule
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 8.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2015-03-16 14:46+0000\n"
|
||||
"PO-Revision-Date: 2015-03-16 14:46+0000\n"
|
||||
"Last-Translator: <>\n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: model:ir.actions.act_window,help:account_statement_operation_rule.action_account_statement_operation_rule
|
||||
msgid "<p class=\"oe_view_nocontent_create\">\n"
|
||||
" Click to create a statement operation rule.\n"
|
||||
" </p><p>\n"
|
||||
" Those can be used to automatically create a move line when reconciling\n"
|
||||
" your bank statements.\n"
|
||||
" </p>\n"
|
||||
" "
|
||||
msgstr ""
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: view:account.statement.operation.rule:account_statement_operation_rule.view_account_statement_operation_rule_form
|
||||
msgid "And"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: view:account.statement.operation.rule:account_statement_operation_rule.view_account_statement_operation_rule_form
|
||||
msgid "And the currency is one of"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: model:ir.model,name:account_statement_operation_rule.model_account_bank_statement_line
|
||||
msgid "Bank Statement Line"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: field:account.statement.operation.rule,create_uid:0
|
||||
msgid "Created by"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: field:account.statement.operation.rule,create_date:0
|
||||
msgid "Created on"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: field:account.statement.operation.rule,currencies:0
|
||||
#: selection:account.statement.operation.rule,rule_type:0
|
||||
msgid "Currencies"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: help:account.statement.operation.rule,currencies:0
|
||||
msgid "For 'Currencies' rules, you can choose for which currencies the rule will be applicable."
|
||||
msgstr ""
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: field:account.statement.operation.rule,id:0
|
||||
msgid "ID"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: help:account.statement.operation.rule,sequence:0
|
||||
msgid "If several rules match, the first one is used."
|
||||
msgstr ""
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: field:account.statement.operation.rule,write_uid:0
|
||||
msgid "Last Updated by"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: field:account.statement.operation.rule,write_date:0
|
||||
msgid "Last Updated on"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: field:account.statement.operation.rule,amount_max:0
|
||||
msgid "Max. Amount"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: field:account.statement.operation.rule,amount_min:0
|
||||
msgid "Min. Amount"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: field:account.statement.operation.rule,name:0
|
||||
msgid "Name"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: field:account.statement.operation.rule,operations:0
|
||||
msgid "Operations"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: view:account.statement.operation.rule:account_statement_operation_rule.view_account_statement_operation_rule_form
|
||||
msgid "Result"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: selection:account.statement.operation.rule,rule_type:0
|
||||
msgid "Roundings"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: view:account.statement.operation.rule:account_statement_operation_rule.view_account_statement_operation_rule_form
|
||||
msgid "Rule"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: field:account.statement.operation.rule,sequence:0
|
||||
msgid "Sequence"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: view:account.statement.operation.rule:account_statement_operation_rule.view_account_statement_operation_rule_form
|
||||
msgid "Statement Operation Rule"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: view:account.statement.operation.rule:account_statement_operation_rule.view_account_statement_operation_rule_search
|
||||
#: view:account.statement.operation.rule:account_statement_operation_rule.view_account_statement_operation_rule_tree
|
||||
#: model:ir.actions.act_window,name:account_statement_operation_rule.action_account_statement_operation_rule
|
||||
#: model:ir.ui.menu,name:account_statement_operation_rule.menu_action_account_statement_operation_rule
|
||||
msgid "Statement Operation Rules"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: view:account.statement.operation.rule:account_statement_operation_rule.view_account_statement_operation_rule_form
|
||||
msgid "Then the following operations will be applied:"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: field:account.statement.operation.rule,rule_type:0
|
||||
msgid "Type"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: view:account.statement.operation.rule:account_statement_operation_rule.view_account_statement_operation_rule_form
|
||||
msgid "When the balance is between"
|
||||
msgstr ""
|
||||
|
||||
158
account_statement_operation_rule/i18n/fr.po
Normal file
158
account_statement_operation_rule/i18n/fr.po
Normal file
@@ -0,0 +1,158 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * account_statement_operation_rule
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 8.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2015-03-16 14:46+0000\n"
|
||||
"PO-Revision-Date: 2015-03-16 14:46+0000\n"
|
||||
"Last-Translator: <>\n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: model:ir.actions.act_window,help:account_statement_operation_rule.action_account_statement_operation_rule
|
||||
msgid "<p class=\"oe_view_nocontent_create\">\n"
|
||||
" Click to create a statement operation rule.\n"
|
||||
" </p><p>\n"
|
||||
" Those can be used to automatically create a move line when reconciling\n"
|
||||
" your bank statements.\n"
|
||||
" </p>\n"
|
||||
" "
|
||||
msgstr "<p class=\"oe_view_nocontent_create\">\n"
|
||||
" Cliquer pour créer une nouvelle règle d'opération relevé.\n"
|
||||
" </p><p>\n"
|
||||
" Elles peuvent être utilisées pour automatiser la création de lignes "
|
||||
" quand vous réconciliez des relevés bancaires."
|
||||
" </p>\n"
|
||||
" "
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: view:account.statement.operation.rule:account_statement_operation_rule.view_account_statement_operation_rule_form
|
||||
msgid "And"
|
||||
msgstr "Et"
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: view:account.statement.operation.rule:account_statement_operation_rule.view_account_statement_operation_rule_form
|
||||
msgid "And the currency is one of"
|
||||
msgstr "Et la devise est une des suivantes"
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: model:ir.model,name:account_statement_operation_rule.model_account_bank_statement_line
|
||||
msgid "Bank Statement Line"
|
||||
msgstr "Ligne de relevé bancaire"
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: field:account.statement.operation.rule,create_uid:0
|
||||
msgid "Created by"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: field:account.statement.operation.rule,create_date:0
|
||||
msgid "Created on"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: field:account.statement.operation.rule,currencies:0
|
||||
#: selection:account.statement.operation.rule,rule_type:0
|
||||
msgid "Currencies"
|
||||
msgstr "Devises"
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: help:account.statement.operation.rule,currencies:0
|
||||
msgid "For 'Currencies' rules, you can choose for which currencies the rule will be applicable."
|
||||
msgstr "Pour les règles 'Devises', vous pouvez sélectionner les devises pour lesquelles la règle s'applique."
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: field:account.statement.operation.rule,id:0
|
||||
msgid "ID"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: help:account.statement.operation.rule,sequence:0
|
||||
msgid "If several rules match, the first one is used."
|
||||
msgstr "Si plusieurs règles correspondent, la première est utilisée."
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: field:account.statement.operation.rule,write_uid:0
|
||||
msgid "Last Updated by"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: field:account.statement.operation.rule,write_date:0
|
||||
msgid "Last Updated on"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: field:account.statement.operation.rule,amount_max:0
|
||||
msgid "Max. Amount"
|
||||
msgstr "Montant max."
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: field:account.statement.operation.rule,amount_min:0
|
||||
msgid "Min. Amount"
|
||||
msgstr "Montant min."
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: field:account.statement.operation.rule,name:0
|
||||
msgid "Name"
|
||||
msgstr "Nom"
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: field:account.statement.operation.rule,operations:0
|
||||
msgid "Operations"
|
||||
msgstr "Opérations"
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: view:account.statement.operation.rule:account_statement_operation_rule.view_account_statement_operation_rule_form
|
||||
msgid "Result"
|
||||
msgstr "Résultat"
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: selection:account.statement.operation.rule,rule_type:0
|
||||
msgid "Roundings"
|
||||
msgstr "Arrondis"
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: view:account.statement.operation.rule:account_statement_operation_rule.view_account_statement_operation_rule_form
|
||||
msgid "Rule"
|
||||
msgstr "Règle"
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: field:account.statement.operation.rule,sequence:0
|
||||
msgid "Sequence"
|
||||
msgstr "Séquence"
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: view:account.statement.operation.rule:account_statement_operation_rule.view_account_statement_operation_rule_form
|
||||
msgid "Statement Operation Rule"
|
||||
msgstr "Règle d'opération de relevé"
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: view:account.statement.operation.rule:account_statement_operation_rule.view_account_statement_operation_rule_search
|
||||
#: view:account.statement.operation.rule:account_statement_operation_rule.view_account_statement_operation_rule_tree
|
||||
#: model:ir.actions.act_window,name:account_statement_operation_rule.action_account_statement_operation_rule
|
||||
#: model:ir.ui.menu,name:account_statement_operation_rule.menu_action_account_statement_operation_rule
|
||||
msgid "Statement Operation Rules"
|
||||
msgstr "Règles d'opération de relevé"
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: view:account.statement.operation.rule:account_statement_operation_rule.view_account_statement_operation_rule_form
|
||||
msgid "Then the following operations will be applied:"
|
||||
msgstr "Alors l'opération suivant sera appliquée :"
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: field:account.statement.operation.rule,rule_type:0
|
||||
msgid "Type"
|
||||
msgstr "Type"
|
||||
|
||||
#. module: account_statement_operation_rule
|
||||
#: view:account.statement.operation.rule:account_statement_operation_rule.view_account_statement_operation_rule_form
|
||||
msgid "When the balance is between"
|
||||
msgstr "Quand la balance est entre"
|
||||
|
||||
4
account_statement_operation_rule/model/__init__.py
Normal file
4
account_statement_operation_rule/model/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import account_statement_operation_rule
|
||||
from . import account_statement_line
|
||||
@@ -0,0 +1,31 @@
|
||||
# -*- 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, api
|
||||
|
||||
|
||||
class AccountBankStatementLine(models.Model):
|
||||
_inherit = 'account.bank.statement.line'
|
||||
|
||||
@api.multi
|
||||
def currency_for_rules(self):
|
||||
return self.currency_id or self.statement_id.currency
|
||||
@@ -0,0 +1,185 @@
|
||||
# -*- 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=[('rounding', 'Roundings'),
|
||||
('currency', 'Currencies')],
|
||||
string='Type',
|
||||
default='rounding',
|
||||
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'),
|
||||
)
|
||||
currencies = fields.Many2many(
|
||||
comodel_name='res.currency',
|
||||
string='Currencies',
|
||||
help="For 'Currencies' rules, you can choose for which currencies "
|
||||
"the rule will be applicable.",
|
||||
)
|
||||
sequence = fields.Integer(
|
||||
default=20,
|
||||
help="If several rules match, the first one is used.",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _between_with_bounds(low, value, high, currency):
|
||||
""" Equivalent to a three way comparison: ``min <= value <= high``
|
||||
|
||||
The comparisons are done with the currency to use the correct
|
||||
precision.
|
||||
"""
|
||||
if currency.compare_amounts(value, low) == -1:
|
||||
return False
|
||||
if currency.compare_amounts(value, high) == 1:
|
||||
return False
|
||||
return True
|
||||
|
||||
@api.multi
|
||||
def _balance_in_range(self, balance, currency):
|
||||
amount_min = self.amount_min
|
||||
amount_max = self.amount_max
|
||||
return self._between_with_bounds(amount_min, balance,
|
||||
amount_max, currency)
|
||||
|
||||
@api.model
|
||||
def _is_multicurrency(self, statement_line):
|
||||
currency = statement_line.currency_for_rules()
|
||||
company_currency = statement_line.company_id.currency_id
|
||||
return currency != company_currency
|
||||
|
||||
@api.multi
|
||||
def _is_valid_balance(self, statement_line, move_lines, balance):
|
||||
if self._is_multicurrency(statement_line):
|
||||
return False
|
||||
currency = statement_line.currency_for_rules()
|
||||
return self._balance_in_range(balance, currency)
|
||||
|
||||
@api.multi
|
||||
def _is_valid_multicurrency(self, statement_line, move_lines, balance):
|
||||
""" Check if the multi-currency rule can be applied
|
||||
|
||||
The rule is applied if and only if:
|
||||
* The currency is not company's one
|
||||
* The currency of the statement line and all the lines is the same
|
||||
* The balance of the amount currencies is 0
|
||||
* The balance is between the bounds configured on the rule
|
||||
"""
|
||||
if not self._is_multicurrency(statement_line):
|
||||
return False
|
||||
currency = statement_line.currency_for_rules()
|
||||
if currency not in self.currencies:
|
||||
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 False
|
||||
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._balance_in_range(balance, currency)
|
||||
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 == 'rounding':
|
||||
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
|
||||
|
||||
currency = statement_line.currency_for_rules()
|
||||
if currency.is_zero(balance):
|
||||
return self.browse()
|
||||
|
||||
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,2 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_account_statement_operation_rule,account.statement.operation.rule,model_account_statement_operation_rule,account.group_account_user,1,1,1,1
|
||||
|
@@ -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;
|
||||
},
|
||||
});
|
||||
};
|
||||
23
account_statement_operation_rule/tests/__init__.py
Normal file
23
account_statement_operation_rule/tests/__init__.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# -*- 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 . import test_rule_rounding
|
||||
from . import test_rule_currency
|
||||
78
account_statement_operation_rule/tests/common.py
Normal file
78
account_statement_operation_rule/tests/common.py
Normal file
@@ -0,0 +1,78 @@
|
||||
# -*- 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/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
|
||||
def prepare_statement(test, difference,
|
||||
statement_line_currency=None,
|
||||
move_line_currency=None,
|
||||
amount_currency_difference=0):
|
||||
""" Prepare a bank statement line and a move line
|
||||
|
||||
The difference is applied on the bank statement line relatively to
|
||||
the move line.
|
||||
"""
|
||||
amount = 100
|
||||
amount_currency = 120
|
||||
statement_obj = test.env['account.bank.statement']
|
||||
statement_line_obj = test.env['account.bank.statement.line']
|
||||
move_obj = test.env['account.move']
|
||||
move_line_obj = test.env['account.move.line']
|
||||
statement = statement_obj.create({
|
||||
'name': '/',
|
||||
'journal_id': test.ref('account.cash_journal')
|
||||
})
|
||||
line_vals = {
|
||||
'name': '001',
|
||||
'amount': amount + difference,
|
||||
'statement_id': statement.id,
|
||||
}
|
||||
if statement_line_currency:
|
||||
line_vals.update({
|
||||
'currency_id': statement_line_currency.id,
|
||||
'amount_currency': amount_currency + amount_currency_difference,
|
||||
})
|
||||
|
||||
statement_line = statement_line_obj.create(line_vals)
|
||||
move = move_obj.create({
|
||||
'journal_id': test.ref('account.sales_journal')
|
||||
})
|
||||
line_vals = {
|
||||
'move_id': move.id,
|
||||
'name': '001',
|
||||
'account_id': test.ref('account.a_recv'),
|
||||
'debit': amount,
|
||||
}
|
||||
if move_line_currency:
|
||||
line_vals.update({
|
||||
'currency_id': move_line_currency.id,
|
||||
'amount_currency': amount_currency,
|
||||
})
|
||||
move_line = move_line_obj.create(line_vals)
|
||||
line_vals = {
|
||||
'move_id': move.id,
|
||||
'name': '001',
|
||||
'account_id': test.ref('account.a_sale'),
|
||||
'credit': amount,
|
||||
}
|
||||
if move_line_currency:
|
||||
line_vals['currency_id'] = move_line_currency.id
|
||||
move_line_obj.create(line_vals)
|
||||
return statement_line, move_line
|
||||
164
account_statement_operation_rule/tests/test_rule_currency.py
Normal file
164
account_statement_operation_rule/tests/test_rule_currency.py
Normal file
@@ -0,0 +1,164 @@
|
||||
# -*- 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.tests import common
|
||||
|
||||
from .common import prepare_statement
|
||||
|
||||
|
||||
class TestRuleCurrency(common.TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestRuleCurrency, self).setUp()
|
||||
self.operation_obj = self.env['account.statement.operation.template']
|
||||
self.rule_obj = self.env['account.statement.operation.rule']
|
||||
self.aed = self.browse_ref('base.AED')
|
||||
self.afn = self.browse_ref('base.AFN')
|
||||
self.all = self.browse_ref('base.ALL')
|
||||
self.amd = self.browse_ref('base.AMD')
|
||||
self.aoa = self.browse_ref('base.AOA')
|
||||
self.operation_currency_1 = self.operation_obj.create({
|
||||
'name': 'Currency AED, AFR, ALL -1.0 to 0.0',
|
||||
'label': 'Currency',
|
||||
'account_id': self.ref('account.rsa'),
|
||||
'amount_type': 'percentage_of_total',
|
||||
'amount': 100.0,
|
||||
})
|
||||
self.rule_currency_1 = self.rule_obj.create({
|
||||
'name': 'Currency AED, AFR, ALL -1.0 to 0.0',
|
||||
'rule_type': 'currency',
|
||||
'operations': [(6, 0, (self.operation_currency_1.id, ))],
|
||||
'amount_min': -1.0,
|
||||
'amount_max': 0,
|
||||
'sequence': 1,
|
||||
'currencies': [(6, 0, [self.aed.id, self.afn.id, self.all.id])],
|
||||
})
|
||||
self.operation_currency_2 = self.operation_obj.create({
|
||||
'name': 'Currency AED, AFR, ALL -2.0 to -1.0',
|
||||
'label': 'Currency',
|
||||
'account_id': self.ref('account.rsa'),
|
||||
'amount_type': 'percentage_of_total',
|
||||
'amount': 100.0,
|
||||
})
|
||||
self.rule_currency_2 = self.rule_obj.create({
|
||||
'name': 'Currency AED, AFR, ALL -2.0 to 1.0',
|
||||
'rule_type': 'currency',
|
||||
'operations': [(6, 0, (self.operation_currency_2.id, ))],
|
||||
'amount_min': -2.0,
|
||||
'amount_max': -1.0,
|
||||
'sequence': 2,
|
||||
'currencies': [(6, 0, [self.aed.id, self.afn.id, self.all.id])],
|
||||
})
|
||||
self.operation_currency_3 = self.operation_obj.create({
|
||||
'name': 'Currency AMD, AOA -2.0 to 0.0',
|
||||
'label': 'Currency',
|
||||
'account_id': self.ref('account.rsa'),
|
||||
'amount_type': 'percentage_of_total',
|
||||
'amount': 100.0,
|
||||
|
||||
})
|
||||
self.rule_currency_3 = self.rule_obj.create({
|
||||
'name': 'Currency AMD, AOA -2.0 to 0.0',
|
||||
'rule_type': 'currency',
|
||||
'operations': [(6, 0, (self.operation_currency_3.id, ))],
|
||||
'amount_min': -2,
|
||||
'amount_max': 0,
|
||||
'sequence': 2,
|
||||
'currencies': [(6, 0, [self.amd.id, self.aoa.id])],
|
||||
})
|
||||
|
||||
def test_no_currency_match(self):
|
||||
"""No rules for the current currency"""
|
||||
sek = self.browse_ref('base.SEK')
|
||||
statement_line, move_line = prepare_statement(
|
||||
self, -0.5,
|
||||
statement_line_currency=sek,
|
||||
move_line_currency=sek)
|
||||
ops = self.rule_obj.operations_for_reconciliation(statement_line.id,
|
||||
move_line.ids)
|
||||
self.assertFalse(ops)
|
||||
|
||||
def test_rounding_lines(self):
|
||||
"""No Currencies rules on lines with company currency"""
|
||||
statement_line, move_line = prepare_statement(self, -0.5)
|
||||
ops = self.rule_obj.operations_for_reconciliation(statement_line.id,
|
||||
move_line.ids)
|
||||
self.assertFalse(ops)
|
||||
|
||||
def test_currency_rule_1(self):
|
||||
"""Rule 1 is found with -0.5 AED"""
|
||||
statement_line, move_line = prepare_statement(
|
||||
self, -0.5,
|
||||
statement_line_currency=self.aed,
|
||||
move_line_currency=self.aed,
|
||||
amount_currency_difference=0)
|
||||
rule = self.rule_obj.find_first_rule(statement_line, [move_line])
|
||||
self.assertEquals(rule, self.rule_currency_1)
|
||||
|
||||
def test_currency_rule_2(self):
|
||||
"""Rule 2 is found with -2 AED"""
|
||||
statement_line, move_line = prepare_statement(
|
||||
self, -2,
|
||||
statement_line_currency=self.aed,
|
||||
move_line_currency=self.aed,
|
||||
amount_currency_difference=0)
|
||||
rule = self.rule_obj.find_first_rule(statement_line, [move_line])
|
||||
self.assertEquals(rule, self.rule_currency_2)
|
||||
|
||||
def test_currency_rule_3(self):
|
||||
"""Rule 3 is found with -2 AOA"""
|
||||
statement_line, move_line = prepare_statement(
|
||||
self, -2,
|
||||
statement_line_currency=self.aoa,
|
||||
move_line_currency=self.aoa,
|
||||
amount_currency_difference=0)
|
||||
rule = self.rule_obj.find_first_rule(statement_line, [move_line])
|
||||
self.assertEquals(rule, self.rule_currency_3)
|
||||
|
||||
def test_currency_rule_not_in_bounds(self):
|
||||
"""No rule is found with -3 AOA"""
|
||||
statement_line, move_line = prepare_statement(
|
||||
self, -3,
|
||||
statement_line_currency=self.aoa,
|
||||
move_line_currency=self.aoa,
|
||||
amount_currency_difference=0)
|
||||
rule = self.rule_obj.find_first_rule(statement_line, [move_line])
|
||||
self.assertFalse(rule)
|
||||
|
||||
def test_no_rule_amount_currency_different(self):
|
||||
"""No rule when amount currency is different"""
|
||||
statement_line, move_line = prepare_statement(
|
||||
self, -0.5,
|
||||
statement_line_currency=self.aed,
|
||||
move_line_currency=self.aed,
|
||||
amount_currency_difference=0.5)
|
||||
rule = self.rule_obj.find_first_rule(statement_line, [move_line])
|
||||
self.assertFalse(rule)
|
||||
|
||||
def test_rule_amount_currency_difference_near_zero(self):
|
||||
"""Apply the rule when the difference is near 0"""
|
||||
statement_line, move_line = prepare_statement(
|
||||
self, -0.5,
|
||||
statement_line_currency=self.aed,
|
||||
move_line_currency=self.aed,
|
||||
amount_currency_difference=-0.001)
|
||||
rule = self.rule_obj.find_first_rule(statement_line, [move_line])
|
||||
self.assertEquals(rule, self.rule_currency_1)
|
||||
166
account_statement_operation_rule/tests/test_rule_rounding.py
Normal file
166
account_statement_operation_rule/tests/test_rule_rounding.py
Normal file
@@ -0,0 +1,166 @@
|
||||
# -*- 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.tests import common
|
||||
|
||||
from .common import prepare_statement
|
||||
|
||||
|
||||
class TestRuleRounding(common.TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestRuleRounding, self).setUp()
|
||||
self.operation_obj = self.env['account.statement.operation.template']
|
||||
self.rule_obj = self.env['account.statement.operation.rule']
|
||||
self.operation_round_1 = self.operation_obj.create({
|
||||
'name': 'Rounding -1.0 to 0.0',
|
||||
'label': 'Rounding',
|
||||
'account_id': self.ref('account.rsa'),
|
||||
'amount_type': 'percentage_of_total',
|
||||
'amount': 100.0,
|
||||
|
||||
})
|
||||
self.rule_round_1 = self.rule_obj.create({
|
||||
'name': 'Rounding -1.0 to 0.0',
|
||||
'rule_type': 'rounding',
|
||||
'operations': [(6, 0, (self.operation_round_1.id, ))],
|
||||
'amount_min': -1.0,
|
||||
'amount_max': 0,
|
||||
'sequence': 1,
|
||||
})
|
||||
self.operation_round_2 = self.operation_obj.create({
|
||||
'name': 'Rounding -2.0 to -1.0',
|
||||
'label': 'Rounding',
|
||||
'account_id': self.ref('account.rsa'),
|
||||
'amount_type': 'percentage_of_total',
|
||||
'amount': 100.0,
|
||||
|
||||
})
|
||||
self.rule_round_2 = self.rule_obj.create({
|
||||
'name': 'Rounding -1.0 to 0.0',
|
||||
'rule_type': 'rounding',
|
||||
'operations': [(6, 0, (self.operation_round_2.id, ))],
|
||||
'amount_min': -2.0,
|
||||
'amount_max': -1.0,
|
||||
'sequence': 2,
|
||||
})
|
||||
self.operation_round_3 = self.operation_obj.create({
|
||||
'name': 'Rounding 0.0 to 2.0',
|
||||
'label': 'Rounding',
|
||||
'account_id': self.ref('account.rsa'),
|
||||
'amount_type': 'percentage_of_total',
|
||||
'amount': 100.0,
|
||||
|
||||
})
|
||||
self.rule_round_3 = self.rule_obj.create({
|
||||
'name': 'Rounding 0.0 to 2.0',
|
||||
'rule_type': 'rounding',
|
||||
'operations': [(6, 0, (self.operation_round_3.id, ))],
|
||||
'amount_min': 0,
|
||||
'amount_max': 2,
|
||||
'sequence': 2,
|
||||
})
|
||||
|
||||
def test_rule_round_1(self):
|
||||
"""-0.5 => rule round 1"""
|
||||
statement_line, move_line = prepare_statement(self, -0.5)
|
||||
rule = self.rule_obj.find_first_rule(statement_line, [move_line])
|
||||
self.assertEquals(rule, self.rule_round_1)
|
||||
|
||||
def test_rule_round_1_limit(self):
|
||||
"""-1 => rule round 1"""
|
||||
statement_line, move_line = prepare_statement(self, -1)
|
||||
rule = self.rule_obj.find_first_rule(statement_line, [move_line])
|
||||
self.assertEquals(rule, self.rule_round_1)
|
||||
|
||||
def test_rule_round_1_near_limit(self):
|
||||
"""-1.0001 => rule round 1"""
|
||||
statement_line, move_line = prepare_statement(self, -1.0001)
|
||||
rule = self.rule_obj.find_first_rule(statement_line, [move_line])
|
||||
self.assertEquals(rule, self.rule_round_1)
|
||||
|
||||
def test_rule_round_2(self):
|
||||
"""-1.01 => rule round 2"""
|
||||
statement_line, move_line = prepare_statement(self, -1.01)
|
||||
rule = self.rule_obj.find_first_rule(statement_line, [move_line])
|
||||
self.assertEquals(rule, self.rule_round_2)
|
||||
|
||||
def test_rule_round_2_limit(self):
|
||||
"""-2 => rule round 2"""
|
||||
statement_line, move_line = prepare_statement(self, -2)
|
||||
rule = self.rule_obj.find_first_rule(statement_line, [move_line])
|
||||
self.assertEquals(rule, self.rule_round_2)
|
||||
|
||||
def test_rule_round_3(self):
|
||||
"""+1.5 => rule round 3"""
|
||||
statement_line, move_line = prepare_statement(self, 1.5)
|
||||
rule = self.rule_obj.find_first_rule(statement_line, [move_line])
|
||||
self.assertEquals(rule, self.rule_round_3)
|
||||
|
||||
def test_rule_round_3_limit(self):
|
||||
"""+2 => rule round 3"""
|
||||
statement_line, move_line = prepare_statement(self, 2)
|
||||
rule = self.rule_obj.find_first_rule(statement_line, [move_line])
|
||||
self.assertEquals(rule, self.rule_round_3)
|
||||
|
||||
def test_rule_no_round_below(self):
|
||||
"""-3 => no rule"""
|
||||
statement_line, move_line = prepare_statement(self, -3)
|
||||
rule = self.rule_obj.find_first_rule(statement_line, [move_line])
|
||||
self.assertFalse(rule)
|
||||
|
||||
def test_rule_no_round_above(self):
|
||||
"""+3 => no rule"""
|
||||
statement_line, move_line = prepare_statement(self, 3)
|
||||
rule = self.rule_obj.find_first_rule(statement_line, [move_line])
|
||||
self.assertFalse(rule)
|
||||
|
||||
def test_rule_no_round_zero(self):
|
||||
"""0 => no rule"""
|
||||
statement_line, move_line = prepare_statement(self, 0)
|
||||
rule = self.rule_obj.find_first_rule(statement_line, [move_line])
|
||||
self.assertFalse(rule)
|
||||
|
||||
def test_rule_no_round_near_zero(self):
|
||||
"""0.0001 => no rule"""
|
||||
statement_line, move_line = prepare_statement(self, 0.0001)
|
||||
rule = self.rule_obj.find_first_rule(statement_line, [move_line])
|
||||
self.assertFalse(rule)
|
||||
|
||||
def test_operations(self):
|
||||
"""test operations_for_reconciliation()"""
|
||||
statement_line, move_line = prepare_statement(self, -0.5)
|
||||
ops = self.rule_obj.operations_for_reconciliation(statement_line.id,
|
||||
move_line.ids)
|
||||
self.assertEquals(ops, self.operation_round_1)
|
||||
|
||||
def test_multicurrency_lines(self):
|
||||
"""No rounding rules on multi-currency lines"""
|
||||
currency = self.browse_ref('base.AED')
|
||||
statement_line, move_line = prepare_statement(
|
||||
self,
|
||||
-0.5,
|
||||
statement_line_currency=currency,
|
||||
move_line_currency=currency
|
||||
)
|
||||
ops = self.rule_obj.operations_for_reconciliation(statement_line.id,
|
||||
move_line.ids)
|
||||
self.assertFalse(ops)
|
||||
@@ -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,89 @@
|
||||
<?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="rule" string="Rule">
|
||||
<group name="amount" attrs="{'invisible': [('rule_type', 'not in', ('rounding', '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>
|
||||
<label for="currencies" string="And the currency is one of" attrs="{'invisible': [('rule_type', '!=', 'currency')]}"/>
|
||||
<div attrs="{'invisible': [('rule_type', '!=', 'currency')]}">
|
||||
<field name="currencies" class="oe_inline" widget="many2many_tags"/>
|
||||
</div>
|
||||
</group>
|
||||
</group>
|
||||
<group name="operations" string="Result">
|
||||
<label for="operations" 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>
|
||||
85
account_statement_operation_rule_dunning_fees/README.rst
Normal file
85
account_statement_operation_rule_dunning_fees/README.rst
Normal file
@@ -0,0 +1,85 @@
|
||||
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
|
||||
:alt: License
|
||||
|
||||
Bank Statement Operation Rules with Dunning Fees
|
||||
================================================
|
||||
|
||||
Extends the *Bank Statement Operation Rules* with a new rule, the
|
||||
**Dunning Fees** rule. It allows to automatically create a write-off
|
||||
entry for the amount paid by the customers when they received dunning
|
||||
fees (using the **Account Credit Control** module).
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
As this module aims to automatize the ``Statement Operation Templates``,
|
||||
you first want to ensure that you have an operation configured for the
|
||||
dunning fees.
|
||||
You can find them in ``Invoicing > Configuration > Miscellaneous >
|
||||
Statement Operation Templates``. An example of operation is (the account
|
||||
is where the amount received for the dunning fees will be input):
|
||||
|
||||
=================== ========================== ======= ============
|
||||
Account Amount Type Amount Label
|
||||
=================== ========================== ======= ============
|
||||
Depends of the l10n Percentage of open balance 100.0 % Dunning Fees
|
||||
=================== ========================== ======= ============
|
||||
|
||||
The configuration of the rules themselves happens in ``Invoicing >
|
||||
Configuration > Miscellaneous > Statement Operation Rules``.
|
||||
|
||||
There is no conditions to setup on this rule. It will be applied if the
|
||||
amount in the bank statement line is above the journal entries amount
|
||||
and if the difference is comprised in the amount of the dunning fees for
|
||||
the journal entries.
|
||||
|
||||
Example:
|
||||
|
||||
======================= ======
|
||||
Document Amount
|
||||
======================= ======
|
||||
Journal Entry (invoice) 100.-
|
||||
Dunning Fees no1 5.-
|
||||
Dunning Fees no2 10.-
|
||||
Dunning Fees no3 15.-
|
||||
======================= ======
|
||||
|
||||
The customer received 3 times dunning fees, with a increasing amount.
|
||||
The customer might pay from 100.- to 115.-. The difference between
|
||||
100.- and what the customer paid above goes to the write-off account
|
||||
configured on the operation. If the customer pays 99.- or 116.-, the
|
||||
Dunning Fees rule is not valid and the other rules will be evaluated.
|
||||
|
||||
.. note:: The Dunning Fees rule must be placed before the Roundings
|
||||
rules, otherwise the fees might be confused with roundings.
|
||||
|
||||
.. 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
|
||||
|
||||
Dependencies
|
||||
------------
|
||||
|
||||
This module only works with the ``account_credit_control_dunning_fees``
|
||||
module in the project: https://github.com/OCA/account-financial-tools
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import model
|
||||
37
account_statement_operation_rule_dunning_fees/__openerp__.py
Normal file
37
account_statement_operation_rule_dunning_fees/__openerp__.py
Normal file
@@ -0,0 +1,37 @@
|
||||
# -*- 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 with Dunning Fees',
|
||||
'version': '8.0.1.0.0',
|
||||
'author': 'Camptocamp',
|
||||
'maintainer': 'Camptocamp',
|
||||
'license': 'AGPL-3',
|
||||
'category': 'Accounting & Finance',
|
||||
'depends': ['account_statement_operation_rule',
|
||||
# in https://github.com/OCA/account-financial-tools
|
||||
'account_credit_control_dunning_fees',
|
||||
],
|
||||
'website': 'http://www.camptocamp.com',
|
||||
'data': ['view/account_statement_operation_rule_view.xml',
|
||||
],
|
||||
'installable': True,
|
||||
'auto_install': True,
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * account_statement_operation_rule_dunning_fees
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 8.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2015-03-16 14:56+0000\n"
|
||||
"PO-Revision-Date: 2015-03-16 14:56+0000\n"
|
||||
"Last-Translator: <>\n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. module: account_statement_operation_rule_dunning_fees
|
||||
#: code:addons/account_statement_operation_rule_dunning_fees/model/account_statement_operation_rule.py:97
|
||||
#, python-format
|
||||
msgid "The Dunning Fees rule must be before the Rounding Rules"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_statement_operation_rule_dunning_fees
|
||||
#: view:account.statement.operation.rule:account_statement_operation_rule_dunning_fees.view_account_statement_operation_rule_form
|
||||
msgid "This rule is applied when the invoice line has dunning fees and the payment includes the same amount of fees or a part of this amount."
|
||||
msgstr ""
|
||||
|
||||
#. module: account_statement_operation_rule_dunning_fees
|
||||
#: selection:account.statement.operation.rule,rule_type:0
|
||||
msgid "Dunning Fees"
|
||||
msgstr ""
|
||||
32
account_statement_operation_rule_dunning_fees/i18n/fr.po
Normal file
32
account_statement_operation_rule_dunning_fees/i18n/fr.po
Normal file
@@ -0,0 +1,32 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * account_statement_operation_rule_dunning_fees
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 8.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2015-03-16 14:56+0000\n"
|
||||
"PO-Revision-Date: 2015-03-16 14:56+0000\n"
|
||||
"Last-Translator: <>\n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. module: account_statement_operation_rule_dunning_fees
|
||||
#: code:addons/account_statement_operation_rule_dunning_fees/model/account_statement_operation_rule.py:97
|
||||
#, python-format
|
||||
msgid "The Dunning Fees rule must be before the Rounding Rules"
|
||||
msgstr "Les frais de rappel doivent être placés avant les règles d'arrondi"
|
||||
|
||||
#. module: account_statement_operation_rule_dunning_fees
|
||||
#: view:account.statement.operation.rule:account_statement_operation_rule_dunning_fees.view_account_statement_operation_rule_form
|
||||
msgid "This rule is applied when the invoice line has dunning fees and the payment includes the same amount of fees or a part of this amount."
|
||||
msgstr "Cette règle est appliquée quand la ligne de facture a des frais de rappel que le paiement reçu inclu une partie ou le montant complet des frais de rappel."
|
||||
|
||||
#. module: account_statement_operation_rule_dunning_fees
|
||||
#: selection:account.statement.operation.rule,rule_type:0
|
||||
msgid "Dunning Fees"
|
||||
msgstr "Frais de rappel"
|
||||
@@ -0,0 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import account_statement_operation_rule
|
||||
@@ -0,0 +1,100 @@
|
||||
# -*- 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, exceptions, _
|
||||
|
||||
|
||||
class AccountStatementOperationRule(models.Model):
|
||||
_inherit = 'account.statement.operation.rule'
|
||||
|
||||
rule_type = fields.Selection(
|
||||
selection_add=[('dunning_fees', 'Dunning Fees')],
|
||||
)
|
||||
|
||||
@api.multi
|
||||
def _is_valid_dunning_fees(self, statement_line, move_lines, balance):
|
||||
control_line_obj = self.env['credit.control.line']
|
||||
control_lines = None
|
||||
for line in move_lines:
|
||||
domain = [('move_line_id', '=', line.id),
|
||||
('state', '=', 'sent')]
|
||||
line_control_lines = control_line_obj.search(domain)
|
||||
if line_control_lines and control_lines:
|
||||
# Several lines have credit control lines, use case
|
||||
# not covered, needs to be handled manually, dunning
|
||||
# fees rules not applied
|
||||
control_lines = None
|
||||
break
|
||||
elif line_control_lines:
|
||||
control_lines = line_control_lines
|
||||
if control_lines:
|
||||
# If we have an amount of 100.- with 3 credit control
|
||||
# lines, with the following fees amounts:
|
||||
# * 1st level: 5.-
|
||||
# * 2nd level: 10.-
|
||||
# * 3rd level: 15.-
|
||||
# The customer might pay from 100.- to 115.-, the rest
|
||||
# goes to the writeoff account configured on the operation.
|
||||
max_fees = max(control_lines.mapped('dunning_fees_amount'))
|
||||
# only use the dunning rule if the balance is between -fees and 0
|
||||
currency = statement_line.currency_for_rules()
|
||||
return self._between_with_bounds(0, balance, max_fees, currency)
|
||||
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.
|
||||
"""
|
||||
if self.rule_type == 'dunning_fees':
|
||||
return self._is_valid_dunning_fees(statement_line,
|
||||
move_lines,
|
||||
balance)
|
||||
else:
|
||||
_super = super(AccountStatementOperationRule, self)
|
||||
return _super.is_valid(statement_line, move_lines, balance)
|
||||
|
||||
@api.constrains('sequence')
|
||||
def check_dunning_before_rounding(self):
|
||||
if self.rule_type == 'dunning_fees':
|
||||
operator = '<='
|
||||
other_type = 'rounding'
|
||||
elif self.rule_type == 'rounding':
|
||||
operator = '>'
|
||||
other_type = 'dunning_fees'
|
||||
else:
|
||||
return
|
||||
message = _('The Dunning Fees rule must be before the Rounding Rules')
|
||||
if self.search([('sequence', operator, self.sequence),
|
||||
('rule_type', '=', other_type)], limit=1):
|
||||
raise exceptions.ValidationError(message)
|
||||
@@ -0,0 +1,23 @@
|
||||
# -*- 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 . import test_rule_dunning_fees
|
||||
from . import test_rule_sequence
|
||||
@@ -0,0 +1,171 @@
|
||||
# -*- 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.tests import common
|
||||
from openerp.addons.account_statement_operation_rule.tests.common import (
|
||||
prepare_statement
|
||||
)
|
||||
|
||||
|
||||
def prepare_statement_with_dunning_fees(test, difference, fees):
|
||||
""" Prepare a bank statement line and a move line
|
||||
|
||||
The difference is applied on the bank statement line relatively to
|
||||
the move line.
|
||||
|
||||
The fees is a list of dunning fees (amounts) applied on the move line.
|
||||
"""
|
||||
statement_line, move_line = prepare_statement(test, difference)
|
||||
control_lines = test.env['credit.control.line'].browse()
|
||||
for fee in fees:
|
||||
values = {
|
||||
'date': move_line.date,
|
||||
'date_due': move_line.date,
|
||||
'state': 'sent',
|
||||
'channel': 'letter',
|
||||
'partner_id': test.ref('base.res_partner_1'),
|
||||
'amount_due': move_line.credit,
|
||||
'balance_due': move_line.credit,
|
||||
'policy_level_id': test.ref('account_credit_control.3_time_1'),
|
||||
'company_id': move_line.company_id.id,
|
||||
'move_line_id': move_line.id,
|
||||
'dunning_fees_amount': fee,
|
||||
}
|
||||
control_lines += test.env['credit.control.line'].create(values)
|
||||
return statement_line, move_line, control_lines
|
||||
|
||||
|
||||
class TestDunningRule(common.TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestDunningRule, self).setUp()
|
||||
self.operation_obj = self.env['account.statement.operation.template']
|
||||
self.rule_obj = self.env['account.statement.operation.rule']
|
||||
self.operation_dunning = self.operation_obj.create({
|
||||
'name': 'Dunning Fees',
|
||||
'label': 'Dunning Fees',
|
||||
'account_id': self.ref('account.rsa'),
|
||||
'amount_type': 'percentage_of_total',
|
||||
'amount': 100.0,
|
||||
|
||||
})
|
||||
self.rule_dunning = self.rule_obj.create({
|
||||
'name': 'Dunning Fees',
|
||||
'rule_type': 'dunning_fees',
|
||||
'operations': [(6, 0, (self.operation_dunning.id, ))],
|
||||
'sequence': 1,
|
||||
})
|
||||
self.operation_round_1 = self.operation_obj.create({
|
||||
'name': 'Rounding -1.0 to 0.0',
|
||||
'label': 'Rounding',
|
||||
'account_id': self.ref('account.rsa'),
|
||||
'amount_type': 'percentage_of_total',
|
||||
'amount': 100.0,
|
||||
|
||||
})
|
||||
self.rule_round_1 = self.rule_obj.create({
|
||||
'name': 'Rounding -1.0 to 0.0',
|
||||
'rule_type': 'rounding',
|
||||
'operations': [(6, 0, (self.operation_round_1.id, ))],
|
||||
'amount_min': -1.0,
|
||||
'amount_max': 0,
|
||||
'sequence': 2,
|
||||
})
|
||||
|
||||
def test_paid_dunning_fees(self):
|
||||
"""Customer paid the dunning fees of 10.-"""
|
||||
statement_line, move_line, __ = prepare_statement_with_dunning_fees(
|
||||
self, 10, [10]
|
||||
)
|
||||
rule = self.rule_obj.find_first_rule(statement_line, [move_line])
|
||||
self.assertEquals(rule, self.rule_dunning)
|
||||
|
||||
def test_no_paid_dunning_fees(self):
|
||||
"""Customer paid the dunning fees of 10.-"""
|
||||
statement_line, move_line, __ = prepare_statement_with_dunning_fees(
|
||||
self, 0, [10]
|
||||
)
|
||||
rule = self.rule_obj.find_first_rule(statement_line, [move_line])
|
||||
self.assertFalse(rule)
|
||||
|
||||
def test_paid_part_of_dunning_fees(self):
|
||||
"""Customer paid only 5.- of the dunning fees of 10.-"""
|
||||
statement_line, move_line, __ = prepare_statement_with_dunning_fees(
|
||||
self, 5, [10]
|
||||
)
|
||||
rule = self.rule_obj.find_first_rule(statement_line, [move_line])
|
||||
self.assertEquals(rule, self.rule_dunning)
|
||||
|
||||
def test_paid_too_much_dunning_fees(self):
|
||||
"""Customer paid 15.- of the dunning fees of 10.-"""
|
||||
statement_line, move_line, __ = prepare_statement_with_dunning_fees(
|
||||
self, 15, [10]
|
||||
)
|
||||
rule = self.rule_obj.find_first_rule(statement_line, [move_line])
|
||||
self.assertFalse(rule)
|
||||
|
||||
def test_paid_no_dunning_fees_and_less_amount(self):
|
||||
"""Customer paid 0.- of the dunning fees of 10.- and 1.- less"""
|
||||
statement_line, move_line, __ = prepare_statement_with_dunning_fees(
|
||||
self, -1, [10]
|
||||
)
|
||||
rule = self.rule_obj.find_first_rule(statement_line, [move_line])
|
||||
self.assertEquals(rule, self.rule_round_1)
|
||||
|
||||
def test_paid_dunning_fees_several(self):
|
||||
"""Customer paid 15.- of the dunning fees of 5.-, 10.- and 15.-"""
|
||||
statement_line, move_line, __ = prepare_statement_with_dunning_fees(
|
||||
self, 15, [5, 10, 15]
|
||||
)
|
||||
rule = self.rule_obj.find_first_rule(statement_line, [move_line])
|
||||
self.assertEquals(rule, self.rule_dunning)
|
||||
|
||||
def test_paid_too_much_dunning_fees_several(self):
|
||||
"""Customer paid 16.- of the dunning fees of 5.-, 10.- and 15.-"""
|
||||
statement_line, move_line, __ = prepare_statement_with_dunning_fees(
|
||||
self, 16, [5, 10, 15]
|
||||
)
|
||||
rule = self.rule_obj.find_first_rule(statement_line, [move_line])
|
||||
self.assertFalse(rule)
|
||||
|
||||
def test_ignored_credit_control_line(self):
|
||||
"""Customer paid 15.- of the fees of 5.-, 10.- and draft 15.-"""
|
||||
prepare = prepare_statement_with_dunning_fees
|
||||
statement_line, move_line, control_lines = prepare(
|
||||
self, 15, [5, 10, 15]
|
||||
)
|
||||
for control_line in control_lines:
|
||||
if control_line.dunning_fees_amount == 15:
|
||||
control_line.state = 'draft'
|
||||
rule = self.rule_obj.find_first_rule(statement_line, [move_line])
|
||||
self.assertFalse(rule)
|
||||
|
||||
def test_ignored_credit_control_line_take_other(self):
|
||||
"""Customer paid 10.- of the fees of 5.-, 10.- and draft 15.-"""
|
||||
prepare = prepare_statement_with_dunning_fees
|
||||
statement_line, move_line, control_lines = prepare(
|
||||
self, 10, [5, 10, 15]
|
||||
)
|
||||
for control_line in control_lines:
|
||||
if control_line.dunning_fees_amount == 15:
|
||||
control_line.state = 'draft'
|
||||
rule = self.rule_obj.find_first_rule(statement_line, [move_line])
|
||||
self.assertEquals(rule, self.rule_dunning)
|
||||
@@ -0,0 +1,83 @@
|
||||
# -*- 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 exceptions
|
||||
from openerp.tests import common
|
||||
|
||||
|
||||
class TestRuleSequence(common.TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestRuleSequence, self).setUp()
|
||||
self.operation_obj = self.env['account.statement.operation.template']
|
||||
self.rule_obj = self.env['account.statement.operation.rule']
|
||||
self.rule_dunning = self.rule_obj.create({
|
||||
'name': 'Dunning Fees',
|
||||
'rule_type': 'dunning_fees',
|
||||
'sequence': 5,
|
||||
})
|
||||
self.rule_round_1 = self.rule_obj.create({
|
||||
'name': 'Rounding -1.0 to 0.0',
|
||||
'rule_type': 'rounding',
|
||||
'amount_min': -1.0,
|
||||
'amount_max': 0,
|
||||
'sequence': 10,
|
||||
})
|
||||
self.rule_round_2 = self.rule_obj.create({
|
||||
'name': 'Rounding -2.0 to -1.0',
|
||||
'rule_type': 'rounding',
|
||||
'amount_min': -2.0,
|
||||
'amount_max': -1.0,
|
||||
'sequence': 15,
|
||||
})
|
||||
self.rule_currency = self.rule_obj.create({
|
||||
'name': 'Currency',
|
||||
'rule_type': 'currency',
|
||||
'amount_min': -2.0,
|
||||
'amount_max': -1.0,
|
||||
'sequence': 20,
|
||||
})
|
||||
|
||||
def test_dunning_first(self):
|
||||
""" Dunning rule can be the first """
|
||||
self.rule_dunning.sequence = 1
|
||||
self.rule_round_1.sequence = 2
|
||||
self.rule_round_2.sequence = 3
|
||||
self.rule_currency.sequence = 4
|
||||
|
||||
def test_dunning_after_rounding(self):
|
||||
""" Dunning rule cannot be after a rounding rule """
|
||||
with self.assertRaises(exceptions.ValidationError):
|
||||
self.rule_dunning.sequence = 30
|
||||
|
||||
def test_dunning_equal_rounding(self):
|
||||
""" Dunning rule cannot be equal to a rounding rule """
|
||||
with self.assertRaises(exceptions.ValidationError):
|
||||
self.rule_dunning.sequence = 10
|
||||
|
||||
def test_rounding_before_dunning(self):
|
||||
""" Rounding cannot be before dunning """
|
||||
with self.assertRaises(exceptions.ValidationError):
|
||||
self.rule_round_1.sequence = 1
|
||||
|
||||
def test_currency_before_dunning(self):
|
||||
""" Currency can be before dunning"""
|
||||
self.rule_currency.sequence = 1
|
||||
@@ -0,0 +1,18 @@
|
||||
<?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="inherit_id" ref="account_statement_operation_rule.view_account_statement_operation_rule_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<group name="rule" position="inside">
|
||||
<group name="dunning_fees" attrs="{'invisible': [('rule_type', '!=', 'dunning_fees')]}">
|
||||
<label string="This rule is applied when the invoice line has dunning fees and the payment includes the same amount of fees or a part of this amount."/>
|
||||
</group>
|
||||
</group>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
||||
@@ -1 +1,2 @@
|
||||
account-financial-tools
|
||||
bank-payment
|
||||
|
||||
Reference in New Issue
Block a user