openerp-community-reviewer team mailing list archive
-
openerp-community-reviewer team
-
Mailing list archive
-
Message #06587
[Merge] lp:~camptocamp/account-financial-tools/add-manual-line-and-fees-nbi into lp:account-financial-tools
Nicolas Bessi - Camptocamp has proposed merging lp:~camptocamp/account-financial-tools/add-manual-line-and-fees-nbi into lp:account-financial-tools.
Requested reviews:
Account Core Editors (account-core-editors)
For more details, see:
https://code.launchpad.net/~camptocamp/account-financial-tools/add-manual-line-and-fees-nbi/+merge/218584
Extend credit control to add new functionnalities:
There is now a wizard that allows to manually force policy level of an invoice.
The wizard can be reach from invoice.
Adds first version of dunning fees addons
--
https://code.launchpad.net/~camptocamp/account-financial-tools/add-manual-line-and-fees-nbi/+merge/218584
Your team Account Core Editors is requested to review the proposed merge of lp:~camptocamp/account-financial-tools/add-manual-line-and-fees-nbi into lp:account-financial-tools.
=== modified file 'account_credit_control/__openerp__.py'
--- account_credit_control/__openerp__.py 2014-03-24 08:37:48 +0000
+++ account_credit_control/__openerp__.py 2014-05-07 11:50:00 +0000
@@ -19,7 +19,7 @@
#
##############################################################################
{'name': 'Account Credit Control',
- 'version': '0.1.1',
+ 'version': '0.2.0',
'author': 'Camptocamp',
'maintainer': 'Camptocamp',
'category': 'Finance',
@@ -69,6 +69,7 @@
"wizard/credit_control_emailer_view.xml",
"wizard/credit_control_marker_view.xml",
"wizard/credit_control_printer_view.xml",
+ "wizard/credit_control_policy_changer_view.xml",
"security/ir.model.access.csv"],
'demo_xml': ["credit_control_demo.xml"],
'tests': [],
=== modified file 'account_credit_control/account.py'
--- account_credit_control/account.py 2013-09-04 12:37:34 +0000
+++ account_credit_control/account.py 2014-05-07 11:50:00 +0000
@@ -27,11 +27,11 @@
_inherit = "account.account"
_columns = {
- 'credit_control_line_ids':
- fields.one2many('credit.control.line',
- 'account_id',
- string='Credit Lines',
- readonly=True),
+ 'credit_control_line_ids': fields.one2many(
+ 'credit.control.line',
+ 'account_id',
+ string='Credit Lines',
+ readonly=True),
}
def copy_data(self, cr, uid, id, default=None, context=None):
@@ -42,34 +42,3 @@
default['credit_control_line_ids'] = False
return super(AccountAccount, self).copy_data(
cr, uid, id, default=default, context=context)
-
-
-class AccountInvoice(orm.Model):
- """Add a link to a credit control policy on account.account"""
-
- _inherit = "account.invoice"
- _columns = {
- 'credit_policy_id':
- fields.many2one('credit.control.policy',
- 'Credit Control Policy',
- help=("The Credit Control Policy used for this "
- "invoice. If nothing is defined, it will "
- "use the account setting or the partner "
- "setting.")
- ),
- 'credit_control_line_ids':
- fields.one2many('credit.control.line',
- 'invoice_id',
- string='Credit Lines',
- readonly=True),
- }
-
- def copy_data(self, cr, uid, id, default=None, context=None):
- if default is None:
- default = {}
- else:
- default = default.copy()
- default = default.copy()
- default['credit_control_line_ids'] = False
- return super(AccountInvoice, self).copy_data(
- cr, uid, id, default=default, context=context)
=== modified file 'account_credit_control/invoice.py'
--- account_credit_control/invoice.py 2013-09-18 07:59:03 +0000
+++ account_credit_control/invoice.py 2014-05-07 11:50:00 +0000
@@ -18,7 +18,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
-from openerp.osv import orm
+from openerp.osv import orm, fields
from openerp.tools.translate import _
@@ -26,7 +26,37 @@
"""Check on cancelling of an invoice"""
_inherit = 'account.invoice'
+ _columns = {
+ 'credit_policy_id':
+ fields.many2one('credit.control.policy',
+ 'Credit Control Policy',
+ help=("The Credit Control Policy used for this "
+ "invoice. If nothing is defined, it will "
+ "use the account setting or the partner "
+ "setting."),
+ readonly=True,
+ ),
+ 'credit_control_line_ids':
+ fields.one2many('credit.control.line',
+ 'invoice_id',
+ string='Credit Lines',
+ readonly=True),
+ }
+
+ def copy_data(self, cr, uid, id, default=None, context=None):
+ """Ensure that credit lines and policcy are not copied"""
+ if default is None:
+ default = {}
+ else:
+ default = default.copy()
+ default = default.copy()
+ default['credit_control_line_ids'] = False
+ default['credit_policy_id'] = False
+ return super(AccountInvoice, self).copy_data(
+ cr, uid, id, default=default, context=context)
+
def action_cancel(self, cr, uid, ids, context=None):
+ """Prevent to cancel invoice related to credit line"""
# We will search if this invoice is linked with credit
cc_line_obj = self.pool.get('credit.control.line')
for invoice_id in ids:
=== modified file 'account_credit_control/line.py'
--- account_credit_control/line.py 2013-09-04 12:37:34 +0000
+++ account_credit_control/line.py 2014-05-07 11:50:00 +0000
@@ -38,9 +38,11 @@
_name = "credit.control.line"
_description = "A credit control line"
_rec_name = "id"
-
+ _order = "date DESC"
_columns = {
- 'date': fields.date('Controlling date', required=True),
+ 'date': fields.date('Controlling date',
+ required=True,
+ select=True),
# maturity date of related move line we do not use a related field in order to
# allow manual changes
'date_due': fields.date('Due date',
@@ -116,6 +118,7 @@
string='Level',
store=True,
readonly=True),
+ 'manually_overriden': fields.boolean('Manually overriden')
}
@@ -141,8 +144,27 @@
return data
def create_or_update_from_mv_lines(self, cr, uid, ids, lines,
- level_id, controlling_date, context=None):
- """Create or update line based on levels"""
+ level_id, controlling_date,
+ check_tolerance=True, context=None):
+ """Create or update line based on levels
+
+ if check_tolerance is true credit line will not be
+ created if open amount is too small.
+ eg. we do not want to send a letter for 10 cents
+ of open amount.
+
+ :param lines: move.line id list
+ :param level_id: credit.control.policy.level id
+ :param controlling_date: date string of the credit controlling date.
+ Generally it should be the same
+ as create date
+ :param check_tolerance: boolean if True credit line
+ will not be generated if open amount
+ is smaller than company defined
+ tolerance
+
+ :returns: list of created credit line ids
+ """
currency_obj = self.pool.get('res.currency')
level_obj = self.pool.get('credit.control.policy.level')
ml_obj = self.pool.get('account.move.line')
@@ -164,26 +186,31 @@
for line in ml_obj.browse(cr, uid, lines, context):
open_amount = line.amount_residual_currency
-
- if open_amount > tolerance.get(line.currency_id.id, tolerance_base):
- vals = self._prepare_from_move_line(
- cr, uid, line, level, controlling_date, open_amount, context=context)
- line_id = self.create(cr, uid, vals, context=context)
- line_ids.append(line_id)
-
- # when we have lines generated earlier in draft,
- # on the same level, it means that we have left
- # them, so they are to be considered as ignored
- previous_draft_ids = self.search(
- cr, uid,
- [('move_line_id', '=', line.id),
- ('level', '=', level.id),
- ('state', '=', 'draft'),
- ('id', '!=', line_id)],
- context=context)
- if previous_draft_ids:
- self.write(cr, uid, previous_draft_ids,
- {'state': 'ignored'}, context=context)
+ cur_tolerance = tolerance.get(line.currency_id.id, tolerance_base)
+ if check_tolerance and open_amount < cur_tolerance:
+ continue
+ vals = self._prepare_from_move_line(cr, uid,
+ line,
+ level,
+ controlling_date,
+ open_amount,
+ context=context)
+ line_id = self.create(cr, uid, vals, context=context)
+ line_ids.append(line_id)
+
+ # when we have lines generated earlier in draft,
+ # on the same level, it means that we have left
+ # them, so they are to be considered as ignored
+ previous_draft_ids = self.search(
+ cr, uid,
+ [('move_line_id', '=', line.id),
+ ('policy_level_id', '=', level.id),
+ ('state', '=', 'draft'),
+ ('id', '!=', line_id)],
+ context=context)
+ if previous_draft_ids:
+ self.write(cr, uid, previous_draft_ids,
+ {'state': 'ignored'}, context=context)
return line_ids
=== modified file 'account_credit_control/line_view.xml'
--- account_credit_control/line_view.xml 2013-03-18 09:21:33 +0000
+++ account_credit_control/line_view.xml 2014-05-07 11:50:00 +0000
@@ -10,6 +10,7 @@
<field name="date_due"/>
<field name="date_sent"/>
<field name="level"/>
+ <field name="manually_overriden"/>
<field name="state"/>
<field name="channel"/>
<field name="invoice_id"/>
@@ -32,7 +33,7 @@
<field name="type">search</field>
<field name="arch" type="xml">
<search string="Control Credit Lines">
- <group>
+ <group string="Filters">
<filter name="filter_draft" icon="terp-mail-message-new"
string="Draft" domain="[('state', '=', 'draft')]"
help="Draft lines have to be triaged."/>
@@ -48,6 +49,9 @@
<filter name="filter_error" icon="terp-gtk-stop" string="Error"
domain="[('state', 'in', ('error', 'email_error'))]"
help="An error has occured during the sending of the email."/>
+ <filter name="filter_manual" icon="terp-gtk-stop" string="Manual change"
+ domain="[('manually_overriden', '=', True)]"
+ help="The line was deprecated by a manual change of policy on invoice."/>
<separator orientation="vertical"/>
<field name="date"/>
@@ -89,6 +93,8 @@
<separator orientation="vertical"/>
<filter domain='[]' context="{'group_by': 'channel'}"
icon="terp-document-new" string="Channel"/>
+ <filter domain='[]' context="{'group_by': 'manually_overriden'}"
+ icon="terp-document-new" string="Manual change"/>
</group>
</search>
</field>
@@ -103,6 +109,7 @@
<field name="date"/>
<field name="date_due"/>
<field name="level"/>
+ <field name="manually_overriden"/>
<field name="state"/>
<field name="channel"/>
<field name="invoice_id"/>
=== modified file 'account_credit_control/partner.py'
--- account_credit_control/partner.py 2013-09-04 12:29:15 +0000
+++ account_credit_control/partner.py 2014-05-07 11:50:00 +0000
@@ -19,6 +19,7 @@
#
##############################################################################
from openerp.osv import orm, fields
+from openerp.tools.translate import _
class ResPartner(orm.Model):
@@ -28,21 +29,47 @@
_inherit = "res.partner"
_columns = {
- 'credit_policy_id':
- fields.many2one('credit.control.policy',
- 'Credit Control Policy',
- help=("The Credit Control Policy used for this "
- "partner. This setting can be forced on the "
- "invoice. If nothing is defined, it will use "
- "the company setting.")),
- 'credit_control_line_ids':
- fields.one2many('credit.control.line',
- 'invoice_id',
- string='Credit Control Lines',
- readonly=True)
+ 'credit_policy_id': fields.many2one(
+ 'credit.control.policy',
+ 'Credit Control Policy',
+ domain="[('account_ids', 'in', property_account_receivable)]",
+ help=("The Credit Control Policy used for this "
+ "partner. This setting can be forced on the "
+ "invoice. If nothing is defined, it will use "
+ "the company setting.")
+ ),
+ 'credit_control_line_ids': fields.one2many(
+ 'credit.control.line',
+ 'invoice_id',
+ string='Credit Control Lines',
+ readonly=True
+ )
}
+ def _check_credit_policy(self, cr, uid, part_ids, context=None):
+ """Ensure that policy on partner are limited to the account policy"""
+ if isinstance(part_ids, (int, long)):
+ part_ids = [part_ids]
+ policy_obj = self.pool['credit.control.policy']
+ for partner in self.browse(cr, uid, part_ids, context):
+ if not partner.property_account_receivable or \
+ not partner.credit_policy_id:
+ return True
+ account = partner.property_account_receivable
+ policy_obj.check_policy_against_account(
+ cr, uid,
+ account.id,
+ partner.credit_policy_id.id,
+ context=context
+ )
+ return True
+
+ _constraints = [(_check_credit_policy,
+ 'The policy must be related to the receivable account',
+ ['credit_policy_id'])]
+
def copy_data(self, cr, uid, id, default=None, context=None):
+ """Remove credit lines when copying partner"""
if default is None:
default = {}
else:
=== modified file 'account_credit_control/policy.py'
--- account_credit_control/policy.py 2014-03-04 07:52:47 +0000
+++ account_credit_control/policy.py 2014-05-07 11:50:00 +0000
@@ -18,11 +18,11 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
-from openerp.osv.orm import Model, fields
+from openerp.osv import orm, fields
from openerp.tools.translate import _
-class CreditControlPolicy(Model):
+class CreditControlPolicy(orm.Model):
"""Define a policy of reminder"""
_name = "credit.control.policy"
@@ -42,7 +42,7 @@
'account_ids': fields.many2many('account.account',
string='Accounts',
required=True,
- domain="[('reconcile', '=', True)]",
+ domain="[('type', '=', 'receivable')]",
help="This policy will be active only"
" for the selected accounts"),
'active': fields.boolean('Active'),
@@ -103,7 +103,10 @@
my_obj = self.pool.get(model)
move_l_obj = self.pool.get('account.move.line')
- default_domain = self._move_lines_domain(cr, uid, policy, controlling_date, context=context)
+ default_domain = self._move_lines_domain(cr, uid,
+ policy,
+ controlling_date,
+ context=context)
to_add_ids = set()
to_remove_ids = set()
@@ -198,15 +201,34 @@
if isinstance(policy_id, list):
policy_id = policy_id[0]
cr.execute("SELECT move_line_id FROM credit_control_line"
- " WHERE policy_id != %s and move_line_id in %s",
+ " WHERE policy_id != %s and move_line_id in %s"
+ " AND manually_overriden IS false",
(policy_id, tuple(lines)))
res = cr.fetchall()
if res:
different_lines.update([x[0] for x in res])
return different_lines
-
-class CreditControlPolicyLevel(Model):
+ def check_policy_against_account(self, cr, uid, account_id, policy_id,
+ context=None):
+ """Ensure that policy correspond to account relation"""
+ policy = self.browse(cr, uid, policy_id, context=context)
+ account = self.pool['account.account'].browse(cr, uid, account_id,
+ context=context)
+ policies_id = self.search(cr, uid, [],
+ context=context)
+ policies = self.browse(cr, uid, policies_id, context=context)
+ allowed = [x for x in policies if
+ account in x.account_ids or x.do_nothing]
+ if policy not in allowed:
+ allowed_names = u"\n".join(x.name for x in allowed)
+ raise orm.except_orm(
+ _('You can only use a policy set on account %s') % account.name,
+ _("Please choose one of the following policies:\n %s") % allowed_names)
+ return True
+
+
+class CreditControlPolicyLevel(orm.Model):
"""Define a policy level. A level allows to determine if
a move line is due and the level of overdue of the line"""
@@ -319,8 +341,11 @@
" FROM credit_control_line\n"
" WHERE move_line_id = mv_line.id\n"
# lines from a previous level with a draft or ignored state
+ # or manually overriden
# have to be generated again for the previous level
- " AND state not in ('draft', 'ignored'))")
+ " AND NOT manually_overriden\n"
+ " AND state NOT IN ('draft', 'ignored'))"
+ " AND (mv_line.debit IS NOT NULL AND mv_line.debit != 0.0)\n")
sql += " AND"
sql += self._get_sql_date_boundary_for_computation_mode(cr, uid, level,
controlling_date, context)
@@ -346,11 +371,15 @@
" WHERE cr_line.id = (SELECT credit_control_line.id FROM credit_control_line\n"
" WHERE credit_control_line.move_line_id = mv_line.id\n"
" AND state != 'ignored'"
+ " AND NOT manually_overriden"
" ORDER BY credit_control_line.level desc limit 1)\n"
" AND cr_line.level = %(previous_level)s\n"
+ " AND (mv_line.debit IS NOT NULL AND mv_line.debit != 0.0)\n"
# lines from a previous level with a draft or ignored state
+ # or manually overriden
# have to be generated again for the previous level
- " AND cr_line.state not in ('draft', 'ignored')\n"
+ " AND NOT manually_overriden\n"
+ " AND cr_line.state NOT IN ('draft', 'ignored')\n"
" AND mv_line.id in %(line_ids)s\n")
sql += " AND "
sql += self._get_sql_date_boundary_for_computation_mode(cr, uid, level,
=== modified file 'account_credit_control/run.py'
--- account_credit_control/run.py 2013-09-04 12:29:15 +0000
+++ account_credit_control/run.py 2014-05-07 11:50:00 +0000
@@ -81,14 +81,24 @@
def _check_run_date(self, cr, uid, ids, controlling_date, context=None):
"""Ensure that there is no credit line in the future using controlling_date"""
- line_obj = self.pool.get('credit.control.line')
+ run_obj = self.pool['credit.control.run']
+ runs = run_obj.search(cr, uid, [('date', '>', controlling_date)],
+ order='date DESC', limit=1, context=context)
+ if runs:
+ run = run_obj.browse(cr, uid, runs[0], context=context)
+ raise orm.except_orm(_('Error'),
+ _('A run has already been executed more '
+ 'recently than %s') % (run.date))
+
+ line_obj = self.pool['credit.control.line']
lines = line_obj.search(cr, uid, [('date', '>', controlling_date)],
order='date DESC', limit=1, context=context)
if lines:
line = line_obj.browse(cr, uid, lines[0], context=context)
raise orm.except_orm(_('Error'),
- _('A run has already been executed more '
- 'recently than %s') % (line.date))
+ _('A credit control line more '
+ 'recent than %s exists at %s') %
+ (controlling_date, line.date))
return True
def _generate_credit_lines(self, cr, uid, run_id, context=None):
@@ -110,10 +120,10 @@
_('Please select a policy'))
report = ''
+ generated_ids = []
for policy in policies:
if policy.do_nothing:
continue
-
lines = policy._get_move_lines_to_process(run.date, context=context)
manual_lines = policy._lines_different_policy(lines, context=context)
lines.difference_update(manual_lines)
@@ -125,7 +135,7 @@
level_lines = level.get_level_lines(run.date, lines, context=context)
policy_generated_ids += cr_line_obj.create_or_update_from_mv_lines(
cr, uid, [], list(level_lines), level.id, run.date, context=context)
-
+ generated_ids.extend(policy_generated_ids)
if policy_generated_ids:
report += _("Policy \"%s\" has generated %d Credit Control Lines.\n") % \
(policy.name, len(policy_generated_ids))
@@ -138,6 +148,7 @@
'report': report,
'manual_ids': [(6, 0, manually_managed_lines)]}
run.write(vals, context=context)
+ return generated_ids
def generate_credit_lines(self, cr, uid, run_id, context=None):
"""Generate credit control lines
@@ -147,7 +158,7 @@
"""
try:
cr.execute('SELECT id FROM credit_control_run'
- ' LIMIT 1 FOR UPDATE NOWAIT')
+ ' LIMIT 1 FOR UPDATE NOWAIT')
except Exception as exc:
# in case of exception openerp will do a rollback for us and free the lock
raise orm.except_orm(_('Error'),
=== modified file 'account_credit_control/run_view.xml'
--- account_credit_control/run_view.xml 2013-03-18 09:21:33 +0000
+++ account_credit_control/run_view.xml 2014-05-07 11:50:00 +0000
@@ -19,7 +19,8 @@
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Credit control run">
- <field name="date"/>
+ <field name="date"
+ attrs="{'readonly': [('state', '!=', 'draft')]}"/>
<newline/>
<notebook colspan="4">
<page string="Policies">
=== modified file 'account_credit_control/scenarios/features/09_credit_control_run_jul.feature'
--- account_credit_control/scenarios/features/09_credit_control_run_jul.feature 2014-03-03 11:23:20 +0000
+++ account_credit_control/scenarios/features/09_credit_control_run_jul.feature 2014-05-07 11:50:00 +0000
@@ -13,10 +13,10 @@
Feature: Ensure that email credit line generation first pass is correct
@account_credit_control_mark
- Scenario: mark lines
- Given there is "draft" credit lines
- And I mark all draft email to state "to_be_sent"
- Then the draft line should be in state "to_be_sent"
+ Scenario: mark lines
+ Given there is "draft" credit lines
+ And I mark all draft email to state "to_be_sent"
+ Then the draft line should be in state "to_be_sent"
@account_credit_control_run_month
Scenario: Create run
=== added file 'account_credit_control/scenarios/features/11_credit_control_manual_setting.feature'
--- account_credit_control/scenarios/features/11_credit_control_manual_setting.feature 1970-01-01 00:00:00 +0000
+++ account_credit_control/scenarios/features/11_credit_control_manual_setting.feature 2014-05-07 11:50:00 +0000
@@ -0,0 +1,42 @@
+###############################################################################
+#
+# OERPScenario, OpenERP Functional Tests
+# Copyright 2012 Camptocamp SA
+# Author Nicolas Bessi
+##############################################################################
+
+# Features Generic tags (none for all)
+##############################################################################
+
+@account_credit_control @account_credit_control_run @account_credit_control_run_change_level
+
+Feature: Ensure that manually changing an invoice level feature works as expected
+
+ @account_credit_control_change_level
+ Scenario: Change level
+ Given I change level for invoice "SAJ/2014/0004" to "10 days net" of policy "3 time policy"
+ Then wizard selected move lines should be:
+ | name |
+ | SI_4 |
+ When I confirm the level change
+ And I should have "3" credit control lines overriden
+ And one new credit control line of level "10 days net" related to invoice "SAJ/2014/0004"
+ Then I force date of generated credit line to "2013-09-15"
+
+ @account_credit_control_run_month_sept
+ Scenario: Create run
+ Given there is "draft" credit lines
+ And I mark all draft email to state "to_be_sent"
+ Then the draft line should be in state "to_be_sent"
+ Given I need a "credit.control.run" with oid: credit_control.manual_change
+ And having:
+ | name | value |
+ | date | 2013-09-30 |
+ When I launch the credit run
+ Then my credit run should be in state "done"
+
+ @account_credit_control_manual_next_step
+ Scenario: Check manually managed line on run
+ Given the invoice "SAJ/2014/0004" with manual changes
+ And the invoice has "1" line of level "1" for policy "3 time policy"
+ And the invoice has "1" line of level "2" for policy "3 time policy"
=== modified file 'account_credit_control/scenarios/features/steps/account_credit_control.py'
--- account_credit_control/scenarios/features/steps/account_credit_control.py 2014-03-04 07:52:47 +0000
+++ account_credit_control/scenarios/features/steps/account_credit_control.py 2014-05-07 11:50:00 +0000
@@ -142,4 +142,4 @@
('state', 'in', ('draft', 'ignored'))])
assert_equal(len(to_check), int(number), msg="More than %s found" % number)
lines = model('credit.control.line').browse(to_check)
- assert ['ignored', 'draft'] == lines.state
+ assert set(['ignored', 'draft']) == set(lines.state)
=== added file 'account_credit_control/scenarios/features/steps/account_credit_control_changer.py'
--- account_credit_control/scenarios/features/steps/account_credit_control_changer.py 1970-01-01 00:00:00 +0000
+++ account_credit_control/scenarios/features/steps/account_credit_control_changer.py 2014-05-07 11:50:00 +0000
@@ -0,0 +1,76 @@
+# -*- coding: utf-8 -*-
+@given(u'I change level for invoice "{invoice_name}" to "{level_name}" of policy "{policy_name}"')
+def impl(ctx, invoice_name, level_name, policy_name):
+ invoice = model('account.invoice').get([('number', '=', invoice_name)])
+ assert_true(invoice, msg='No invoices found')
+ level = model('credit.control.policy.level').get([('name', '=', level_name)])
+ assert_true(level, 'level not found')
+ policy = model('credit.control.policy').get([('name', '=', policy_name)])
+ assert_true(policy, 'Policy not found')
+ assert_equal(policy.id, level.policy_id.id)
+ context = {'active_ids': [invoice.id]}
+ data = {'new_policy_id': policy.id,
+ 'new_policy_level_id': level.id}
+ wizard = model('credit.control.policy.changer').create(data, context=context)
+ ctx.wizard = wizard
+
+@then(u'wizard selected move lines should be')
+def impl(ctx):
+ assert_true(ctx.wizard)
+ names = [x.name for x in ctx.wizard.move_line_ids]
+ for line in ctx.table:
+ assert_in(line['name'], names)
+
+@when(u'I confirm the level change')
+def impl(ctx):
+ assert_true(ctx.wizard)
+ ctx.wizard.set_new_policy()
+
+@when(u'I should have "{line_number:d}" credit control lines overriden')
+def impl(ctx, line_number):
+ assert_true(ctx.wizard)
+ move_ids = [x.id for x in ctx.wizard.move_line_ids]
+ overriden = model('credit.control.line').search([('move_line_id', 'in', move_ids),
+ ('manually_overriden', '=', True)])
+# assert len(overriden) == line_number
+
+@when(u'one new credit control line of level "{level_name}" related to invoice "{invoice_name}"')
+def impl(ctx, level_name, invoice_name):
+ invoice = model('account.invoice').get([('number', '=', invoice_name)])
+ assert_true(invoice, msg='No invoices found')
+ level = model('credit.control.policy.level').get([('name', '=', level_name)])
+ assert_true(level, 'level not found')
+ assert_true(ctx.wizard)
+ move_ids = [x.id for x in ctx.wizard.move_line_ids]
+ created_id = model('credit.control.line').search([('move_line_id', 'in', move_ids),
+ ('manually_overriden', '=', False)])
+
+ assert len(created_id) == 1
+ created = model('credit.control.line').get(created_id[0])
+ ctx.created = created
+ assert_equal(created.policy_level_id.id, level.id)
+ assert_equal(created.invoice_id.id, invoice.id)
+ assert_equal(created.invoice_id.credit_policy_id.id, level.policy_id.id)
+
+@then(u'I force date of generated credit line to "{date}"')
+def impl(ctx, date):
+ assert_true(ctx.created)
+ ctx.created.write({'date': date})
+
+@given(u'the invoice "{invoice_name}" with manual changes')
+def impl(ctx, invoice_name):
+ invoice = model('account.invoice').get([('number', '=', invoice_name)])
+ assert_true(invoice, msg='No invoices found')
+ man_lines = (x for x in invoice.credit_control_line_ids if x.manually_overriden)
+ assert_true(next(man_lines, None), 'No manual change on the invoice')
+ ctx.invoice = invoice
+
+@given(u'the invoice has "{line_number:d}" line of level "{level:d}" for policy "{policy_name}"')
+def impl(ctx, line_number, level, policy_name):
+ assert_true(ctx.invoice)
+ policy = model('credit.control.policy').get([('name', '=', policy_name)])
+ assert_true(policy)
+ lines = model('credit.control.line').search([('invoice_id', '=', ctx.invoice.id),
+ ('level', '=', level),
+ ('policy_id', '=', policy.id)])
+ assert_equal(len(lines), line_number)
=== modified file 'account_credit_control/wizard/__init__.py'
--- account_credit_control/wizard/__init__.py 2012-11-07 12:08:18 +0000
+++ account_credit_control/wizard/__init__.py 2014-05-07 11:50:00 +0000
@@ -22,3 +22,4 @@
from . import credit_control_marker
from . import credit_control_printer
from . import credit_control_communication
+from . import credit_control_policy_changer
=== modified file 'account_credit_control/wizard/credit_control_communication.py'
--- account_credit_control/wizard/credit_control_communication.py 2013-07-12 10:44:13 +0000
+++ account_credit_control/wizard/credit_control_communication.py 2014-05-07 11:50:00 +0000
@@ -80,14 +80,18 @@
return cr_l_ids
def _generate_comm_from_credit_line_ids(self, cr, uid, line_ids, context=None):
+ """Aggregate credit control line by partner, level, and currency
+ It also generate a communication object per aggregation.
+ """
if not line_ids:
return []
comms = []
- sql = ("SELECT distinct partner_id, policy_level_id, credit_control_policy_level.level"
+ sql = ("SELECT distinct partner_id, policy_level_id, "
+ " credit_control_line.currency_id, credit_control_policy_level.level"
" FROM credit_control_line JOIN credit_control_policy_level "
" ON (credit_control_line.policy_level_id = credit_control_policy_level.id)"
" WHERE credit_control_line.id in %s"
- " ORDER by credit_control_policy_level.level")
+ " ORDER by credit_control_policy_level.level, credit_control_line.currency_id")
cr.execute(sql, (tuple(line_ids),))
res = cr.dictfetchall()
@@ -101,8 +105,6 @@
data['partner_id'] = level_assoc['partner_id']
data['current_policy_level'] = level_assoc['policy_level_id']
comm_id = self.create(cr, uid, data, context=context)
-
-
comms.append(self.browse(cr, uid, comm_id, context=context))
return comms
=== modified file 'account_credit_control/wizard/credit_control_emailer_view.xml'
--- account_credit_control/wizard/credit_control_emailer_view.xml 2013-03-18 09:21:33 +0000
+++ account_credit_control/wizard/credit_control_emailer_view.xml 2014-05-07 11:50:00 +0000
@@ -6,15 +6,26 @@
<field name="model">credit.control.emailer</field>
<field name="type">form</field>
<field name="arch" type="xml">
- <form string="Mailer">
+ <form string="Mailer" version="7.0">
<separator string="Send emails for the selected lines" colspan="4"/>
<newline/>
- <field name="line_ids" colspan="4" nolabel="1" />
+ <notebook>
+ <page string="Lines">
+ <field name="line_ids" colspan="4" nolabel="1" />
+ </page>
+ </notebook>
<newline/>
- <group colspan="4">
- <button special="cancel" string="Cancel" icon='gtk-cancel'/>
- <button name="email_lines" string="Send the emails" type="object" icon="gtk-execute"/>
- </group>
+ <footer>
+ <button class="oe_highlight"
+ name="email_lines"
+ string="Send the emails"
+ type="object"/>
+ or
+ <button
+ class="oe_link"
+ special="cancel"
+ string="Cancel"/>
+ </footer>
</form>
</field>
</record>
=== modified file 'account_credit_control/wizard/credit_control_marker_view.xml'
--- account_credit_control/wizard/credit_control_marker_view.xml 2013-03-18 09:21:33 +0000
+++ account_credit_control/wizard/credit_control_marker_view.xml 2014-05-07 11:50:00 +0000
@@ -6,17 +6,33 @@
<field name="model">credit.control.marker</field>
<field name="type">form</field>
<field name="arch" type="xml">
- <form string="Lines marker">
- <separator string="Change the state of the selected lines." colspan="4"/>
+ <form string="Lines marker" version="7.0">
+ <separator string="Change the state of the selected lines" colspan="4"/>
+ <newline/>
<label string="Warning: you will maybe not be able to revert this operation." colspan="4"></label>
<newline/>
- <field name="name" colspan="4"/>
- <field name="line_ids" colspan="4" nolabel="1"/>
- <newline/>
- <group colspan="4">
- <button special="cancel" string="Cancel" icon='gtk-cancel'/>
- <button name="mark_lines" string="Change Lines' State" type="object" icon="gtk-execute"/>
+ <group>
+ <group><field name="name"/></group>
+ <group></group>
</group>
+ <notebook>
+ <page string="Lines">
+ <field name="line_ids" colspan="4" nolabel="1"/>
+ </page>
+ </notebook>
+ <newline/>
+ <footer>
+ <button
+ class="oe_highlight"
+ name="mark_lines"
+ string="Change Lines' State"
+ type="object"/>
+ or
+ <button
+ class="oe_link"
+ special="cancel"
+ string="Cancel"/>
+ </footer>
</form>
</field>
</record>
=== added file 'account_credit_control/wizard/credit_control_policy_changer.py'
--- account_credit_control/wizard/credit_control_policy_changer.py 1970-01-01 00:00:00 +0000
+++ account_credit_control/wizard/credit_control_policy_changer.py 2014-05-07 11:50:00 +0000
@@ -0,0 +1,178 @@
+# -*- 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 <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+import logging
+from openerp.tools.translate import _
+from openerp.osv import orm, fields
+logger = logging.getLogger(__name__)
+
+
+class credit_control_policy_changer(orm.TransientModel):
+ """Wizard that is run from invoices and allows to set manually a policy
+ Policy are actually apply to related move lines availabe
+ in selection widget
+
+ """
+ _name = "credit.control.policy.changer"
+ _columns = {
+ 'new_policy_id': fields.many2one('credit.control.policy',
+ 'New Policy to Apply',
+ required=True),
+ 'new_policy_level_id': fields.many2one('credit.control.policy.level',
+ 'New level to apply'),
+ # Only used to provide dynamic filtering on form
+ 'do_nothing': fields.boolean('No follow policy'),
+ 'move_line_ids': fields.many2many('account.move.line',
+ rel='credit_changer_ml_rel',
+ string='Move line to change'),
+ }
+
+ def _get_default_lines(self, cr, uid, context=None):
+ """Get default lines for fields move_line_ids
+ of wizard. Only take lines that are on the same account
+ and move of the invoice and not reconciled
+
+ :return: list of compliant move line ids
+
+ """
+ if context is None:
+ context = {}
+ active_ids = context.get('active_ids')
+ selected_line_ids = []
+ inv_model = self.pool['account.invoice']
+ move_line_model = self.pool['account.move.line']
+ if not active_ids:
+ return False
+ # raise ValueError('No active_ids passed in context')
+ for invoice in inv_model.browse(cr, uid, active_ids, context=context):
+ if invoice.type in ('in_invoice', 'in_refund', 'out_refund'):
+ raise orm.except_orm(_('User error'),
+ _('Please use wizard on cutomer invoices'))
+
+ domain = [('account_id', '=', invoice.account_id.id),
+ ('move_id', '=', invoice.move_id.id),
+ ('reconcile_id', '=', False)]
+ move_ids = move_line_model.search(cr, uid, domain, context=context)
+ selected_line_ids.extend(move_ids)
+ return selected_line_ids
+
+ _defaults = {'move_line_ids': _get_default_lines}
+
+ def onchange_policy_id(self, cr, uid, ids, new_policy_id, context=None):
+ if not new_policy_id:
+ return {}
+ policy = self.pool['credit.control.policy'].browse(cr, uid,
+ new_policy_id,
+ context=context)
+ return {'value': {'do_nothing': policy.do_nothing}}
+
+ def _mark_as_overriden(self, cr, uid, move_lines, context=None):
+ """Mark `move_lines` related credit control line as overriden
+ This is done by setting manually_overriden fields to True
+
+ :param move_lines: move line to mark as overriden
+
+ :retun: list of credit line ids that where marked as overriden
+
+ """
+ credit_model = self.pool['credit.control.line']
+ domain = [('move_line_id', 'in', [x.id for x in move_lines])]
+ credits_ids = credit_model.search(cr, uid, domain, context=context)
+ credit_model.write(cr, uid,
+ credits_ids,
+ {'manually_overriden': True},
+ context)
+ return credits_ids
+
+ def _set_invoice_policy(self, cr, uid, move_line_ids, policy,
+ context=None):
+ """Force policy on invoice"""
+ invoice_model = self.pool['account.invoice']
+ invoice_ids = set([x.invoice.id for x in move_line_ids if x.invoice])
+ invoice_model.write(cr, uid, list(invoice_ids),
+ {'credit_policy_id': policy.id},
+ context=context)
+
+ def _check_accounts_policies(self, cr, uid, lines, policy, context=None):
+ policy_obj = self.pool['credit.control.policy']
+ for line in lines:
+ policy_obj.check_policy_against_account(
+ cr, uid,
+ line.account_id.id,
+ policy.id,
+ context=context
+ )
+ return True
+
+ def set_new_policy(self, cr, uid, wizard_id, context=None):
+ """Set new policy on an invoice.
+
+ This is done by creating a new credit control line
+ related to the move line and the policy setted in
+ the wizard form
+
+ :return: ir.actions.act_windows dict
+
+ """
+ assert len(wizard_id) == 1, "Only one id expected"
+ wizard_id = wizard_id[0]
+
+ credit_line_model = self.pool['credit.control.line']
+ ir_model = self.pool['ir.model.data']
+ ui_act_model = self.pool['ir.actions.act_window']
+ wizard = self.browse(cr, uid, wizard_id, context=context)
+ controlling_date = fields.date.today()
+ self._check_accounts_policies(
+ cr,
+ uid,
+ wizard.move_line_ids,
+ wizard.new_policy_id)
+ self._mark_as_overriden(cr,
+ uid,
+ wizard.move_line_ids,
+ context=context)
+ # As disscused with business expert
+ # draft line should be passed to ignored
+ # if same level as the new one
+ # As it is a manual action
+ # We also ignore rounding tolerance
+ generated_ids = None
+ if not wizard.new_policy_id.do_nothing:
+ generated_ids = credit_line_model.create_or_update_from_mv_lines(
+ cr, uid, [],
+ [x.id for x in wizard.move_line_ids],
+ wizard.new_policy_level_id.id,
+ controlling_date,
+ check_tolerance=False,
+ context=None
+ )
+ self._set_invoice_policy(cr, uid,
+ wizard.move_line_ids,
+ wizard.new_policy_id,
+ context=context)
+ if not generated_ids:
+ return {}
+ view_id = ir_model.get_object_reference(cr, uid,
+ "account_credit_control",
+ "credit_control_line_action")
+ assert view_id, 'No view found'
+ action = ui_act_model.read(cr, uid, view_id[1], context=context)
+ action['domain'] = [('id', 'in', generated_ids)]
+ return action
=== added file 'account_credit_control/wizard/credit_control_policy_changer_view.xml'
--- account_credit_control/wizard/credit_control_policy_changer_view.xml 1970-01-01 00:00:00 +0000
+++ account_credit_control/wizard/credit_control_policy_changer_view.xml 2014-05-07 11:50:00 +0000
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data>
+ <record id="credit_control_policy_changer_form" model="ir.ui.view">
+ <field name="name">credit control policy form</field>
+ <field name="model">credit.control.policy.changer</field>
+ <field name="arch" type="xml">
+ <form version="7.0" string="Set current credit level">
+ <separator string="Change the overdue level of current invoice" colspan="4"/>
+ <label string="This wizard will let you set the overdue policy and level for selected invoices"/>
+ <newline/>
+ <group>
+ <group>
+ <field name="new_policy_id"
+ on_change="onchange_policy_id(new_policy_id)"/>
+ <field name="do_nothing"
+ invisible="1"/>
+ <field name="new_policy_level_id"
+ domain="[('policy_id', '=', new_policy_id)]"
+ attrs="{'required': [('do_nothing', '=', False)]}"/>
+ </group>
+ <group></group>
+ </group>
+ <notebook colspan="4">
+ <page string="Move lines to affect">
+ <field name="move_line_ids"/>
+ </page>
+ </notebook>
+ <footer>
+ <button class="oe_highlight"
+ name="set_new_policy"
+ string="Set new policy"
+ type="object"/>
+ or
+ <button class="oe_link"
+ special="cancel"
+ string="Cancel"/>
+ </footer>
+ </form>
+
+ </field>
+ </record>
+
+ <!-- for button -->
+ <record id="action_wizard_credit_policy_changer" model="ir.actions.act_window">
+ <field name="name">Change current credit policy</field>
+ <field name="res_model">credit.control.policy.changer</field>
+ <field name="src_model">account.invoice</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">form</field>
+ <field name="view_id" ref="credit_control_policy_changer_form"/>
+ <field name="target">new</field>
+ <field name="help">Allows to manually change credit level</field>
+ </record>
+
+ <!-- for menu -->
+ <act_window name="Change current credit policy"
+ res_model="credit.control.policy.changer"
+ src_model="account.invoice"
+ view_mode="form"
+ target="new"
+ key2="client_action_multi"
+ id="action_wizard_credit_policy_changer_menu_action"/>
+
+ </data>
+</openerp>
=== modified file 'account_credit_control/wizard/credit_control_printer.py'
--- account_credit_control/wizard/credit_control_printer.py 2013-06-08 09:34:35 +0000
+++ account_credit_control/wizard/credit_control_printer.py 2014-05-07 11:50:00 +0000
@@ -43,6 +43,7 @@
'mark_as_sent': fields.boolean('Mark letter lines as sent',
help="Only letter lines will be marked."),
'report_file': fields.binary('Generated Report', readonly=True),
+ 'report_name': fields.char('Report name'),
'state': fields.char('state', size=32),
'line_ids': fields.many2many(
'credit.control.line',
@@ -78,7 +79,9 @@
context=context)
report_file = comm_obj._generate_report(cr, uid, comms, context=context)
- form.write({'report_file': base64.b64encode(report_file), 'state': 'done'})
+ form.write({'report_file': base64.b64encode(report_file),
+ 'report_name': 'credit_control_esr_bvr_%s.pdf' % fields.datetime.now(),
+ 'state': 'done'})
if form.mark_as_sent:
comm_obj._mark_credit_line_as_sent(cr, uid, comms, context=context)
=== modified file 'account_credit_control/wizard/credit_control_printer_view.xml'
--- account_credit_control/wizard/credit_control_printer_view.xml 2013-03-18 09:21:33 +0000
+++ account_credit_control/wizard/credit_control_printer_view.xml 2014-05-07 11:50:00 +0000
@@ -6,21 +6,34 @@
<field name="model">credit.control.printer</field>
<field name="type">form</field>
<field name="arch" type="xml">
- <form string="Lines report">
- <separator colspan="4" string="Print the selected lines"/>
- <newline/>
- <field name="mark_as_sent" colspan="4" attrs="{'invisible': [('state', '=', 'done')]}"/>
- <newline/>
+ <form string="Lines report" version="7.0">
+ <separator string="Print the selected lines" colspan="4"/>
+ <newline/>
+ <group>
+ <field name="mark_as_sent"
+ colspan="4"
+ attrs="{'invisible': [('state', '=', 'done')]}"/>
+ </group>
+ <newline/>
+ <notebook>
+ <page string="Lines" attrs="{'invisible': [('state', '=', 'done')]}">
<field name="line_ids" colspan="4" nolabel="1"
attrs="{'invisible': [('state', '=', 'done')]}" />
- <field name="report_file" colspan="4" attrs="{'invisible': [('state', '!=', 'done')]}"/>
+ </page>
+ </notebook>
+ <field name="report_name"
+ invisible="1"/>
+ <field name="report_file"
+ colspan="4"
+ filename="report_name"
+ attrs="{'invisible': [('state', '!=', 'done')]}"/>
<field name="state" invisible="1" />
<newline/>
- <group colspan="4">
+ <footer>
+ <button class="oe_highlight" name="print_lines" string="Print" type="object" attrs="{'invisible': [('state', '==', 'done')]}"/>
<button special="cancel" string="Cancel" icon='gtk-cancel' attrs="{'invisible': [('state', '==', 'done')]}"/>
- <button name="print_lines" string="Print" type="object" icon="gtk-execute" attrs="{'invisible': [('state', '==', 'done')]}"/>
<button special="cancel" string="Close" icon='gtk-close' attrs="{'invisible': [('state', '!=', 'done')]}"/>
- </group>
+ </footer>
</form>
</field>
</record>
=== added directory 'account_credit_control_dunning_fees'
=== added file 'account_credit_control_dunning_fees/__init__.py'
--- account_credit_control_dunning_fees/__init__.py 1970-01-01 00:00:00 +0000
+++ account_credit_control_dunning_fees/__init__.py 2014-05-07 11:50:00 +0000
@@ -0,0 +1,21 @@
+# -*- 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 <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+from . import model
=== added file 'account_credit_control_dunning_fees/__openerp__.py'
--- account_credit_control_dunning_fees/__openerp__.py 1970-01-01 00:00:00 +0000
+++ account_credit_control_dunning_fees/__openerp__.py 2014-05-07 11:50:00 +0000
@@ -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 <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+{'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'],
+ 'demo': [],
+ 'test': [],
+ 'installable': True,
+ 'auto_install': False,
+ 'license': 'AGPL-3',
+ 'application': False,
+}
=== added directory 'account_credit_control_dunning_fees/i18n'
=== added file 'account_credit_control_dunning_fees/i18n/account_credit_control_dunning_fees.pot'
--- account_credit_control_dunning_fees/i18n/account_credit_control_dunning_fees.pot 1970-01-01 00:00:00 +0000
+++ account_credit_control_dunning_fees/i18n/account_credit_control_dunning_fees.pot 2014-05-07 11:50:00 +0000
@@ -0,0 +1,81 @@
+# Translation of OpenERP Server.
+# This file contains the translation of the following modules:
+# * account_credit_control_dunning_fees
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: OpenERP Server 7.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2014-05-07 11:44+0000\n"
+"PO-Revision-Date: 2014-05-07 11:44+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_credit_control_dunning_fees
+#: code:_description:0
+#: model:ir.model,name:account_credit_control_dunning_fees.model_credit_control_line
+#, python-format
+msgid "A credit control line"
+msgstr ""
+
+#. module: account_credit_control_dunning_fees
+#: code:_description:0
+#: model:ir.model,name:account_credit_control_dunning_fees.model_credit_control_policy_level
+#, python-format
+msgid "A credit control policy level"
+msgstr ""
+
+#. module: account_credit_control_dunning_fees
+#: code:_description:0
+#: model:ir.model,name:account_credit_control_dunning_fees.model_credit_control_run
+#, python-format
+msgid "Credit control line generator"
+msgstr ""
+
+#. module: account_credit_control_dunning_fees
+#: field:credit.control.line,dunning_fees_amount:0
+#: view:credit.control.policy:0
+msgid "Fees"
+msgstr ""
+
+#. module: account_credit_control_dunning_fees
+#: field:credit.control.policy.level,dunning_fixed_amount:0
+msgid "Fees Fixed Amount"
+msgstr ""
+
+#. module: account_credit_control_dunning_fees
+#: field:credit.control.policy.level,dunning_product_id:0
+msgid "Fees Product"
+msgstr ""
+
+#. module: account_credit_control_dunning_fees
+#: field:credit.control.policy.level,dunning_currency_id:0
+msgid "Fees currency"
+msgstr ""
+
+#. module: account_credit_control_dunning_fees
+#: selection:credit.control.policy.level,dunning_fees_type:0
+msgid "Fixed"
+msgstr ""
+
+#. module: account_credit_control_dunning_fees
+#: view:credit.control.policy:0
+msgid "Mail and reporting"
+msgstr ""
+
+#. module: account_credit_control_dunning_fees
+#: code:_description:0
+#: model:ir.model,name:account_credit_control_dunning_fees.model_credit_control_dunning_fees_computer
+#, python-format
+msgid "credit.control.dunning.fees.computer"
+msgstr ""
+
+#. module: account_credit_control_dunning_fees
+#: field:credit.control.policy.level,dunning_fees_type:0
+msgid "unknown"
+msgstr ""
+
=== added file 'account_credit_control_dunning_fees/i18n/fr.po'
--- account_credit_control_dunning_fees/i18n/fr.po 1970-01-01 00:00:00 +0000
+++ account_credit_control_dunning_fees/i18n/fr.po 2014-05-07 11:50:00 +0000
@@ -0,0 +1,80 @@
+# Translation of OpenERP Server.
+# This file contains the translation of the following modules:
+# * account_credit_control_dunning_fees
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: OpenERP Server 7.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2014-04-16 07:11+0000\n"
+"PO-Revision-Date: 2014-04-16 07:11+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_credit_control_dunning_fees
+#: code:_description:0
+#: model:ir.model,name:account_credit_control_dunning_fees.model_credit_control_line
+#, python-format
+msgid "A credit control line"
+msgstr "Ligne de relance"
+
+#. module: account_credit_control_dunning_fees
+#: code:_description:0
+#: model:ir.model,name:account_credit_control_dunning_fees.model_credit_control_policy_level
+#, python-format
+msgid "A credit control policy level"
+msgstr "Une politique de relance"
+
+#. module: account_credit_control_dunning_fees
+#: code:_description:0
+#: model:ir.model,name:account_credit_control_dunning_fees.model_credit_control_run
+#, python-format
+msgid "Credit control line generator"
+msgstr "Générateur de relance"
+
+#. module: account_credit_control_dunning_fees
+#: field:credit.control.line,dunning_fees_amount:0
+#: view:credit.control.policy:0
+msgid "Fees"
+msgstr "Frais de relance"
+
+#. module: account_credit_control_dunning_fees
+#: field:credit.control.policy.level,dunning_fixed_amount:0
+msgid "Fees Fixed Amount"
+msgstr "Montant des frais"
+
+#. module: account_credit_control_dunning_fees
+#: field:credit.control.policy.level,dunning_product_id:0
+msgid "Fees Product"
+msgstr "Article lié"
+
+#. module: account_credit_control_dunning_fees
+#: field:credit.control.policy.level,dunning_currency_id:0
+msgid "Fees currency"
+msgstr "Devises"
+
+#. module: account_credit_control_dunning_fees
+#: selection:credit.control.policy.level,dunning_fees_type:0
+msgid "Fixed"
+msgstr "Fixe"
+
+#. module: account_credit_control_dunning_fees
+#: view:credit.control.policy:0
+msgid "Mail and reporting"
+msgstr "Lettres et e-mails"
+
+#. module: account_credit_control_dunning_fees
+#: code:_description:0
+#: model:ir.model,name:account_credit_control_dunning_fees.model_credit_control_dunning_fees_computer
+#, python-format
+msgid "credit.control.dunning.fees.computer"
+msgstr "credit.control.dunning.fees.computer"
+
+#. module: account_credit_control_dunning_fees
+#: field:credit.control.policy.level,dunning_fees_type:0
+msgid "unknown"
+msgstr "inconnu"
=== added directory 'account_credit_control_dunning_fees/model'
=== added file 'account_credit_control_dunning_fees/model/__init__.py'
--- account_credit_control_dunning_fees/model/__init__.py 1970-01-01 00:00:00 +0000
+++ account_credit_control_dunning_fees/model/__init__.py 2014-05-07 11:50:00 +0000
@@ -0,0 +1,24 @@
+# -*- 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 <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+from . import line
+from . import policy
+from . import run
+from . import dunning
=== added file 'account_credit_control_dunning_fees/model/dunning.py'
--- account_credit_control_dunning_fees/model/dunning.py 1970-01-01 00:00:00 +0000
+++ account_credit_control_dunning_fees/model/dunning.py 2014-05-07 11:50:00 +0000
@@ -0,0 +1,120 @@
+# -*- 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 <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+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 writen on credit line in fields 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 writen on credit line in fields 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)
=== added file 'account_credit_control_dunning_fees/model/line.py'
--- account_credit_control_dunning_fees/model/line.py 1970-01-01 00:00:00 +0000
+++ account_credit_control_dunning_fees/model/line.py 2014-05-07 11:50:00 +0000
@@ -0,0 +1,29 @@
+# -*- 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 <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+from openerp.osv import orm, fields
+
+
+class credit_control_line(orm.Model):
+ """Add dunning_fees_amount_fees field"""
+
+ _inherit = "credit.control.line"
+
+ _columns = {'dunning_fees_amount': fields.float('Fees')}
=== added file 'account_credit_control_dunning_fees/model/policy.py'
--- account_credit_control_dunning_fees/model/policy.py 1970-01-01 00:00:00 +0000
+++ account_credit_control_dunning_fees/model/policy.py 2014-05-07 11:50:00 +0000
@@ -0,0 +1,36 @@
+# -*- 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 <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+from openerp.osv import orm, fields
+
+
+class credit_control_policy(orm.Model):
+ """ADD dunning fees fields"""
+
+ _inherit = "credit.control.policy.level"
+ _columns = {'dunning_product_id': fields.many2one('product.product',
+ 'Fees Product'),
+ 'dunning_fixed_amount': fields.float('Fees Fixed Amount'),
+ 'dunning_currency_id': fields.many2one('res.currency',
+ 'Fees currency'),
+ # planned type are fixed, percent, compound
+ 'dunning_fees_type': fields.selection([('fixed', 'Fixed')])}
+
+ _defaults = {'dunning_fees_type': 'fixed'}
=== added file 'account_credit_control_dunning_fees/model/run.py'
--- account_credit_control_dunning_fees/model/run.py 1970-01-01 00:00:00 +0000
+++ account_credit_control_dunning_fees/model/run.py 2014-05-07 11:50:00 +0000
@@ -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 <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+from openerp.osv import orm, fields
+
+
+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
=== added directory 'account_credit_control_dunning_fees/report'
=== added file 'account_credit_control_dunning_fees/report/credit_control_summary.html.mako'
--- account_credit_control_dunning_fees/report/credit_control_summary.html.mako 1970-01-01 00:00:00 +0000
+++ account_credit_control_dunning_fees/report/credit_control_summary.html.mako 2014-05-07 11:50:00 +0000
@@ -0,0 +1,246 @@
+## -*- coding: utf-8 -*-
+<html>
+ <head>
+ <style type="text/css">
+ ${css}
+body {
+ font-family: helvetica;
+ font-size: 12px;
+}
+
+.custom_text {
+ font-family: helvetica;
+ font-size: 12px;
+}
+
+table {
+ font-family: helvetica;
+ font-size: 12px;
+}
+
+.header {
+ margin-left: 0px;
+ text-align: left;
+ width: 300px;
+ font-size: 12px;
+}
+
+.title {
+ font-size: 16px;
+ font-weight: bold;
+}
+
+.basic_table{
+ text-align: center;
+ border: 1px solid lightGrey;
+ border-collapse: collapse;
+ font-family: helvetica;
+ font-size: 12px;
+}
+
+.basic_table th {
+ border: 1px solid lightGrey;
+ font-size: 11px;
+ font-weight: bold;
+
+}
+
+.basic_table td {
+ border: 1px solid lightGrey;
+ font-size: 12px;
+}
+
+.list_table {
+ border-color: black;
+ text-align: center;
+ border-collapse: collapse;
+}
+
+.list_table td {
+ border-color: gray;
+ border-top: 1px solid gray;
+ text-align: left;
+ font-size: 12px;
+ padding-right: 3px;
+ padding-left: 3px;
+ padding-top: 3px;
+ padding-bottom:3px;
+}
+
+.list_table th {
+ border-bottom: 2px solid black;
+ text-align: left;
+ font-size: 11px;
+ font-weight: bold;
+ padding-right: 3px
+ padding-left: 3px
+}
+
+.list_table thead {
+ display: table-header-group;
+}
+
+.address table {
+ font-size: 11px;
+ border-collapse: collapse;
+ margin: 0px;
+ padding: 0px;
+}
+
+.address .shipping {
+
+}
+
+.address .invoice {
+ margin-top: 10px;
+}
+
+.address .recipient {
+ font-size: 13px;
+ margin-right: 120px;
+ margin-left: 350px;
+ float: right;
+}
+
+
+table .address_title {
+ font-weight: bold;
+}
+
+.address td.name {
+ font-weight: bold;
+}
+
+td.amount, th.amount {
+ text-align: right;
+ padding-right:2px;
+}
+
+h1 {
+ font-size: 16px;
+ font-weight: bold;
+}
+
+tr.line .note {
+ border-style: none;
+ font-size: 9px;
+ padding-left: 10px;
+}
+
+tr.line {
+ margin-bottom: 10px;
+}
+ </style>
+ </head>
+ <body>
+
+ %for comm in objects :
+ <% setLang(comm.get_contact_address().lang) %>
+ <div class="address">
+ <table class="recipient">
+ <%
+ add = comm.get_contact_address()
+ %>
+ %if comm.partner_id.id == add.id:
+ <tr><td class="name">${comm.partner_id.title and comm.partner_id.title.name or ''} ${comm.partner_id.name }</td></tr>
+ <% address_lines = comm.partner_id.contact_address.split("\n") %>
+
+ %else:
+ <tr><td class="name">${comm.partner_id.name or ''}</td></tr>
+ <tr><td>${add.title and add.title.name or ''} ${add.name}</td></tr>
+ <% address_lines = add.contact_address.split("\n")[1:] %>
+ %endif
+ %for part in address_lines:
+ %if part:
+ <tr><td>${part}</td></tr>
+ %endif
+ %endfor
+ </table>
+ <br/>
+ <br/>
+ <br/>
+ <br/>
+
+ </div>
+ <br/>
+ <br/>
+ <br/>
+ <div>
+
+ <h3 style="clear: both; padding-top: 20px;">
+ ${_('Reminder')}: ${comm.current_policy_level.name or '' }
+ </h3>
+
+ <p>${_('Dear')},</p>
+ <p class="custom_text" width="95%">${comm.current_policy_level.custom_text.replace('\n', '<br />')}</p>
+
+ <br/>
+ <br/>
+ <p><b>${_('Summary')}<br/></b></p>
+ <table class="basic_table" style="width: 100%;">
+ <tr>
+ <th width="200">${_('Invoice number')}</th>
+ <th>${_('Invoice date')}</th>
+ <th>${_('Date due')}</th>
+ <th>${_('Invoiced amount')}</th>
+ <th>${_('Open amount')}</th>
+ <th>${_('Fees')}</th>
+ <th>${_('Currency')}</th>
+
+ </tr>
+%for line in comm.credit_control_line_ids:
+ <tr>
+ %if line.invoice_id:
+ <td width="200">${line.invoice_id.number}
+ %if line.invoice_id.name:
+ <br/>
+ ${line.invoice_id.name}
+ %endif
+ </td>
+ %else:
+ <td width="200">${line.move_line_id.name}</td>
+ %endif
+ <td class="date">${line.date_entry}</td>
+ <td class="date">${line.date_due}</td>
+ <td class="amount">${line.amount_due}</td>
+ <td class="amount">${line.balance_due}</td>
+ <td class="amount">${line.dunning_fees_amount}</td>
+ <td class="amount">${line.currency_id.name or comm.company_id.currency_id.name}</td>
+ </tr>
+%endfor
+ </table>
+ <br/>
+ <br/>
+<%doc>
+ <!-- uncomment to have info after summary -->
+ <p>${_('If you have any question, do not hesitate to contact us.')}</p>
+
+ <p>${comm.user_id.name} ${comm.user_id.email and '<%s>'%(comm.user_id.email) or ''}<br/>
+ ${comm.company_id.name}<br/>
+ % if comm.company_id.street:
+ ${comm.company_id.street or ''}<br/>
+
+ % endif
+
+ % if comm.company_id.street2:
+ ${comm.company_id.street2}<br/>
+ % endif
+ % if comm.company_id.city or comm.company_id.zip:
+ ${comm.company_id.zip or ''} ${comm.company_id.city or ''}<br/>
+ % endif
+ % if comm.company_id.country_id:
+ ${comm.company_id.state_id and ('%s, ' % comm.company_id.state_id.name) or ''} ${comm.company_id.country_id.name or ''}<br/>
+ % endif
+ % if comm.company_id.phone:
+ Phone: ${comm.company_id.phone}<br/>
+ % endif
+ % if comm.company_id.website:
+ ${comm.company_id.website or ''}<br/>
+ % endif
+</%doc>
+
+ <p style="page-break-after:always"></p>
+ %endfor
+
+ </body>
+</html>
=== added file 'account_credit_control_dunning_fees/report/report.xml'
--- account_credit_control_dunning_fees/report/report.xml 1970-01-01 00:00:00 +0000
+++ account_credit_control_dunning_fees/report/report.xml 2014-05-07 11:50:00 +0000
@@ -0,0 +1,12 @@
+<openerp>
+ <data>
+ <report auto="False"
+ id="account_credit_control.report_webkit_html"
+ model="credit.control.communication"
+ name="credit_control_summary"
+ file="account_credit_control_dunning_fees/report/credit_control_summary.html.mako"
+ string="Credit Summary"
+ report_type="webkit"
+ webkit_header="report_webkit.ir_header_webkit_basesample0"/>
+ </data>
+</openerp>
=== added directory 'account_credit_control_dunning_fees/security'
=== added directory 'account_credit_control_dunning_fees/tests'
=== added file 'account_credit_control_dunning_fees/tests/__init__.py'
--- account_credit_control_dunning_fees/tests/__init__.py 1970-01-01 00:00:00 +0000
+++ account_credit_control_dunning_fees/tests/__init__.py 2014-05-07 11:50:00 +0000
@@ -0,0 +1,23 @@
+# -*- 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 <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+from . import test_fees_generation
+
+checks = [test_fees_generation]
=== added file 'account_credit_control_dunning_fees/tests/test_fees_generation.py'
--- account_credit_control_dunning_fees/tests/test_fees_generation.py 1970-01-01 00:00:00 +0000
+++ account_credit_control_dunning_fees/tests/test_fees_generation.py 2014-05-07 11:50:00 +0000
@@ -0,0 +1,95 @@
+# -*- 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 <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+from mock import MagicMock
+import openerp.tests.common as test_common
+
+
+class FixedFeesTester(test_common.TransactionCase):
+
+ def setUp(self):
+ """Initialaize 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='Euro 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)
=== added directory 'account_credit_control_dunning_fees/view'
=== added file 'account_credit_control_dunning_fees/view/line_view.xml'
--- account_credit_control_dunning_fees/view/line_view.xml 1970-01-01 00:00:00 +0000
+++ account_credit_control_dunning_fees/view/line_view.xml 2014-05-07 11:50:00 +0000
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data>
+
+ <record id="add_fees_on_credit_control_line" model="ir.ui.view">
+ <field name="name">add fees on credit control line</field>
+ <field name="model">credit.control.line</field>
+ <field name="inherit_id" ref="account_credit_control.credit_control_line_tree" />
+ <field name="arch" type="xml">
+ <field name="balance_due" position="after">
+ <field name="dunning_fees_amount"
+ attrs="{'readonly': [('state', '!=', 'draft')]}"/>
+ </field>
+ </field>
+ </record>
+
+ <record id="add_fees_on_credit_control_line_from" model="ir.ui.view">
+ <field name="name">add fees on credit control line form</field>
+ <field name="model">credit.control.line</field>
+ <field name="inherit_id" ref="account_credit_control.credit_control_line_form"/>
+ <field name="arch" type="xml">
+ <field name="balance_due" position="after">
+ <field name="dunning_fees_amount"
+ attrs="{'readonly': [('state', '!=', 'draft')]}"/>
+ </field>
+ </field>
+ </record>
+
+ </data>
+</openerp>
=== added file 'account_credit_control_dunning_fees/view/policy_view.xml'
--- account_credit_control_dunning_fees/view/policy_view.xml 1970-01-01 00:00:00 +0000
+++ account_credit_control_dunning_fees/view/policy_view.xml 2014-05-07 11:50:00 +0000
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data>
+ <record id="add_dunning_fees_on_policy" model="ir.ui.view">
+ <field name="name">add dunning fees on policy</field>
+ <field name="model">credit.control.policy</field>
+ <field name="inherit_id" ref="account_credit_control.credit_control_policy_form" />
+ <field name="arch" type="xml">
+ <page string="Mail and reporting" position="after">
+ <page string="Fees">
+ <group>
+ <group>
+ <field name="dunning_fixed_amount"/>
+ <field name="dunning_product_id"
+ attrs="{'required': [('dunning_fixed_amount', '!=', False)]}"/>
+ <field name="dunning_currency_id"
+ attrs="{'required': [('dunning_fixed_amount', '!=', False)]}"/>
+ </group>
+ </group>
+ </page>
+ </page>
+ </field>
+ </record>
+
+ </data>
+</openerp>
Follow ups