mirror of
https://github.com/OCA/account-financial-tools.git
synced 2025-02-02 12:47:26 +02:00
Migrate the main models to the new API
This commit is contained in:
@@ -18,27 +18,15 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
from openerp.osv import orm, fields
|
||||
from openerp import models, fields
|
||||
|
||||
|
||||
class AccountAccount(orm.Model):
|
||||
"""Add a link to a credit control policy on account.account"""
|
||||
class AccountAccount(models.Model):
|
||||
""" Add a link to a credit control policy on account.account """
|
||||
|
||||
_inherit = "account.account"
|
||||
|
||||
_columns = {
|
||||
'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):
|
||||
if default is None:
|
||||
default = {}
|
||||
else:
|
||||
default = default.copy()
|
||||
default['credit_control_line_ids'] = False
|
||||
return super(AccountAccount, self).copy_data(
|
||||
cr, uid, id, default=default, context=context)
|
||||
credit_control_line_ids = fields.One2many('credit.control.line',
|
||||
'account_id',
|
||||
string='Credit Lines',
|
||||
readonly=True)
|
||||
|
||||
@@ -18,24 +18,20 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
from openerp.osv import orm, fields
|
||||
from openerp import models, fields
|
||||
|
||||
|
||||
class ResCompany(orm.Model):
|
||||
"""Add credit control parameters"""
|
||||
class ResCompany(models.Model):
|
||||
""" Add credit control parameters """
|
||||
_inherit = 'res.company'
|
||||
|
||||
_columns = {
|
||||
'credit_control_tolerance': fields.float('Credit Control Tolerance'),
|
||||
# This is not a property on the partner because we cannot search
|
||||
# on fields.property (subclass fields.function).
|
||||
'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 overridden"
|
||||
" on partners or invoices.")
|
||||
),
|
||||
}
|
||||
|
||||
_defaults = {"credit_control_tolerance": 0.1}
|
||||
credit_control_tolerance = fields.Float(string='Credit Control Tolerance',
|
||||
default=0.1)
|
||||
# This is not a property on the partner because we cannot search
|
||||
# on fields.property (subclass fields.function).
|
||||
credit_policy_id = fields.Many2one('credit.control.policy',
|
||||
string='Credit Control Policy',
|
||||
help="The Credit Control Policy used "
|
||||
"on partners by default. "
|
||||
"This setting can be overridden"
|
||||
" on partners or invoices.")
|
||||
|
||||
@@ -20,14 +20,14 @@
|
||||
##############################################################################
|
||||
import logging
|
||||
|
||||
from openerp.osv import orm, fields
|
||||
from openerp import models, fields, api
|
||||
from openerp.tools.translate import _
|
||||
|
||||
logger = logging.getLogger('credit.line.control')
|
||||
|
||||
|
||||
class CreditControlLine(orm.Model):
|
||||
"""A credit control line describes an amount due by a customer for a due date.
|
||||
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,
|
||||
@@ -38,160 +38,105 @@ class CreditControlLine(orm.Model):
|
||||
_description = "A credit control line"
|
||||
_rec_name = "id"
|
||||
_order = "date DESC"
|
||||
_columns = {
|
||||
'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',
|
||||
required=True,
|
||||
readonly=True,
|
||||
states={'draft': [('readonly', False)]}
|
||||
),
|
||||
|
||||
'date_entry': fields.related(
|
||||
'move_line_id', 'date',
|
||||
type='date',
|
||||
string='Entry date',
|
||||
store=True, readonly=True
|
||||
),
|
||||
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_sent': fields.date(
|
||||
'Sent date',
|
||||
readonly=True,
|
||||
states={'draft': [('readonly', False)]}
|
||||
),
|
||||
date_entry = fields.Date(string='Entry date',
|
||||
related='move_line_id.date',
|
||||
store=True,
|
||||
readonly=True)
|
||||
|
||||
'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,
|
||||
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.")
|
||||
),
|
||||
date_sent = fields.Date(string='Sent date',
|
||||
readonly=True,
|
||||
states={'draft': [('readonly', False)]})
|
||||
|
||||
'channel': fields.selection(
|
||||
[('letter', 'Letter'),
|
||||
('email', 'Email')],
|
||||
'Channel', required=True,
|
||||
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.")
|
||||
|
||||
'invoice_id': fields.many2one(
|
||||
'account.invoice',
|
||||
'Invoice',
|
||||
readonly=True
|
||||
),
|
||||
channel = fields.Selection([('letter', 'Letter'),
|
||||
('email', 'Email')],
|
||||
string='Channel',
|
||||
required=True,
|
||||
readonly=True,
|
||||
states={'draft': [('readonly', False)]})
|
||||
|
||||
'partner_id': fields.many2one(
|
||||
'res.partner',
|
||||
"Partner",
|
||||
required=True
|
||||
),
|
||||
invoice_id = fields.Many2one('account.invoice',
|
||||
string='Invoice',
|
||||
readonly=True)
|
||||
|
||||
'amount_due': fields.float(
|
||||
'Due Amount Tax incl.',
|
||||
required=True,
|
||||
readonly=True
|
||||
),
|
||||
partner_id = fields.Many2one('res.partner',
|
||||
string='Partner',
|
||||
required=True)
|
||||
|
||||
'balance_due': fields.float(
|
||||
'Due balance', required=True,
|
||||
readonly=True
|
||||
),
|
||||
amount_due = fields.Float(string='Due Amount Tax incl.',
|
||||
required=True, readonly=True)
|
||||
|
||||
'mail_message_id': fields.many2one(
|
||||
'mail.mail',
|
||||
'Sent Email',
|
||||
readonly=True
|
||||
),
|
||||
balance_due = fields.Float(string='Due balance', required=True,
|
||||
readonly=True)
|
||||
|
||||
'move_line_id': fields.many2one(
|
||||
'account.move.line',
|
||||
'Move line',
|
||||
required=True,
|
||||
readonly=True
|
||||
),
|
||||
mail_message_id = fields.Many2one('mail.mail', string='Sent Email',
|
||||
readonly=True)
|
||||
|
||||
'account_id': fields.related(
|
||||
'move_line_id',
|
||||
'account_id',
|
||||
type='many2one',
|
||||
relation='account.account',
|
||||
string='Account',
|
||||
store=True,
|
||||
readonly=True
|
||||
),
|
||||
move_line_id = fields.Many2one('account.move.line',
|
||||
string='Move line',
|
||||
required=True,
|
||||
readonly=True)
|
||||
|
||||
'currency_id': fields.related(
|
||||
'move_line_id',
|
||||
'currency_id',
|
||||
type='many2one',
|
||||
relation='res.currency',
|
||||
string='Currency',
|
||||
store=True,
|
||||
readonly=True
|
||||
),
|
||||
account_id = fields.Many2one(related='move_line_id.account_id',
|
||||
store=True,
|
||||
readonly=True)
|
||||
|
||||
'company_id': fields.related(
|
||||
'move_line_id', 'company_id',
|
||||
type='many2one',
|
||||
relation='res.company',
|
||||
string='Company',
|
||||
store=True, readonly=True
|
||||
),
|
||||
currency_id = fields.Many2one(related='move_line_id.currency_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',
|
||||
'Overdue Level',
|
||||
required=True,
|
||||
readonly=True,
|
||||
states={'draft': [('readonly', False)]}
|
||||
),
|
||||
company_id = fields.Many2one(related='move_line_id.company_id',
|
||||
store=True,
|
||||
readonly=True)
|
||||
|
||||
'policy_id': fields.related(
|
||||
'policy_level_id',
|
||||
'policy_id',
|
||||
type='many2one',
|
||||
relation='credit.control.policy',
|
||||
string='Policy',
|
||||
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)]})
|
||||
|
||||
'level': fields.related(
|
||||
'policy_level_id',
|
||||
'level',
|
||||
type='integer',
|
||||
relation='credit.control.policy',
|
||||
string='Level',
|
||||
store=True,
|
||||
readonly=True
|
||||
),
|
||||
policy_id = fields.Many2one(related='policy_level_id.policy_id',
|
||||
store=True,
|
||||
readonly=True)
|
||||
|
||||
'manually_overridden': fields.boolean('Manually overridden')
|
||||
}
|
||||
level = fields.Integer(related='policy_level_id.level',
|
||||
store=True,
|
||||
readonly=True)
|
||||
|
||||
_defaults = {'state': 'draft'}
|
||||
manually_overridden = fields.Boolean(string='Manually overridden')
|
||||
|
||||
def _prepare_from_move_line(self, cr, uid, move_line,
|
||||
level, controlling_date, open_amount,
|
||||
context=None):
|
||||
"""Create credit control line"""
|
||||
@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
|
||||
@@ -208,17 +153,17 @@ class CreditControlLine(orm.Model):
|
||||
data['move_line_id'] = move_line.id
|
||||
return data
|
||||
|
||||
def create_or_update_from_mv_lines(self, cr, uid, ids, lines,
|
||||
level_id, controlling_date,
|
||||
check_tolerance=True, context=None):
|
||||
"""Create or update line based on levels
|
||||
@api.model
|
||||
def create_or_update_from_mv_lines(self, lines, level_id, 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 list
|
||||
:param lines: move.line id recordset
|
||||
:param level_id: credit.control.policy.level id
|
||||
:param controlling_date: date string of the credit controlling date.
|
||||
Generally it should be the same
|
||||
@@ -228,65 +173,54 @@ class CreditControlLine(orm.Model):
|
||||
is smaller than company defined
|
||||
tolerance
|
||||
|
||||
:returns: list of created credit line ids
|
||||
:returns: recordset of created credit lines
|
||||
"""
|
||||
currency_obj = self.pool.get('res.currency')
|
||||
level_obj = self.pool.get('credit.control.policy.level')
|
||||
ml_obj = self.pool.get('account.move.line')
|
||||
user = self.pool.get('res.users').browse(cr, uid, uid)
|
||||
currency_ids = currency_obj.search(cr, uid, [], context=context)
|
||||
currency_obj = self.env['res.currency']
|
||||
level_obj = self.env['credit.control.policy.level']
|
||||
user = self.env.user
|
||||
currencies = currency_obj.search([])
|
||||
|
||||
tolerance = {}
|
||||
tolerance_base = user.company_id.credit_control_tolerance
|
||||
for c_id in currency_ids:
|
||||
tolerance[c_id] = currency_obj.compute(
|
||||
cr, uid,
|
||||
c_id,
|
||||
user.company_id.currency_id.id,
|
||||
tolerance_base,
|
||||
context=context)
|
||||
user_currency = user.company_id.currency_id
|
||||
for currency in currencies:
|
||||
tolerance[currency.id] = currency.compute(tolerance_base,
|
||||
user_currency)
|
||||
|
||||
level = level_obj.browse(cr, uid, level_id, context)
|
||||
line_ids = []
|
||||
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)
|
||||
level = level_obj.browse(level_id)
|
||||
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(cr, uid,
|
||||
line,
|
||||
vals = self._prepare_from_move_line(move_line,
|
||||
level,
|
||||
controlling_date,
|
||||
open_amount,
|
||||
context=context)
|
||||
line_id = self.create(cr, uid, vals, context=context)
|
||||
line_ids.append(line_id)
|
||||
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_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)
|
||||
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 line_ids
|
||||
return new_lines
|
||||
|
||||
def unlink(self, cr, uid, ids, context=None, check=True):
|
||||
for line in self.browse(cr, uid, ids, context=context):
|
||||
@api.multi
|
||||
def unlink(self):
|
||||
for line in self:
|
||||
if line.state != 'draft':
|
||||
raise orm.except_orm(
|
||||
_('Error !'),
|
||||
raise api.Warning(
|
||||
_('You are not allowed to delete a credit control '
|
||||
'line that is not in draft state.')
|
||||
)
|
||||
|
||||
return super(CreditControlLine, self).unlink(cr, uid, ids,
|
||||
context=context)
|
||||
return super(CreditControlLine, self).unlink()
|
||||
|
||||
@@ -18,14 +18,12 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
from openerp.osv import orm, fields
|
||||
from openerp import models, fields
|
||||
|
||||
|
||||
class Mail(orm.Model):
|
||||
class Mail(models.Model):
|
||||
_inherit = 'mail.mail'
|
||||
|
||||
# use HTML fields instead of text
|
||||
_columns = {
|
||||
'body_html': fields.html('Rich-text Contents',
|
||||
help="Rich-text/HTML message"),
|
||||
}
|
||||
body_html = fields.Html('Rich-text Contents',
|
||||
help="Rich-text/HTML message")
|
||||
|
||||
@@ -18,61 +18,41 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
from openerp.osv import orm, fields
|
||||
from openerp import models, fields, api
|
||||
|
||||
|
||||
class ResPartner(orm.Model):
|
||||
"""Add a settings on the credit control policy to use on the partners,
|
||||
and links to the credit control lines."""
|
||||
class ResPartner(models.Model):
|
||||
""" Add a settings on the credit control policy to use on the partners,
|
||||
and links to the credit control lines.
|
||||
"""
|
||||
|
||||
_inherit = "res.partner"
|
||||
|
||||
_columns = {
|
||||
'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
|
||||
)
|
||||
}
|
||||
credit_policy_id = fields.Many2one(
|
||||
'credit.control.policy',
|
||||
string='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
|
||||
@api.constrains('credit_policy_id')
|
||||
def _check_credit_policy(self):
|
||||
""" Ensure that policy on partner are limited to the account policy """
|
||||
for partner in self:
|
||||
if (not partner.property_account_receivable or
|
||||
not partner.credit_policy_id):
|
||||
continue
|
||||
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:
|
||||
default = default.copy()
|
||||
default['credit_control_line_ids'] = False
|
||||
return super(ResPartner, self).copy_data(
|
||||
cr, uid, id, default=default, context=context)
|
||||
policy = partner.credit_policy_id
|
||||
try:
|
||||
policy.check_policy_against_account(account.id)
|
||||
except api.Warning as err:
|
||||
# constrains should raise ValidationError exceptions
|
||||
raise api.ValidationError(err)
|
||||
|
||||
@@ -18,61 +18,47 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
from openerp.osv import orm, fields
|
||||
from openerp import models, fields, api
|
||||
from openerp.tools.translate import _
|
||||
|
||||
|
||||
class CreditControlPolicy(orm.Model):
|
||||
"""Define a policy of reminder"""
|
||||
class CreditControlPolicy(models.Model):
|
||||
""" Define a policy of reminder """
|
||||
|
||||
_name = "credit.control.policy"
|
||||
_description = """Define a reminder policy"""
|
||||
_columns = {
|
||||
'name': fields.char(
|
||||
'Name',
|
||||
required=True,
|
||||
size=128
|
||||
),
|
||||
|
||||
'level_ids': fields.one2many(
|
||||
'credit.control.policy.level',
|
||||
'policy_id',
|
||||
'Policy Levels'
|
||||
),
|
||||
name = fields.Char('Name', required=True)
|
||||
level_ids = fields.One2many('credit.control.policy.level',
|
||||
'policy_id',
|
||||
string='Policy Levels')
|
||||
do_nothing = fields.Boolean('Do nothing',
|
||||
help='For policies which should not '
|
||||
'generate lines or are obsolete')
|
||||
company_id = fields.Many2one('res.company', string='Company')
|
||||
account_ids = fields.Many2many(
|
||||
'account.account',
|
||||
string='Accounts',
|
||||
required=True,
|
||||
domain="[('type', '=', 'receivable')]",
|
||||
help="This policy will be active only"
|
||||
" for the selected accounts",
|
||||
)
|
||||
active = fields.Boolean('Active', default=True)
|
||||
|
||||
'do_nothing': fields.boolean(
|
||||
'Do nothing',
|
||||
help='For policies which should not '
|
||||
'generate lines or are obsolete'
|
||||
),
|
||||
|
||||
'company_id': fields.many2one('res.company', 'Company'),
|
||||
|
||||
'account_ids': fields.many2many(
|
||||
'account.account',
|
||||
string='Accounts',
|
||||
required=True,
|
||||
domain="[('type', '=', 'receivable')]",
|
||||
help="This policy will be active only"
|
||||
" for the selected accounts"
|
||||
),
|
||||
'active': fields.boolean('Active'),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'active': True,
|
||||
}
|
||||
|
||||
def _move_lines_domain(self, cr, uid, policy, controlling_date,
|
||||
context=None):
|
||||
"""Build the default domain for searching move lines"""
|
||||
account_ids = [a.id for a in policy.account_ids]
|
||||
@api.multi
|
||||
def _move_lines_domain(self, controlling_date):
|
||||
""" Build the default domain for searching move lines """
|
||||
self.ensure_one()
|
||||
account_ids = [a.id for a in self.account_ids]
|
||||
return [('account_id', 'in', account_ids),
|
||||
('date_maturity', '<=', controlling_date),
|
||||
('reconcile_id', '=', False),
|
||||
('partner_id', '!=', False)]
|
||||
|
||||
def _due_move_lines(self, cr, uid, policy, controlling_date, context=None):
|
||||
@api.multi
|
||||
@api.returns('account.move.line')
|
||||
def _due_move_lines(self, controlling_date):
|
||||
""" Get the due move lines for the policy of the company.
|
||||
|
||||
The set of ids will be reduced and extended according
|
||||
@@ -83,18 +69,17 @@ class CreditControlPolicy(orm.Model):
|
||||
Assume that only the receivable lines have a maturity date and that
|
||||
accounts used in the policy are reconcilable.
|
||||
"""
|
||||
move_l_obj = self.pool.get('account.move.line')
|
||||
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
|
||||
if user.company_id.credit_policy_id.id != policy.id:
|
||||
return set()
|
||||
self.ensure_one()
|
||||
move_l_obj = self.env['account.move.line']
|
||||
user = self.env.user
|
||||
if user.company_id.credit_policy_id.id != self.id:
|
||||
return move_l_obj.browse()
|
||||
domain_line = self._move_lines_domain(controlling_date)
|
||||
return move_l_obj.search(domain_line)
|
||||
|
||||
domain_line = self._move_lines_domain(cr, uid, policy,
|
||||
controlling_date,
|
||||
context=context)
|
||||
return set(move_l_obj.search(cr, uid, domain_line, context=context))
|
||||
|
||||
def _move_lines_subset(self, cr, uid, policy, controlling_date,
|
||||
model, move_relation_field, context=None):
|
||||
@api.multi
|
||||
@api.returns('account.move.line')
|
||||
def _move_lines_subset(self, controlling_date, model, move_relation_field):
|
||||
""" Get the move lines related to one model for a policy.
|
||||
|
||||
Do not use direct SQL in order to respect security rules.
|
||||
@@ -104,274 +89,243 @@ class CreditControlPolicy(orm.Model):
|
||||
|
||||
The policy relation field must be named credit_policy_id.
|
||||
|
||||
:param browse_record policy: policy
|
||||
:param str controlling_date: date of credit control
|
||||
:param str model: name of the model where is defined a credit_policy_id
|
||||
:param str move_relation_field: name of the field in account.move.line
|
||||
which is a many2one to `model`
|
||||
:return: set of ids to add in the process, set of ids to remove from
|
||||
:return: recordset to add in the process, recordset to remove from
|
||||
the process
|
||||
"""
|
||||
self.ensure_one()
|
||||
# MARK possible place for a good optimisation
|
||||
my_obj = self.pool.get(model)
|
||||
move_l_obj = self.pool.get('account.move.line')
|
||||
my_obj = self.env[model]
|
||||
move_l_obj = self.env['account.move.line']
|
||||
default_domain = self._move_lines_domain(controlling_date)
|
||||
|
||||
default_domain = self._move_lines_domain(cr, uid,
|
||||
policy,
|
||||
controlling_date,
|
||||
context=context)
|
||||
to_add_ids = set()
|
||||
to_remove_ids = set()
|
||||
to_add = move_l_obj.browse()
|
||||
to_remove = move_l_obj.browse()
|
||||
|
||||
# The lines which are linked to this policy have to be included in the
|
||||
# run for this policy.
|
||||
# If another object override the credit_policy_id (ie. invoice after
|
||||
add_obj_ids = my_obj.search(
|
||||
cr, uid,
|
||||
[('credit_policy_id', '=', policy.id)],
|
||||
context=context)
|
||||
if add_obj_ids:
|
||||
add_objs = my_obj.search([('credit_policy_id', '=', self.id)])
|
||||
if add_objs:
|
||||
domain = list(default_domain)
|
||||
domain.append((move_relation_field, 'in', add_obj_ids))
|
||||
to_add_ids = set(move_l_obj.search(cr, uid, domain,
|
||||
context=context))
|
||||
domain.append((move_relation_field, 'in', add_objs))
|
||||
to_add = move_l_obj.search(domain)
|
||||
|
||||
# The lines which are linked to another policy do not have to be
|
||||
# included in the run for this policy.
|
||||
neg_obj_ids = my_obj.search(
|
||||
cr, uid,
|
||||
[('credit_policy_id', '!=', policy.id),
|
||||
('credit_policy_id', '!=', False)],
|
||||
context=context)
|
||||
if neg_obj_ids:
|
||||
neg_objs = my_obj.search([('credit_policy_id', '!=', self.id),
|
||||
('credit_policy_id', '!=', False)])
|
||||
if neg_objs:
|
||||
domain = list(default_domain)
|
||||
domain.append((move_relation_field, 'in', neg_obj_ids))
|
||||
to_remove_ids = set(move_l_obj.search(cr, uid, domain,
|
||||
context=context))
|
||||
return to_add_ids, to_remove_ids
|
||||
domain.append((move_relation_field, 'in', neg_objs))
|
||||
to_remove = move_l_obj.search(domain)
|
||||
return to_add, to_remove
|
||||
|
||||
def _get_partner_related_lines(self, cr, uid, policy, controlling_date,
|
||||
context=None):
|
||||
@api.multi
|
||||
@api.returns('account.move.line')
|
||||
def _get_partner_related_lines(self, controlling_date):
|
||||
""" Get the move lines for a policy related to a partner.
|
||||
|
||||
:param browse_record policy: policy
|
||||
:param str controlling_date: date of credit control
|
||||
:param str model: name of the model where is defined a credit_policy_id
|
||||
:param str move_relation_field: name of the field in account.move.line
|
||||
which is a many2one to `model`
|
||||
:return: set of ids to add in the process, set of ids to remove from
|
||||
:return: recordset to add in the process, recordset to remove from
|
||||
the process
|
||||
"""
|
||||
return self._move_lines_subset(cr, uid, policy, controlling_date,
|
||||
'res.partner', 'partner_id',
|
||||
context=context)
|
||||
return self._move_lines_subset(controlling_date, 'res.partner',
|
||||
'partner_id')
|
||||
|
||||
def _get_invoice_related_lines(self, cr, uid, policy, controlling_date,
|
||||
context=None):
|
||||
@api.multi
|
||||
@api.returns('account.move.line')
|
||||
def _get_invoice_related_lines(self, controlling_date):
|
||||
""" Get the move lines for a policy related to an invoice.
|
||||
|
||||
:param browse_record policy: policy
|
||||
:param str controlling_date: date of credit control
|
||||
:param str model: name of the model where is defined a credit_policy_id
|
||||
:param str move_relation_field: name of the field in account.move.line
|
||||
which is a many2one to `model`
|
||||
:return: set of ids to add in the process, set of ids to remove from
|
||||
:return: recordset to add in the process, recordset to remove from
|
||||
the process
|
||||
"""
|
||||
return self._move_lines_subset(cr, uid, policy, controlling_date,
|
||||
'account.invoice', 'invoice',
|
||||
context=context)
|
||||
return self._move_lines_subset(controlling_date, 'account.invoice',
|
||||
'invoice')
|
||||
|
||||
def _get_move_lines_to_process(self, cr, uid, policy_id, controlling_date,
|
||||
context=None):
|
||||
"""Build a list of move lines ids to include in a run
|
||||
@api.multi
|
||||
@api.returns('account.move.line')
|
||||
def _get_move_lines_to_process(self, controlling_date):
|
||||
""" Build a list of move lines ids to include in a run
|
||||
for a policy at a given date.
|
||||
|
||||
:param int/long policy: id of the policy
|
||||
:param str controlling_date: date of credit control
|
||||
:return: set of ids to include in the run
|
||||
"""
|
||||
assert not (isinstance(policy_id, list) and len(policy_id) > 1), \
|
||||
"policy_id: only one id expected"
|
||||
if isinstance(policy_id, list):
|
||||
policy_id = policy_id[0]
|
||||
|
||||
policy = self.browse(cr, uid, policy_id, context=context)
|
||||
:return: recordset to include in the run
|
||||
"""
|
||||
self.ensure_one()
|
||||
# there is a priority between the lines, depicted by the calls below
|
||||
# warning, side effect method called on lines
|
||||
lines = self._due_move_lines(cr, uid, policy, controlling_date,
|
||||
context=context)
|
||||
add_ids, remove_ids = self._get_partner_related_lines(cr, uid, policy,
|
||||
controlling_date,
|
||||
context=context)
|
||||
lines = lines.union(add_ids).difference(remove_ids)
|
||||
add_ids, remove_ids = self._get_invoice_related_lines(cr, uid, policy,
|
||||
controlling_date,
|
||||
context=context)
|
||||
lines = lines.union(add_ids).difference(remove_ids)
|
||||
lines = self._due_move_lines(controlling_date)
|
||||
to_add, to_remove = self._get_partner_related_lines(controlling_date)
|
||||
lines = (lines | to_add) - to_remove
|
||||
to_add, to_remove = self._get_invoice_related_lines(controlling_date)
|
||||
lines = (lines | to_add) - to_remove
|
||||
return lines
|
||||
|
||||
def _lines_different_policy(self, cr, uid, policy_id, lines, context=None):
|
||||
""" Return a set of move lines ids for which there is an existing credit line
|
||||
but with a different policy.
|
||||
@api.multi
|
||||
@api.returns('account.move.line')
|
||||
def _lines_different_policy(self, lines):
|
||||
""" Return a set of move lines ids for which there is an
|
||||
existing credit line but with a different policy.
|
||||
"""
|
||||
different_lines = set()
|
||||
self.ensure_one()
|
||||
move_line_obj = self.env['account.move.line']
|
||||
different_lines = move_line_obj.browse()
|
||||
if not lines:
|
||||
return different_lines
|
||||
assert not (isinstance(policy_id, list) and len(policy_id) > 1), \
|
||||
"policy_id: only one id expected"
|
||||
if isinstance(policy_id, list):
|
||||
policy_id = policy_id[0]
|
||||
cr = self._cr
|
||||
cr.execute("SELECT move_line_id FROM credit_control_line"
|
||||
" WHERE policy_id != %s and move_line_id in %s"
|
||||
" AND manually_overridden IS false",
|
||||
(policy_id, tuple(lines)))
|
||||
(self.id, tuple(line.id for line in lines)))
|
||||
res = cr.fetchall()
|
||||
if res:
|
||||
different_lines.update([x[0] for x in res])
|
||||
return move_line_obj.browse([row[0] for row 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)
|
||||
@api.multi
|
||||
def check_policy_against_account(self, account_id):
|
||||
""" Ensure that the policy corresponds to account relation """
|
||||
account = self.env['account.account'].browse(account_id)
|
||||
policies = self.search([])
|
||||
allowed = [x for x in policies
|
||||
if account in x.account_ids or x.do_nothing]
|
||||
if policy not in allowed:
|
||||
if self not in allowed:
|
||||
allowed_names = u"\n".join(x.name for x in allowed)
|
||||
raise orm.except_orm(
|
||||
raise api.Warning(
|
||||
_('You can only use a policy set on '
|
||||
'account %s') % account.name,
|
||||
_("Please choose one of the following "
|
||||
"policies:\n %s") % allowed_names
|
||||
'account %s.\n'
|
||||
'Please choose one of the following '
|
||||
'policies:\n %s') % (account.name, allowed_names)
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
class CreditControlPolicyLevel(orm.Model):
|
||||
class CreditControlPolicyLevel(models.Model):
|
||||
"""Define a policy level. A level allows to determine if
|
||||
a move line is due and the level of overdue of the line"""
|
||||
|
||||
_name = "credit.control.policy.level"
|
||||
_order = 'level'
|
||||
_description = """A credit control policy level"""
|
||||
_columns = {
|
||||
'policy_id': fields.many2one('credit.control.policy',
|
||||
'Related Policy', required=True),
|
||||
'name': fields.char('Name', size=128, required=True,
|
||||
translate=True),
|
||||
'level': fields.integer('Level', required=True),
|
||||
|
||||
'computation_mode': fields.selection(
|
||||
[('net_days', 'Due Date'),
|
||||
('end_of_month', 'Due Date, End Of Month'),
|
||||
('previous_date', 'Previous Reminder')],
|
||||
'Compute Mode',
|
||||
required=True
|
||||
),
|
||||
|
||||
'delay_days': fields.integer('Delay (in days)', required='True'),
|
||||
'email_template_id': fields.many2one('email.template',
|
||||
'Email Template',
|
||||
required=True),
|
||||
'channel': fields.selection([('letter', 'Letter'),
|
||||
('email', 'Email')],
|
||||
'Channel', required=True),
|
||||
'custom_text': fields.text('Custom Message',
|
||||
required=True,
|
||||
translate=True),
|
||||
'custom_mail_text': fields.text('Custom Mail Message',
|
||||
required=True, translate=True),
|
||||
|
||||
}
|
||||
|
||||
def _check_level_mode(self, cr, uid, rids, context=None):
|
||||
""" The smallest level of a policy cannot be computed on the
|
||||
"previous_date". Return False if this happens. """
|
||||
if isinstance(rids, (int, long)):
|
||||
rids = [rids]
|
||||
for level in self.browse(cr, uid, rids, context):
|
||||
smallest_level_id = self.search(
|
||||
cr, uid,
|
||||
[('policy_id', '=', level.policy_id.id)],
|
||||
order='level asc', limit=1, context=context)
|
||||
smallest_level = self.browse(cr, uid, smallest_level_id[0],
|
||||
context)
|
||||
if smallest_level.computation_mode == 'previous_date':
|
||||
return False
|
||||
return True
|
||||
name = fields.Char(string='Name', required=True, translate=True)
|
||||
policy_id = fields.Many2one('credit.control.policy',
|
||||
string='Related Policy',
|
||||
required=True)
|
||||
level = fields.Integer(string='Level', required=True)
|
||||
computation_mode = fields.Selection(
|
||||
[('net_days', 'Due Date'),
|
||||
('end_of_month', 'Due Date, End Of Month'),
|
||||
('previous_date', 'Previous Reminder')],
|
||||
string='Compute Mode',
|
||||
required=True
|
||||
)
|
||||
delay_days = fields.Integer(string='Delay (in days)', required=True)
|
||||
email_template_id = fields.Many2one('email.template',
|
||||
string='Email Template',
|
||||
required=True)
|
||||
channel = fields.Selection([('letter', 'Letter'),
|
||||
('email', 'Email')],
|
||||
string='Channel',
|
||||
required=True)
|
||||
custom_text = fields.Text(string='Custom Message',
|
||||
required=True,
|
||||
translate=True)
|
||||
custom_mail_text = fields.Text(string='Custom Mail Message',
|
||||
required=True, translate=True)
|
||||
|
||||
_sql_constraint = [('unique level',
|
||||
'UNIQUE (policy_id, level)',
|
||||
'Level must be unique per policy')]
|
||||
|
||||
_constraints = [(_check_level_mode,
|
||||
'The smallest level can not be of type Previous Reminder',
|
||||
['level'])]
|
||||
@api.one
|
||||
@api.constrains('level', 'computation_mode')
|
||||
def _check_level_mode(self):
|
||||
""" The smallest level of a policy cannot be computed on the
|
||||
"previous_date".
|
||||
"""
|
||||
smallest_level = self.search([('policy_id', '=', self.policy_id.id)],
|
||||
order='level asc', limit=1)
|
||||
if smallest_level.computation_mode == 'previous_date':
|
||||
return api.ValidationError(_('The smallest level can not be of '
|
||||
'type Previous Reminder'))
|
||||
|
||||
def _previous_level(self, cr, uid, policy_level, context=None):
|
||||
@api.multi
|
||||
def _previous_level(self):
|
||||
""" For one policy level, returns the id of the previous level
|
||||
|
||||
If there is no previous level, it returns None, it means that's the
|
||||
first policy level
|
||||
|
||||
:param browse_record policy_level: policy level
|
||||
:return: previous level id or None if there is no previous level
|
||||
:return: previous level or None if there is no previous level
|
||||
"""
|
||||
previous_level_ids = self.search(
|
||||
cr,
|
||||
uid,
|
||||
[('policy_id', '=', policy_level.policy_id.id),
|
||||
('level', '<', policy_level.level)],
|
||||
order='level desc',
|
||||
limit=1,
|
||||
context=context)
|
||||
return previous_level_ids[0] if previous_level_ids else None
|
||||
self.ensure_one()
|
||||
previous_levels = self.search([('policy_id', '=', self.policy_id.id),
|
||||
('level', '<', self.level)],
|
||||
order='level desc',
|
||||
limit=1)
|
||||
if not previous_levels:
|
||||
return None
|
||||
return previous_levels
|
||||
|
||||
# ----- sql time related methods ---------
|
||||
|
||||
def _net_days_get_boundary(self):
|
||||
@staticmethod
|
||||
def _net_days_get_boundary():
|
||||
return (" (mv_line.date_maturity + %(delay)s)::date <= "
|
||||
"date(%(controlling_date)s)")
|
||||
|
||||
def _end_of_month_get_boundary(self):
|
||||
@staticmethod
|
||||
def _end_of_month_get_boundary():
|
||||
return ("(date_trunc('MONTH', (mv_line.date_maturity + %(delay)s))+"
|
||||
"INTERVAL '1 MONTH - 1 day')::date"
|
||||
"<= date(%(controlling_date)s)")
|
||||
|
||||
def _previous_date_get_boundary(self):
|
||||
@staticmethod
|
||||
def _previous_date_get_boundary():
|
||||
return "(cr_line.date + %(delay)s)::date <= date(%(controlling_date)s)"
|
||||
|
||||
def _get_sql_date_boundary_for_computation_mode(self, cr, uid, level,
|
||||
controlling_date,
|
||||
context=None):
|
||||
"""Return a where clauses statement for the given
|
||||
controlling date and computation mode of the level"""
|
||||
fname = "_%s_get_boundary" % (level.computation_mode,)
|
||||
@api.multi
|
||||
def _get_sql_date_boundary_for_computation_mode(self, controlling_date):
|
||||
""" Return a where clauses statement for the given controlling
|
||||
date and computation mode of the level
|
||||
"""
|
||||
self.ensure_one()
|
||||
fname = "_%s_get_boundary" % (self.computation_mode, )
|
||||
if hasattr(self, fname):
|
||||
fnc = getattr(self, fname)
|
||||
return fnc()
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
_('Can not get function for computation mode: '
|
||||
'%s is not implemented') % (fname,)
|
||||
'%s is not implemented') % (fname, )
|
||||
)
|
||||
|
||||
# -----------------------------------------
|
||||
|
||||
def _get_first_level_move_line_ids(self, cr, uid, level, controlling_date,
|
||||
lines, context=None):
|
||||
"""Retrieve all the move lines that are linked to a first level.
|
||||
We use Raw SQL for performance. Security rule where applied in
|
||||
policy object when the first set of lines were retrieved"""
|
||||
level_lines = set()
|
||||
@api.multi
|
||||
@api.returns('account.move.line')
|
||||
def _get_first_level_move_lines(self, controlling_date, lines):
|
||||
""" Retrieve all the move lines that are linked to a first level.
|
||||
We use Raw SQL for performance. Security rule where applied in
|
||||
policy object when the first set of lines were retrieved
|
||||
"""
|
||||
self.ensure_one()
|
||||
move_line_obj = self.env['account.move.line']
|
||||
if not lines:
|
||||
return level_lines
|
||||
return move_line_obj.browse()
|
||||
cr = self._cr
|
||||
sql = ("SELECT DISTINCT mv_line.id\n"
|
||||
" FROM account_move_line mv_line\n"
|
||||
" WHERE mv_line.id in %(line_ids)s\n"
|
||||
@@ -385,26 +339,28 @@ class CreditControlPolicyLevel(orm.Model):
|
||||
" 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
|
||||
)
|
||||
_get_sql_date_part = self._get_sql_date_boundary_for_computation_mode
|
||||
sql += _get_sql_date_part(controlling_date)
|
||||
data_dict = {'controlling_date': controlling_date,
|
||||
'line_ids': tuple(lines),
|
||||
'delay': level.delay_days}
|
||||
'line_ids': tuple(line.id for line in lines),
|
||||
'delay': self.delay_days}
|
||||
print cr.mogrify(sql, data_dict)
|
||||
cr.execute(sql, data_dict)
|
||||
res = cr.fetchall()
|
||||
if res:
|
||||
level_lines.update([x[0] for x in res])
|
||||
return level_lines
|
||||
return move_line_obj.browse([row[0] for row in res])
|
||||
return move_line_obj.browse()
|
||||
|
||||
def _get_other_level_move_line_ids(self, cr, uid, level, controlling_date,
|
||||
lines, context=None):
|
||||
@api.multi
|
||||
@api.returns('account.move.line')
|
||||
def _get_other_level_move_lines(self, controlling_date, lines):
|
||||
""" Retrieve the move lines for other levels than first level.
|
||||
"""
|
||||
level_lines = set()
|
||||
self.ensure_one()
|
||||
move_line_obj = self.env['account.move.line']
|
||||
if not lines:
|
||||
return level_lines
|
||||
return move_line_obj.browse()
|
||||
cr = self._cr
|
||||
sql = ("SELECT mv_line.id\n"
|
||||
" FROM account_move_line mv_line\n"
|
||||
" JOIN credit_control_line cr_line\n"
|
||||
@@ -424,41 +380,31 @@ class CreditControlPolicyLevel(orm.Model):
|
||||
" 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,
|
||||
controlling_date,
|
||||
context
|
||||
)
|
||||
previous_level_id = self._previous_level(cr, uid, level,
|
||||
context=context)
|
||||
previous_level = self.browse(cr, uid, previous_level_id,
|
||||
context=context)
|
||||
_get_sql_date_part = self._get_sql_date_boundary_for_computation_mode
|
||||
sql += _get_sql_date_part(controlling_date)
|
||||
previous_level = self._previous_level()
|
||||
data_dict = {'controlling_date': controlling_date,
|
||||
'line_ids': tuple(lines),
|
||||
'delay': level.delay_days,
|
||||
'line_ids': tuple(line.id for line in lines),
|
||||
'delay': self.delay_days,
|
||||
'previous_level': previous_level.level}
|
||||
|
||||
# print cr.mogrify(sql, data_dict)
|
||||
cr.execute(sql, data_dict)
|
||||
res = cr.fetchall()
|
||||
if res:
|
||||
level_lines.update([x[0] for x in res])
|
||||
return level_lines
|
||||
return move_line_obj.browse([row[0] for row in res])
|
||||
return move_line_obj.browse()
|
||||
|
||||
def get_level_lines(self, cr, uid, level_id, controlling_date, lines,
|
||||
context=None):
|
||||
"""get all move lines in entry lines that match the current level"""
|
||||
assert not (isinstance(level_id, list) and len(level_id) > 1), \
|
||||
"level_id: only one id expected"
|
||||
if isinstance(level_id, list):
|
||||
level_id = level_id[0]
|
||||
matching_lines = set()
|
||||
level = self.browse(cr, uid, level_id, context=context)
|
||||
if self._previous_level(cr, uid, level, context=context) is None:
|
||||
method = self._get_first_level_move_line_ids
|
||||
@api.multi
|
||||
@api.returns('account.move.line')
|
||||
def get_level_lines(self, controlling_date, lines):
|
||||
""" get all move lines in entry lines that match the current level """
|
||||
self.ensure_one()
|
||||
move_line_obj = self.env['account.move.line']
|
||||
matching_lines = move_line_obj.browse()
|
||||
if self._previous_level() is None:
|
||||
method = self._get_first_level_move_lines
|
||||
else:
|
||||
method = self._get_other_level_move_line_ids
|
||||
matching_lines.update(method(cr, uid, level,
|
||||
controlling_date, lines,
|
||||
context=context))
|
||||
method = self._get_other_level_move_lines
|
||||
matching_lines += method(controlling_date, lines)
|
||||
return matching_lines
|
||||
|
||||
@@ -20,143 +20,113 @@
|
||||
##############################################################################
|
||||
import logging
|
||||
|
||||
from openerp.osv import orm, fields
|
||||
from openerp import models, fields, api
|
||||
from openerp.tools.translate import _
|
||||
|
||||
logger = logging.getLogger('credit.control.run')
|
||||
|
||||
|
||||
class CreditControlRun(orm.Model):
|
||||
"""Credit Control run generate all credit control lines and reject"""
|
||||
class CreditControlRun(models.Model):
|
||||
""" Credit Control run generate all credit control lines and reject """
|
||||
|
||||
_name = "credit.control.run"
|
||||
_rec_name = 'date'
|
||||
_description = """Credit control line generator"""
|
||||
_columns = {
|
||||
'date': fields.date('Controlling Date', required=True),
|
||||
'policy_ids': fields.many2many(
|
||||
'credit.control.policy',
|
||||
rel="credit_run_policy_rel",
|
||||
id1='run_id', id2='policy_id',
|
||||
string='Policies',
|
||||
readonly=True,
|
||||
states={'draft': [('readonly', False)]}
|
||||
),
|
||||
_description = "Credit control line generator"
|
||||
|
||||
'report': fields.text('Report', readonly=True),
|
||||
date = fields.Date(string='Controlling Date', required=True)
|
||||
|
||||
'state': fields.selection([('draft', 'Draft'),
|
||||
('done', 'Done')],
|
||||
string='State',
|
||||
required=True,
|
||||
readonly=True),
|
||||
@api.model
|
||||
def _get_policies(self):
|
||||
return self.env['credit.control.policy'].search([])
|
||||
|
||||
'manual_ids': fields.many2many(
|
||||
'account.move.line',
|
||||
rel="credit_runreject_rel",
|
||||
string='Lines to handle manually',
|
||||
help=('If a credit control line has been generated'
|
||||
'on a policy and the policy has been changed '
|
||||
'in the meantime, it has to be handled '
|
||||
'manually'),
|
||||
readonly=True
|
||||
),
|
||||
}
|
||||
policy_ids = fields.Many2many(
|
||||
'credit.control.policy',
|
||||
rel="credit_run_policy_rel",
|
||||
id1='run_id', id2='policy_id',
|
||||
string='Policies',
|
||||
readonly=True,
|
||||
states={'draft': [('readonly', False)]},
|
||||
default=_get_policies,
|
||||
)
|
||||
report = fields.Text(string='Report', readonly=True, copy=False)
|
||||
state = fields.Selection([('draft', 'Draft'),
|
||||
('done', 'Done')],
|
||||
string='State',
|
||||
required=True,
|
||||
readonly=True,
|
||||
default='draft')
|
||||
manual_ids = fields.Many2many(
|
||||
'account.move.line',
|
||||
rel="credit_runreject_rel",
|
||||
string='Lines to handle manually',
|
||||
help='If a credit control line has been generated'
|
||||
'on a policy and the policy has been changed '
|
||||
'in the meantime, it has to be handled '
|
||||
'manually',
|
||||
readonly=True,
|
||||
copy=False,
|
||||
)
|
||||
|
||||
def copy_data(self, cr, uid, id, default=None, context=None):
|
||||
if default is None:
|
||||
default = {}
|
||||
else:
|
||||
default = default.copy()
|
||||
default.update({
|
||||
'report': False,
|
||||
'manual_ids': False,
|
||||
})
|
||||
return super(CreditControlRun, self).copy_data(
|
||||
cr, uid, id, default=default, context=context)
|
||||
|
||||
def _get_policies(self, cr, uid, context=None):
|
||||
return self.pool['credit.control.policy'].search(cr, uid, [],
|
||||
context=context)
|
||||
|
||||
_defaults = {'state': 'draft',
|
||||
'policy_ids': _get_policies}
|
||||
|
||||
def _check_run_date(self, cr, uid, ids, controlling_date, context=None):
|
||||
"""Ensure that there is no credit line in the future
|
||||
@api.multi
|
||||
def _check_run_date(self, controlling_date):
|
||||
""" Ensure that there is no credit line in the future
|
||||
using controlling_date
|
||||
|
||||
"""
|
||||
run_obj = self.pool['credit.control.run']
|
||||
runs = run_obj.search(cr, uid, [('date', '>', controlling_date)],
|
||||
order='date DESC', limit=1, context=context)
|
||||
runs = self.search([('date', '>', controlling_date)],
|
||||
order='date DESC', limit=1)
|
||||
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))
|
||||
raise api.Warning(_('A run has already been executed more '
|
||||
'recently than %s') % (runs.date))
|
||||
|
||||
line_obj = self.pool['credit.control.line']
|
||||
lines = line_obj.search(cr, uid, [('date', '>', controlling_date)],
|
||||
order='date DESC', limit=1, context=context)
|
||||
line_obj = self.env['credit.control.line']
|
||||
lines = line_obj.search([('date', '>', controlling_date)],
|
||||
order='date DESC', limit=1)
|
||||
if lines:
|
||||
line = line_obj.browse(cr, uid, lines[0], context=context)
|
||||
raise orm.except_orm(_('Error'),
|
||||
_('A credit control line more '
|
||||
'recent than %s exists at %s') %
|
||||
(controlling_date, line.date))
|
||||
return True
|
||||
raise api.Warning(_('A credit control line more '
|
||||
'recent than %s exists at %s') %
|
||||
(controlling_date, lines.date))
|
||||
|
||||
def _generate_credit_lines(self, cr, uid, run_id, context=None):
|
||||
@api.multi
|
||||
@api.returns('credit.control.line')
|
||||
def _generate_credit_lines(self):
|
||||
""" Generate credit control lines. """
|
||||
cr_line_obj = self.pool.get('credit.control.line')
|
||||
assert not (isinstance(run_id, list) and len(run_id) > 1), \
|
||||
"run_id: only one id expected"
|
||||
if isinstance(run_id, list):
|
||||
run_id = run_id[0]
|
||||
self.ensure_one()
|
||||
cr_line_obj = self.env['credit.control.line']
|
||||
move_line_obj = self.env['account.move.line']
|
||||
manually_managed_lines = move_line_obj.browse()
|
||||
self._check_run_date(self.date)
|
||||
|
||||
run = self.browse(cr, uid, run_id, context=context)
|
||||
manually_managed_lines = set() # line who changed policy
|
||||
credit_line_ids = [] # generated lines
|
||||
run._check_run_date(run.date, context=context)
|
||||
|
||||
policies = run.policy_ids
|
||||
policies = self.policy_ids
|
||||
if not policies:
|
||||
raise orm.except_orm(_('Error'),
|
||||
_('Please select a policy'))
|
||||
raise api.Warning(_('Please select a policy'))
|
||||
|
||||
report = ''
|
||||
generated_ids = []
|
||||
generated = []
|
||||
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)
|
||||
manually_managed_lines.update(manual_lines)
|
||||
policy_generated_ids = []
|
||||
lines = policy._get_move_lines_to_process(self.date)
|
||||
manual_lines = policy._lines_different_policy(lines)
|
||||
lines -= manual_lines
|
||||
manually_managed_lines += manual_lines
|
||||
policy_lines_generated = cr_line_obj.browse()
|
||||
if lines:
|
||||
# policy levels are sorted by level
|
||||
# so iteration is in the correct order
|
||||
for level in reversed(policy.level_ids):
|
||||
level_lines = level.get_level_lines(run.date, lines,
|
||||
context=context)
|
||||
policy_generated_ids += \
|
||||
level_lines = level.get_level_lines(self.date, lines)
|
||||
policy_lines_generated += \
|
||||
cr_line_obj.create_or_update_from_mv_lines(
|
||||
cr, uid,
|
||||
[],
|
||||
list(level_lines),
|
||||
level_lines,
|
||||
level.id,
|
||||
run.date,
|
||||
context=context
|
||||
self.date,
|
||||
)
|
||||
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))
|
||||
credit_line_ids += policy_generated_ids
|
||||
generated += policy_lines_generated
|
||||
if policy_lines_generated:
|
||||
report += (_("Policy \"%s\" has generated %d Credit "
|
||||
"Control Lines.\n") %
|
||||
(policy.name, len(policy_lines_generated)))
|
||||
else:
|
||||
report += _(
|
||||
"Policy \"%s\" has not generated any "
|
||||
@@ -166,24 +136,24 @@ class CreditControlRun(orm.Model):
|
||||
vals = {'state': 'done',
|
||||
'report': report,
|
||||
'manual_ids': [(6, 0, manually_managed_lines)]}
|
||||
run.write(vals, context=context)
|
||||
return generated_ids
|
||||
self.write(vals)
|
||||
return generated
|
||||
|
||||
def generate_credit_lines(self, cr, uid, run_id, context=None):
|
||||
"""Generate credit control lines
|
||||
@api.multi
|
||||
def generate_credit_lines(self):
|
||||
""" Generate credit control lines
|
||||
|
||||
Lock the ``credit_control_run`` Postgres table to avoid concurrent
|
||||
calls of this method.
|
||||
"""
|
||||
try:
|
||||
cr.execute('SELECT id FROM credit_control_run'
|
||||
' LIMIT 1 FOR UPDATE NOWAIT')
|
||||
self._cr.execute('SELECT id FROM credit_control_run'
|
||||
' LIMIT 1 FOR UPDATE NOWAIT')
|
||||
except Exception:
|
||||
# In case of exception openerp will do a rollback
|
||||
# for us and free the lock
|
||||
raise orm.except_orm(_('Error'),
|
||||
_('A credit control run is already running'
|
||||
' in background, please try later.'))
|
||||
raise api.Warning(_('A credit control run is already running'
|
||||
' in background, please try later.'))
|
||||
|
||||
self._generate_credit_lines(cr, uid, run_id, context)
|
||||
self._generate_credit_lines()
|
||||
return True
|
||||
|
||||
Reference in New Issue
Block a user