diff --git a/account_credit_control/__openerp__.py b/account_credit_control/__openerp__.py
index 5da57004a..0f8871bf0 100644
--- a/account_credit_control/__openerp__.py
+++ b/account_credit_control/__openerp__.py
@@ -19,7 +19,7 @@
#
##############################################################################
{'name': 'Account Credit Control',
- 'version': '0.1',
+ 'version': '0.2.0',
'author': 'Camptocamp',
'maintainer': 'Camptocamp',
'category': 'Finance',
@@ -69,6 +69,7 @@ On each generated line, you have many choices:
"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': [],
diff --git a/account_credit_control/account.py b/account_credit_control/account.py
index 9e8011b0b..7a2ef4071 100644
--- a/account_credit_control/account.py
+++ b/account_credit_control/account.py
@@ -27,11 +27,11 @@ class AccountAccount(orm.Model):
_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 @@ class AccountAccount(orm.Model):
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)
diff --git a/account_credit_control/company.py b/account_credit_control/company.py
index eb8187a4c..893b33bb5 100644
--- a/account_credit_control/company.py
+++ b/account_credit_control/company.py
@@ -31,7 +31,7 @@ class ResCompany(orm.Model):
'credit_policy_id': fields.many2one('credit.control.policy',
'Credit Control Policy',
help=("The Credit Control Policy used on partners"
- " by default. This setting can be overriden"
+ " by default. This setting can be overridden"
" on partners or invoices.")),
}
diff --git a/account_credit_control/data.xml b/account_credit_control/data.xml
index aa46023da..09fc3cced 100644
--- a/account_credit_control/data.xml
+++ b/account_credit_control/data.xml
@@ -25,6 +25,21 @@
+
+
+ No follow
+
+ net_days
+
+
+
+ email
+ Manual no follow
+
+ Manual no follow
+
+
diff --git a/account_credit_control/i18n/de.po b/account_credit_control/i18n/de.po
index 761cfb60a..6f3eaaf27 100644
--- a/account_credit_control/i18n/de.po
+++ b/account_credit_control/i18n/de.po
@@ -984,10 +984,10 @@ msgstr "60 days last reminder"
#: help:res.company,credit_policy_id:0
msgid ""
"The Credit Control Policy used on partners by default. This setting can be "
-"overriden on partners or invoices."
+"overridden on partners or invoices."
msgstr ""
"The Credit Control Policy used on partners by default. This setting can be "
-"overriden on partners or invoices."
+"overridden on partners or invoices."
#. module: account_credit_control
#: field:credit.control.line,policy_level_id:0
diff --git a/account_credit_control/i18n/en.po b/account_credit_control/i18n/en.po
index a485dd7c4..4ab005650 100644
--- a/account_credit_control/i18n/en.po
+++ b/account_credit_control/i18n/en.po
@@ -984,10 +984,10 @@ msgstr "60 days last reminder"
#: help:res.company,credit_policy_id:0
msgid ""
"The Credit Control Policy used on partners by default. This setting can be "
-"overriden on partners or invoices."
+"overridden on partners or invoices."
msgstr ""
"The Credit Control Policy used on partners by default. This setting can be "
-"overriden on partners or invoices."
+"overridden on partners or invoices."
#. module: account_credit_control
#: field:credit.control.line,policy_level_id:0
@@ -1042,4 +1042,4 @@ msgstr "Currency"
#. module: account_credit_control
#: report:addons/account_credit_control/report/credit_control_summary.html.mako:211
msgid "If you have any question, do not hesitate to contact us."
-msgstr "If you have any question, do not hesitate to contact us."
\ No newline at end of file
+msgstr "If you have any question, do not hesitate to contact us."
diff --git a/account_credit_control/i18n/es.po b/account_credit_control/i18n/es.po
index 8c6ef96d9..76d47c904 100644
--- a/account_credit_control/i18n/es.po
+++ b/account_credit_control/i18n/es.po
@@ -982,10 +982,10 @@ msgstr "60 days last reminder"
#: help:res.company,credit_policy_id:0
msgid ""
"The Credit Control Policy used on partners by default. This setting can be "
-"overriden on partners or invoices."
+"overridden on partners or invoices."
msgstr ""
"The Credit Control Policy used on partners by default. This setting can be "
-"overriden on partners or invoices."
+"overridden on partners or invoices."
#. module: account_credit_control
#: field:credit.control.line,policy_level_id:0
@@ -1040,4 +1040,4 @@ msgstr "Currency"
#. module: account_credit_control
#: report:addons/account_credit_control/report/credit_control_summary.html.mako:211
msgid "If you have any question, do not hesitate to contact us."
-msgstr "If you have any question, do not hesitate to contact us."
\ No newline at end of file
+msgstr "If you have any question, do not hesitate to contact us."
diff --git a/account_credit_control/i18n/fr.po b/account_credit_control/i18n/fr.po
index 85867283b..6f0e83980 100644
--- a/account_credit_control/i18n/fr.po
+++ b/account_credit_control/i18n/fr.po
@@ -965,7 +965,7 @@ msgstr "Dernier rappel à 60 jours"
#: help:res.company,credit_policy_id:0
msgid ""
"The Credit Control Policy used on partners by default. This setting can be "
-"overriden on partners or invoices."
+"overridden on partners or invoices."
msgstr ""
"Politique de relance par défaut du client. (Ce paramétrage peut être "
"défini plus spécifiquement au niveau de la facture)."
diff --git a/account_credit_control/invoice.py b/account_credit_control/invoice.py
index c296854ea..078df90f4 100644
--- a/account_credit_control/invoice.py
+++ b/account_credit_control/invoice.py
@@ -18,7 +18,7 @@
# along with this program. If not, see .
#
##############################################################################
-from openerp.osv import orm
+from openerp.osv import orm, fields
from openerp.tools.translate import _
@@ -26,7 +26,36 @@ class AccountInvoice(orm.Model):
"""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 policy are not copied"""
+ if default is None:
+ default = {}
+ else:
+ 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:
diff --git a/account_credit_control/line.py b/account_credit_control/line.py
index d450e0697..ffb940c98 100644
--- a/account_credit_control/line.py
+++ b/account_credit_control/line.py
@@ -38,9 +38,11 @@ class CreditControlLine(orm.Model):
_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 @@ class CreditControlLine(orm.Model):
string='Level',
store=True,
readonly=True),
+ 'manually_overridden': fields.boolean('Manually overridden')
}
@@ -141,8 +144,27 @@ class CreditControlLine(orm.Model):
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 @@ class CreditControlLine(orm.Model):
for line in ml_obj.browse(cr, uid, lines, context):
open_amount = line.amount_residual_currency
+ 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)
- 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)
+ # 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
diff --git a/account_credit_control/line_view.xml b/account_credit_control/line_view.xml
index f48708d2f..370a9a136 100644
--- a/account_credit_control/line_view.xml
+++ b/account_credit_control/line_view.xml
@@ -10,6 +10,7 @@
+
@@ -32,7 +33,7 @@
search
-
+
@@ -48,6 +49,9 @@
+
@@ -89,6 +93,8 @@
+
@@ -103,6 +109,7 @@
+
diff --git a/account_credit_control/partner.py b/account_credit_control/partner.py
index 0e4605081..078e2da51 100644
--- a/account_credit_control/partner.py
+++ b/account_credit_control/partner.py
@@ -19,6 +19,7 @@
#
##############################################################################
from openerp.osv import orm, fields
+from openerp.tools.translate import _
class ResPartner(orm.Model):
@@ -28,21 +29,47 @@ class ResPartner(orm.Model):
_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:
diff --git a/account_credit_control/policy.py b/account_credit_control/policy.py
index 16af1f459..34b8bc69c 100644
--- a/account_credit_control/policy.py
+++ b/account_credit_control/policy.py
@@ -18,11 +18,11 @@
# along with this program. If not, see .
#
##############################################################################
-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,12 +42,12 @@ class CreditControlPolicy(Model):
'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'),
}
-
+
_defaults = {
'active': True,
}
@@ -103,7 +103,10 @@ class CreditControlPolicy(Model):
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 @@ class CreditControlPolicy(Model):
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_overridden IS false",
(policy_id, tuple(lines)))
res = cr.fetchall()
if res:
different_lines.update([x[0] for x in res])
return different_lines
+ def check_policy_against_account(self, cr, uid, account_id, policy_id,
+ context=None):
+ """Ensure that the policy corresponds 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(Model):
+
+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,15 +341,17 @@ class CreditControlPolicyLevel(Model):
" 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 overridden
# have to be generated again for the previous level
- " AND state not in ('draft', 'ignored'))")
+ " AND NOT manually_overridden\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)
data_dict = {'controlling_date': controlling_date,
'line_ids': tuple(lines),
'delay': level.delay_days}
-
cr.execute(sql, data_dict)
res = cr.fetchall()
if res:
@@ -346,11 +370,16 @@ class CreditControlPolicyLevel(Model):
" ON (mv_line.id = cr_line.move_line_id)\n"
" 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_overridden"
" 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 overridden
# have to be generated again for the previous level
- " AND cr_line.state not in ('draft', 'ignored')\n"
+ " AND NOT manually_overridden\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,
diff --git a/account_credit_control/run.py b/account_credit_control/run.py
index 1fc739b9f..91b4f63ba 100644
--- a/account_credit_control/run.py
+++ b/account_credit_control/run.py
@@ -81,14 +81,24 @@ class CreditControlRun(orm.Model):
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 @@ class CreditControlRun(orm.Model):
_('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 @@ class CreditControlRun(orm.Model):
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 @@ class CreditControlRun(orm.Model):
'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 @@ class CreditControlRun(orm.Model):
"""
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'),
diff --git a/account_credit_control/run_view.xml b/account_credit_control/run_view.xml
index 2ca3e6827..59c99b9a5 100644
--- a/account_credit_control/run_view.xml
+++ b/account_credit_control/run_view.xml
@@ -19,7 +19,8 @@
form