diff --git a/account_statement_operation_rule/README.rst b/account_statement_operation_rule/README.rst new file mode 100644 index 00000000..9f6b82d2 --- /dev/null +++ b/account_statement_operation_rule/README.rst @@ -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 + +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. diff --git a/account_statement_operation_rule/__init__.py b/account_statement_operation_rule/__init__.py new file mode 100644 index 00000000..bad9dcdd --- /dev/null +++ b/account_statement_operation_rule/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import model diff --git a/account_statement_operation_rule/__openerp__.py b/account_statement_operation_rule/__openerp__.py new file mode 100644 index 00000000..1302d8da --- /dev/null +++ b/account_statement_operation_rule/__openerp__.py @@ -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 . +# +############################################################################## + +{'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, + } diff --git a/account_statement_operation_rule/i18n/account_statement_operation_rule.pot b/account_statement_operation_rule/i18n/account_statement_operation_rule.pot new file mode 100644 index 00000000..430e735e --- /dev/null +++ b/account_statement_operation_rule/i18n/account_statement_operation_rule.pot @@ -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 "

\n" +" Click to create a statement operation rule.\n" +"

\n" +" Those can be used to automatically create a move line when reconciling\n" +" your bank statements.\n" +"

\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 "" + diff --git a/account_statement_operation_rule/i18n/fr.po b/account_statement_operation_rule/i18n/fr.po new file mode 100644 index 00000000..4e22afe3 --- /dev/null +++ b/account_statement_operation_rule/i18n/fr.po @@ -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 "

\n" +" Click to create a statement operation rule.\n" +"

\n" +" Those can be used to automatically create a move line when reconciling\n" +" your bank statements.\n" +"

\n" +" " +msgstr "

\n" +" Cliquer pour créer une nouvelle règle d'opération relevé.\n" +"

\n" +" Elles peuvent être utilisées pour automatiser la création de lignes " +" quand vous réconciliez des relevés bancaires." +"

\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" + diff --git a/account_statement_operation_rule/model/__init__.py b/account_statement_operation_rule/model/__init__.py new file mode 100644 index 00000000..7854e715 --- /dev/null +++ b/account_statement_operation_rule/model/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- + +from . import account_statement_operation_rule +from . import account_statement_line diff --git a/account_statement_operation_rule/model/account_statement_line.py b/account_statement_operation_rule/model/account_statement_line.py new file mode 100644 index 00000000..40ead9cd --- /dev/null +++ b/account_statement_operation_rule/model/account_statement_line.py @@ -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 . +# +############################################################################## + + +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 diff --git a/account_statement_operation_rule/model/account_statement_operation_rule.py b/account_statement_operation_rule/model/account_statement_operation_rule.py new file mode 100644 index 00000000..633fb2a3 --- /dev/null +++ b/account_statement_operation_rule/model/account_statement_operation_rule.py @@ -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 . +# +############################################################################## + +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 diff --git a/account_statement_operation_rule/security/ir.model.access.csv b/account_statement_operation_rule/security/ir.model.access.csv new file mode 100644 index 00000000..f6b7bfb6 --- /dev/null +++ b/account_statement_operation_rule/security/ir.model.access.csv @@ -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 diff --git a/account_statement_operation_rule/static/src/js/account_widgets.js b/account_statement_operation_rule/static/src/js/account_widgets.js new file mode 100644 index 00000000..8912a7eb --- /dev/null +++ b/account_statement_operation_rule/static/src/js/account_widgets.js @@ -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; + }, + }); +}; diff --git a/account_statement_operation_rule/tests/__init__.py b/account_statement_operation_rule/tests/__init__.py new file mode 100644 index 00000000..a33e398b --- /dev/null +++ b/account_statement_operation_rule/tests/__init__.py @@ -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 . +# +############################################################################## + +from . import test_rule_rounding +from . import test_rule_currency diff --git a/account_statement_operation_rule/tests/common.py b/account_statement_operation_rule/tests/common.py new file mode 100644 index 00000000..96621947 --- /dev/null +++ b/account_statement_operation_rule/tests/common.py @@ -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 . +# +############################################################################## + + +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 diff --git a/account_statement_operation_rule/tests/test_rule_currency.py b/account_statement_operation_rule/tests/test_rule_currency.py new file mode 100644 index 00000000..ecf9fe6d --- /dev/null +++ b/account_statement_operation_rule/tests/test_rule_currency.py @@ -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 . +# +############################################################################## + +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) diff --git a/account_statement_operation_rule/tests/test_rule_rounding.py b/account_statement_operation_rule/tests/test_rule_rounding.py new file mode 100644 index 00000000..de0639e6 --- /dev/null +++ b/account_statement_operation_rule/tests/test_rule_rounding.py @@ -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 . +# +############################################################################## + +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) diff --git a/account_statement_operation_rule/view/account_statement_operation_rule.xml b/account_statement_operation_rule/view/account_statement_operation_rule.xml new file mode 100644 index 00000000..dacd629c --- /dev/null +++ b/account_statement_operation_rule/view/account_statement_operation_rule.xml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/account_statement_operation_rule/view/account_statement_operation_rule_view.xml b/account_statement_operation_rule/view/account_statement_operation_rule_view.xml new file mode 100644 index 00000000..8025126d --- /dev/null +++ b/account_statement_operation_rule/view/account_statement_operation_rule_view.xml @@ -0,0 +1,89 @@ + + + + + + account.statement.operation.rule.form + account.statement.operation.rule + +
+ +
+
+ + + + + + + + + +
+
+
+
+ + account.statement.operation.rule.tree + account.statement.operation.rule + + + + + + + + + + + + + account.statement.operation.rule.search + account.statement.operation.rule + + + + + + + + + + + + Statement Operation Rules + account.statement.operation.rule + form + tree,form + + +

+ Click to create a statement operation rule. +

+ Those can be used to automatically create a move line when reconciling + your bank statements. +

+
+
+ +
+
diff --git a/account_statement_operation_rule_dunning_fees/README.rst b/account_statement_operation_rule_dunning_fees/README.rst new file mode 100644 index 00000000..29fec194 --- /dev/null +++ b/account_statement_operation_rule_dunning_fees/README.rst @@ -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 + +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. diff --git a/account_statement_operation_rule_dunning_fees/__init__.py b/account_statement_operation_rule_dunning_fees/__init__.py new file mode 100644 index 00000000..bad9dcdd --- /dev/null +++ b/account_statement_operation_rule_dunning_fees/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import model diff --git a/account_statement_operation_rule_dunning_fees/__openerp__.py b/account_statement_operation_rule_dunning_fees/__openerp__.py new file mode 100644 index 00000000..16e70692 --- /dev/null +++ b/account_statement_operation_rule_dunning_fees/__openerp__.py @@ -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 . +# +############################################################################## + +{'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, + } diff --git a/account_statement_operation_rule_dunning_fees/i18n/account_statement_operation_rule_dunning_fees.pot b/account_statement_operation_rule_dunning_fees/i18n/account_statement_operation_rule_dunning_fees.pot new file mode 100644 index 00000000..4f5470cd --- /dev/null +++ b/account_statement_operation_rule_dunning_fees/i18n/account_statement_operation_rule_dunning_fees.pot @@ -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 "" diff --git a/account_statement_operation_rule_dunning_fees/i18n/fr.po b/account_statement_operation_rule_dunning_fees/i18n/fr.po new file mode 100644 index 00000000..9d07b6dd --- /dev/null +++ b/account_statement_operation_rule_dunning_fees/i18n/fr.po @@ -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" diff --git a/account_statement_operation_rule_dunning_fees/model/__init__.py b/account_statement_operation_rule_dunning_fees/model/__init__.py new file mode 100644 index 00000000..f8bd646b --- /dev/null +++ b/account_statement_operation_rule_dunning_fees/model/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import account_statement_operation_rule diff --git a/account_statement_operation_rule_dunning_fees/model/account_statement_operation_rule.py b/account_statement_operation_rule_dunning_fees/model/account_statement_operation_rule.py new file mode 100644 index 00000000..78099f2e --- /dev/null +++ b/account_statement_operation_rule_dunning_fees/model/account_statement_operation_rule.py @@ -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 . +# +############################################################################## + +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) diff --git a/account_statement_operation_rule_dunning_fees/tests/__init__.py b/account_statement_operation_rule_dunning_fees/tests/__init__.py new file mode 100644 index 00000000..62079f42 --- /dev/null +++ b/account_statement_operation_rule_dunning_fees/tests/__init__.py @@ -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 . +# +############################################################################## + +from . import test_rule_dunning_fees +from . import test_rule_sequence diff --git a/account_statement_operation_rule_dunning_fees/tests/test_rule_dunning_fees.py b/account_statement_operation_rule_dunning_fees/tests/test_rule_dunning_fees.py new file mode 100644 index 00000000..6e68563b --- /dev/null +++ b/account_statement_operation_rule_dunning_fees/tests/test_rule_dunning_fees.py @@ -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 . +# +############################################################################## + +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) diff --git a/account_statement_operation_rule_dunning_fees/tests/test_rule_sequence.py b/account_statement_operation_rule_dunning_fees/tests/test_rule_sequence.py new file mode 100644 index 00000000..335ba796 --- /dev/null +++ b/account_statement_operation_rule_dunning_fees/tests/test_rule_sequence.py @@ -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 . +# +############################################################################## + +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 diff --git a/account_statement_operation_rule_dunning_fees/view/account_statement_operation_rule_view.xml b/account_statement_operation_rule_dunning_fees/view/account_statement_operation_rule_view.xml new file mode 100644 index 00000000..19985bd3 --- /dev/null +++ b/account_statement_operation_rule_dunning_fees/view/account_statement_operation_rule_view.xml @@ -0,0 +1,18 @@ + + + + + + account.statement.operation.rule.form + account.statement.operation.rule + + + + + + + + + + diff --git a/oca_dependencies.txt b/oca_dependencies.txt index 34f82ae9..9a273770 100644 --- a/oca_dependencies.txt +++ b/oca_dependencies.txt @@ -1 +1,2 @@ +account-financial-tools bank-payment