diff --git a/account_credit_control_dunning_fees/__openerp__.py b/account_credit_control_dunning_fees/__openerp__.py new file mode 100644 index 000000000..8f0f153fc --- /dev/null +++ b/account_credit_control_dunning_fees/__openerp__.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Author: Nicolas Bessi +# 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': 'Credit control dunning fees', + 'version': '0.1.0', + 'author': 'Camptocamp', + 'maintainer': 'Camptocamp', + 'category': 'Accounting', + 'complexity': 'normal', + 'depends': ['account_credit_control'], + 'description': """ +Dunning Fees for Credit Control +=============================== + +This extention of credit control adds the notion of dunning fees +on credit control lines. + +Configuration +------------- +For release 0.1 only fixed fees are supported. + +You can specifiy a fixed fees amount, a product and a currency +on the credit control level form. + +The amount will be used as fees values the currency will determine +the currency of the fee. If the credit control line has not the +same currency as the fees currency, fees will be converted to +the credit control line currency. + +The product is used to compute taxes in reconciliation process. + +Run +--- +Fees are automatically computed on credit run and saved +on the generated credit lines. + +Fees can be manually edited as long credit line is draft + +Credit control Summary report includes a new fees column. +------- +Support of fees price list + +""", + 'website': 'http://www.camptocamp.com', + 'data': ['view/policy_view.xml', + 'view/line_view.xml', + 'report/report.xml', + 'security/ir.model.access.csv'], + 'demo': [], + 'test': [], + 'installable': True, + 'auto_install': False, + 'license': 'AGPL-3', + 'application': False} diff --git a/account_credit_control_dunning_fees/model/dunning.py b/account_credit_control_dunning_fees/model/dunning.py new file mode 100644 index 000000000..5213b4f20 --- /dev/null +++ b/account_credit_control_dunning_fees/model/dunning.py @@ -0,0 +1,124 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Author: Nicolas Bessi +# 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.osv import orm + + +class FeesComputer(orm.BaseModel): + """Model that compute dunnig fees. + + This class does not need any database storage as + it contains pure logic. + + It inherits form ``orm.BaseModel`` to benefit of orm facility + + Similar to AbstractModel but log access and actions + """ + + _name = 'credit.control.dunning.fees.computer' + _auto = False + _log_access = True + _register = True + _transient = False + + def _get_compute_fun(self, level_fees_type): + """Retrieve function of class that should compute the fees based on type + + :param level_fee_type: type exisiting in model + `credit.control.policy.level` + for field dunning_fees_type + + :returns: a function of class :class:`FeesComputer` + with following signature + self, cr, uid, credit_line (record), context + + """ + if level_fees_type == 'fixed': + return self.compute_fixed_fees + else: + raise NotImplementedError('fees type %s is not supported' % + level_fees_type) + + def _compute_fees(self, cr, uid, credit_line_ids, context=None): + """Compute fees for `credit_line_ids` parameter + + Fees amount is written on credit line in field dunning_fees_amount + + :param credit_line_ids: list of `credit.control.line` ids + + :returns: `credit_line_ids` list of `credit.control.line` ids + + """ + if context is None: + context = {} + if not credit_line_ids: + return credit_line_ids + c_model = self.pool['credit.control.line'] + credit_lines = c_model.browse(cr, uid, credit_line_ids, + context=context) + for credit in credit_lines: + # if there is no dependence between generated credit lines + # this could be threaded + self._compute(cr, uid, credit, context=context), + return credit_line_ids + + def _compute(self, cr, uid, credit_line, context=None): + """Compute fees for a given credit line + + Fees amount is written on credit line in field dunning_fees_amount + + :param credit_line: credit line record + + :returns: `credit_line` record + """ + fees_type = credit_line.policy_level_id.dunning_fees_type + compute = self._get_compute_fun(fees_type) + fees = compute(cr, uid, credit_line, context=context) + if fees: + credit_line.write({'dunning_fees_amount': fees}, + context=context) + return credit_line + + def compute_fixed_fees(self, cr, uid, credit_line, context=None): + """Compute fees amount for fixed fees. + Correspond to the fixed dunning fees type + + if currency of the fees is not the same as the currency + of the credit line, fees amount is converted to + currency of credit line. + + :param credit_line: credit line record + + :return: fees amount float (in credit line currency) + + """ + currency_model = self.pool['res.currency'] + credit_currency = credit_line.currency_id + level = credit_line.policy_level_id + fees_amount = level.dunning_fixed_amount + if not fees_amount: + return 0.0 + fees_currency = level.dunning_currency_id + if fees_currency == credit_currency: + return fees_amount + else: + return currency_model.compute(cr, uid, fees_currency.id, + credit_currency.id, fees_amount, + context=context) diff --git a/account_credit_control_dunning_fees/model/run.py b/account_credit_control_dunning_fees/model/run.py new file mode 100644 index 000000000..cba74e73d --- /dev/null +++ b/account_credit_control_dunning_fees/model/run.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Author: Nicolas Bessi +# 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.osv import orm + + +class credit_control_run(orm.Model): + """Add computation of fees""" + + _inherit = "credit.control.run" + + def _generate_credit_lines(self, cr, uid, run_id, context=None): + """Override method to add fees computation""" + credit_line_ids = super(credit_control_run, + self)._generate_credit_lines( + cr, + uid, + run_id, + context=context) + fees_model = self.pool['credit.control.dunning.fees.computer'] + fees_model._compute_fees(cr, uid, credit_line_ids, context=context) + return credit_line_ids diff --git a/account_credit_control_dunning_fees/security/ir.model.access.csv b/account_credit_control_dunning_fees/security/ir.model.access.csv new file mode 100644 index 000000000..8f42df245 --- /dev/null +++ b/account_credit_control_dunning_fees/security/ir.model.access.csv @@ -0,0 +1,7 @@ +id,group_id/id,name,model_id/id,perm_read,perm_write,perm_create,perm_unlink +dunning_fees_manager,account_credit_control.group_account_credit_control_manager,dunning_fees_manager,model_credit_control_dunning_fees_computer,1,1,1,1 +dunning_fees_user,account_credit_control.group_account_credit_control_user,dunning_fees_user,model_credit_control_dunning_fees_computer,1,1,1,1 +dunning_fees_user,account_credit_control.group_account_credit_control_info,dunning_fees_user,model_credit_control_dunning_fees_computer,1,0,0,0 +dunning_fees_account_user,account.group_account_user,dunning_fees_account_user,model_credit_control_dunning_fees_computer,1,0,0,0 +dunning_fees_account_manager,account.group_account_manager,dunning_fees_account_manager,model_credit_control_dunning_fees_computer,1,1,1,1 +dunning_fees_invoices,account.group_account_invoice,dunning_fees_invoice,model_credit_control_dunning_fees_computer,1,0,0,0 diff --git a/account_credit_control_dunning_fees/tests/test_fees_generation.py b/account_credit_control_dunning_fees/tests/test_fees_generation.py new file mode 100644 index 000000000..b685aea61 --- /dev/null +++ b/account_credit_control_dunning_fees/tests/test_fees_generation.py @@ -0,0 +1,97 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Author: Nicolas Bessi +# 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 mock import MagicMock +import openerp.tests.common as test_common + + +class FixedFeesTester(test_common.TransactionCase): + + def setUp(self): + """Initialize credit control level mock to test fees computations""" + super(FixedFeesTester, self).setUp() + self.currency_model = self.registry('res.currency') + self.euro = self.currency_model.search(self.cr, self.uid, + [('name', '=', 'EUR')]) + self.assertTrue(self.euro) + self.euro = self.registry('res.currency').browse(self.cr, + self.uid, + self.euro[0]) + + self.usd = self.currency_model.search(self.cr, self.uid, + [('name', '=', 'USD')]) + self.assertTrue(self.usd) + self.usd = self.registry('res.currency').browse(self.cr, + self.uid, + self.usd[0]) + + self.euro_level = MagicMock(name='Euro policy level') + self.euro_level.dunning_fixed_amount = 5.0 + self.euro_level.dunning_currency_id = self.euro + self.euro_level.dunning_type = 'fixed' + + self.usd_level = MagicMock(name='USD policy level') + self.usd_level.dunning_fixed_amount = 5.0 + self.usd_level.dunning_currency_id = self.usd + self.usd_level.dunning_type = 'fixed' + self.dunning_model = self.registry( + 'credit.control.dunning.fees.computer' + ) + + def test_type_getter(self): + """Test that correct compute function is returned for "fixed" type""" + c_fun = self.dunning_model._get_compute_fun('fixed') + self.assertEqual(c_fun, self.dunning_model.compute_fixed_fees) + + def test_unknow_type(self): + """Test that non implemented error is raised if invalide fees type""" + with self.assertRaises(NotImplementedError): + self.dunning_model._get_compute_fun('bang') + + def test_computation_same_currency(self): + """Test that fees are correctly computed with same currency""" + credit_line = MagicMock(name='Euro credit line') + credit_line.policy_level_id = self.euro_level + credit_line.currency_id = self.euro + fees = self.dunning_model.compute_fixed_fees(self.cr, self.uid, + credit_line, + {}) + self.assertEqual(fees, self.euro_level.dunning_fixed_amount) + + def test_computation_different_currency(self): + """Test that fees are correctly computed with different currency""" + credit_line = MagicMock(name='USD credit line') + credit_line.policy_level_id = self.euro_level + credit_line.currency_id = self.usd + fees = self.dunning_model.compute_fixed_fees(self.cr, self.uid, + credit_line, + {}) + self.assertNotEqual(fees, self.euro_level.dunning_fixed_amount) + + def test_no_fees(self): + """Test that fees are not generated if no amount defined on level""" + credit_line = MagicMock(name='USD credit line') + credit_line.policy_level_id = self.euro_level + self.euro_level.dunning_fixed_amount = 0.0 + credit_line.currency_id = self.usd + fees = self.dunning_model.compute_fixed_fees(self.cr, self.uid, + credit_line, + {}) + self.assertEqual(fees, 0.0)