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)