diff --git a/account_credit_control/__openerp__.py b/account_credit_control/__openerp__.py index ef5369f00..5fd55c159 100644 --- a/account_credit_control/__openerp__.py +++ b/account_credit_control/__openerp__.py @@ -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/line.py b/account_credit_control/line.py index d450e0697..e2c63cf78 100644 --- a/account_credit_control/line.py +++ b/account_credit_control/line.py @@ -116,6 +116,7 @@ class CreditControlLine(orm.Model): string='Level', store=True, readonly=True), + 'manually_overriden': fields.boolean('Manually overriden') } @@ -141,8 +142,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 +184,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), + ('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) return line_ids diff --git a/account_credit_control/policy.py b/account_credit_control/policy.py index 1940ea672..5e36c6bce 100644 --- a/account_credit_control/policy.py +++ b/account_credit_control/policy.py @@ -319,8 +319,10 @@ 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 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'))") sql += " AND" sql += self._get_sql_date_boundary_for_computation_mode(cr, uid, level, controlling_date, context) @@ -346,11 +348,14 @@ class CreditControlPolicyLevel(Model): " 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" # 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, diff --git a/account_credit_control/wizard/__init__.py b/account_credit_control/wizard/__init__.py index ca0a1758a..f12dea14d 100644 --- a/account_credit_control/wizard/__init__.py +++ b/account_credit_control/wizard/__init__.py @@ -22,3 +22,4 @@ from . import credit_control_emailer from . import credit_control_marker from . import credit_control_printer from . import credit_control_communication +from . import credit_control_policy_changer diff --git a/account_credit_control/wizard/credit_control_policy_changer.py b/account_credit_control/wizard/credit_control_policy_changer.py new file mode 100644 index 000000000..4876b9df7 --- /dev/null +++ b/account_credit_control/wizard/credit_control_policy_changer.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Author: Nicolas Bessi +# Copyright 2014 Camptocamp SA +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## +from itertools import chain +from openerp.osv import orm, fields + + +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', + required=True), + '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): + 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): + 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 _mark_as_overriden(self, cr, uid, move_lines, context=None): + credit_model = self.pool['credit.control.line'] + domain = [('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_new_policy(self, cr, uid, wizard_id, context=None): + 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._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 = 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 + ) + 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 diff --git a/account_credit_control/wizard/credit_control_policy_changer_view.xml b/account_credit_control/wizard/credit_control_policy_changer_view.xml new file mode 100644 index 000000000..ae383d8ec --- /dev/null +++ b/account_credit_control/wizard/credit_control_policy_changer_view.xml @@ -0,0 +1,62 @@ + + + + + credit control policy form + credit.control.policy.changer + +
+ +
+
+ + + + Change current credit policy + credit.control.policy.changer + account.invoice + form + form + + new + Allows to manually change credit level + + + + + +
+