# -*- coding: utf-8 -*- ############################################################################## # # Author: Nicolas Bessi, Guewen Baconnier # Copyright 2012-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 . # ############################################################################## import logging from openerp import models, fields, api, _ logger = logging.getLogger('credit.line.control') class CreditControlLine(models.Model): """ A credit control line describes an amount due by a customer for a due date. A line is created once the due date of the payment is exceeded. It is created in "draft" and some actions are available (send by email, print, ...) """ _name = "credit.control.line" _description = "A credit control line" _rec_name = "id" _order = "date DESC" date = fields.Date(string='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(string='Due date', required=True, readonly=True, states={'draft': [('readonly', False)]}) date_entry = fields.Date(string='Entry date', related='move_line_id.date', store=True, readonly=True) date_sent = fields.Date(string='Sent date', readonly=True, states={'draft': [('readonly', False)]}) state = fields.Selection([('draft', 'Draft'), ('ignored', 'Ignored'), ('to_be_sent', 'Ready To Send'), ('sent', 'Done'), ('error', 'Error'), ('email_error', 'Emailing Error')], 'State', required=True, readonly=True, default='draft', help="Draft lines need to be triaged.\n" "Ignored lines are lines for which we do " "not want to send something.\n" "Draft and ignored lines will be " "generated again on the next run.") channel = fields.Selection([('letter', 'Letter'), ('email', 'Email')], string='Channel', required=True, readonly=True, states={'draft': [('readonly', False)]}) invoice_id = fields.Many2one('account.invoice', string='Invoice', readonly=True) partner_id = fields.Many2one('res.partner', string='Partner', required=True) amount_due = fields.Float(string='Due Amount Tax incl.', required=True, readonly=True) balance_due = fields.Float(string='Due balance', required=True, readonly=True) mail_message_id = fields.Many2one('mail.mail', string='Sent Email', readonly=True) move_line_id = fields.Many2one('account.move.line', string='Move line', required=True, readonly=True) account_id = fields.Many2one('account.account', related='move_line_id.account_id', store=True, readonly=True) currency_id = fields.Many2one('res.currency', related='move_line_id.currency_id', store=True, readonly=True) company_id = fields.Many2one('res.company', related='move_line_id.company_id', store=True, readonly=True) # we can allow a manual change of policy in draft state policy_level_id = fields.Many2one('credit.control.policy.level', string='Overdue Level', required=True, readonly=True, states={'draft': [('readonly', False)]}) policy_id = fields.Many2one('credit.control.policy', related='policy_level_id.policy_id', store=True, readonly=True) level = fields.Integer('credit.control.policy.level', related='policy_level_id.level', store=True, readonly=True) manually_overridden = fields.Boolean(string='Manually overridden') run_id = fields.Many2one(comodel_name='credit.control.run', string='Source') @api.model def _prepare_from_move_line(self, move_line, level, controlling_date, open_amount): """ Create credit control line """ data = {} data['date'] = controlling_date data['date_due'] = move_line.date_maturity data['state'] = 'draft' data['channel'] = level.channel data['invoice_id'] = (move_line.invoice.id if move_line.invoice else False) data['partner_id'] = move_line.partner_id.id data['amount_due'] = (move_line.amount_currency or move_line.debit or move_line.credit) data['balance_due'] = open_amount data['policy_level_id'] = level.id data['move_line_id'] = move_line.id return data @api.model def create_or_update_from_mv_lines(self, lines, level, controlling_date, check_tolerance=True): """ 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 recordset :param level: credit.control.policy.level record :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: recordset of created credit lines """ currency_obj = self.env['res.currency'] user = self.env.user currencies = currency_obj.search([]) tolerance = {} tolerance_base = user.company_id.credit_control_tolerance user_currency = user.company_id.currency_id for currency in currencies: tolerance[currency.id] = currency.compute(tolerance_base, user_currency) new_lines = self.browse() for move_line in lines: open_amount = move_line.amount_residual_currency cur_tolerance = tolerance.get(move_line.currency_id.id, tolerance_base) if check_tolerance and open_amount < cur_tolerance: continue vals = self._prepare_from_move_line(move_line, level, controlling_date, open_amount) line = self.create(vals) new_lines += line # 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_drafts = self.search([('move_line_id', '=', move_line.id), ('policy_level_id', '=', level.id), ('state', '=', 'draft'), ('id', '!=', line.id)]) if previous_drafts: previous_drafts.write({'state': 'ignored'}) return new_lines @api.multi def unlink(self): for line in self: if line.state != 'draft': raise api.Warning( _('You are not allowed to delete a credit control ' 'line that is not in draft state.') ) return super(CreditControlLine, self).unlink()