SaaSERP3/addons/om_account_followup/models/partner.py

375 lines
19 KiB
Python

# -*- coding: utf-8 -*-
from functools import reduce
from lxml import etree
from odoo import api, fields, models, _
from odoo.exceptions import ValidationError
from odoo.tools.misc import formatLang
class ResPartner(models.Model):
_inherit = "res.partner"
def fields_view_get(self, view_id=None, view_type='form', toolbar=False,
submenu=False):
res = super(ResPartner, self).fields_view_get(
view_id=view_id, view_type=view_type, toolbar=toolbar,
submenu=submenu)
if view_type == 'form' and self.env.context.get('Followupfirst'):
doc = etree.XML(res['arch'], parser=None, base_url=None)
first_node = doc.xpath("//page[@name='followup_tab']")
root = first_node[0].getparent()
root.insert(0, first_node[0])
res['arch'] = etree.tostring(doc, encoding="utf-8")
return res
def _get_latest(self):
company = self.env.user.company_id
for partner in self:
amls = partner.unreconciled_aml_ids
latest_date = False
latest_level = False
latest_days = False
latest_level_without_lit = False
latest_days_without_lit = False
for aml in amls:
aml_followup = aml.followup_line_id
if (aml.company_id == company) and aml_followup and \
(not latest_days or latest_days < aml_followup.delay):
latest_days = aml_followup.delay
latest_level = aml_followup.id
if (aml.company_id == company) and aml.followup_date and (
not latest_date or latest_date < aml.followup_date):
latest_date = aml.followup_date
if (aml.company_id == company) and not aml.blocked and \
(aml_followup and (not latest_days_without_lit or
latest_days_without_lit < aml_followup.delay)):
latest_days_without_lit = aml_followup.delay
latest_level_without_lit = aml_followup.id
partner.latest_followup_date = latest_date
partner.latest_followup_level_id = latest_level
partner.latest_followup_level_id_without_lit = latest_level_without_lit
def do_partner_manual_action_dermanord(self, followup_line):
action_text = followup_line.manual_action_note or ''
action_date = self.payment_next_action_date or \
fields.Date.today()
if self.payment_responsible_id:
responsible_id = self.payment_responsible_id.id
else:
p = followup_line.manual_action_responsible_id
responsible_id = p and p.id or False
self.write({'payment_next_action_date': action_date,
'payment_next_action': action_text,
'payment_responsible_id': responsible_id})
def do_partner_manual_action(self, partner_ids):
for partner in self.browse(partner_ids):
followup_without_lit = partner.latest_followup_level_id_without_lit
if partner.payment_next_action:
action_text = \
(partner.payment_next_action or '') + "\n" + \
(followup_without_lit.manual_action_note or '')
else:
action_text = followup_without_lit.manual_action_note or ''
action_date = partner.payment_next_action_date or \
fields.Date.today()
if partner.payment_responsible_id:
responsible_id = partner.payment_responsible_id.id
else:
p = followup_without_lit.manual_action_responsible_id
responsible_id = p and p.id or False
partner.write({'payment_next_action_date': action_date,
'payment_next_action': action_text,
'payment_responsible_id': responsible_id})
def do_partner_print(self, wizard_partner_ids, data):
if not wizard_partner_ids:
return {}
data['partner_ids'] = wizard_partner_ids
datas = {
'ids': wizard_partner_ids,
'model': 'followup.followup',
'form': data
}
return self.env.ref(
'om_account_followup.action_report_followup').report_action(
self, data=datas)
def do_partner_mail(self):
ctx = self.env.context.copy()
ctx['followup'] = True
template = 'om_account_followup.email_template_om_account_followup_default'
unknown_mails = 0
for partner in self:
partners_to_email = [child for child in partner.child_ids if
child.type == 'invoice' and child.email]
if not partners_to_email and partner.email:
partners_to_email = [partner]
if partners_to_email:
level = partner.latest_followup_level_id_without_lit
for partner_to_email in partners_to_email:
if level and level.send_email and \
level.email_template_id and \
level.email_template_id.id:
level.email_template_id.with_context(ctx).send_mail(
partner_to_email.id)
else:
mail_template_id = self.env.ref(template)
mail_template_id.with_context(ctx).send_mail(
partner_to_email.id)
if partner not in partners_to_email:
partner.message_post(body=_(
'Overdue email sent to %s' % ', '.join(
['%s <%s>' % (partner.name, partner.email) for
partner in partners_to_email])))
else:
unknown_mails = unknown_mails + 1
action_text = _("Email not sent because of email address "
"of partner not filled in")
if partner.payment_next_action_date:
payment_action_date = min(
fields.Date.today(),
partner.payment_next_action_date)
else:
payment_action_date = fields.Date.today()
if partner.payment_next_action:
payment_next_action = \
partner.payment_next_action + " \n " + action_text
else:
payment_next_action = action_text
partner.with_context(ctx).write(
{'payment_next_action_date': payment_action_date,
'payment_next_action': payment_next_action})
return unknown_mails
def get_followup_table_html(self):
self.ensure_one()
partner = self.commercial_partner_id
followup_table = ''
if partner.unreconciled_aml_ids:
company = self.env.user.company_id
current_date = fields.Date.today()
report = self.env['report.om_account_followup.report_followup']
final_res = report._lines_get_with_partner(partner, company.id)
for currency_dict in final_res:
currency = currency_dict.get('line', [
{'currency_id': company.currency_id}])[0]['currency_id']
followup_table += '''
<table border="2" width=100%%>
<tr>
<td>''' + _("Invoice Date") + '''</td>
<td>''' + _("Description") + '''</td>
<td>''' + _("Reference") + '''</td>
<td>''' + _("Due Date") + '''</td>
<td>''' + _("Amount") + " (%s)" % (
currency.symbol) + '''</td>
<td>''' + _("Lit.") + '''</td>
</tr>
'''
total = 0
for aml in currency_dict['line']:
block = aml['blocked'] and 'X' or ' '
total += aml['balance']
strbegin = "<TD>"
strend = "</TD>"
date = aml['date_maturity'] or aml['date']
if date <= current_date and aml['balance'] > 0:
strbegin = "<TD><B>"
strend = "</B></TD>"
followup_table += "<TR>" + strbegin + str(aml['date']) + \
strend + strbegin + aml['name'] + \
strend + strbegin + \
(aml['ref'] or '') + strend + \
strbegin + str(date) + strend + \
strbegin + str(aml['balance']) + \
strend + strbegin + block + \
strend + "</TR>"
total = reduce(lambda x, y: x + y['balance'],
currency_dict['line'], 0.00)
total = formatLang(self.env, total, currency_obj=currency)
followup_table += '''<tr> </tr>
</table>
<center>''' + _(
"Amount due") + ''' : %s </center>''' % (total)
return followup_table
def write(self, vals):
if vals.get("payment_responsible_id", False):
for part in self:
if part.payment_responsible_id != \
self.env['res.users'].browse(vals["payment_responsible_id"]):
# Find partner_id of user put as responsible
responsible_partner_id = self.env["res.users"].browse(
vals['payment_responsible_id']).partner_id.id
part.message_post(
body=_("You became responsible to do the next action "
"for the payment follow-up of") +
" <b><a href='#id=" + str(part.id) +
"&view_type=form&model=res.partner'> " + part.name +
" </a></b>",
type='comment',
context=self.env.context,
partner_ids=[responsible_partner_id])
return super(ResPartner, self).write(vals)
def action_done(self):
return self.write({'payment_next_action_date': False,
'payment_next_action': '',
'payment_responsible_id': False})
def do_button_print(self):
self.ensure_one()
company_id = self.env.user.company_id.id
if not self.env['account.move.line'].search(
[('partner_id', '=', self.id),
('account_id.account_type', '=', 'asset_receivable'),
('full_reconcile_id', '=', False),
('company_id', '=', company_id),
'|', ('date_maturity', '=', False),
('date_maturity', '<=', fields.Date.today())]):
raise ValidationError(
_("The partner does not have any accounting entries to "
"print in the overdue report for the current company."))
self.message_post(body=_('Printed overdue payments report'))
self.message_post(body=_('Printed overdue payments report'))
wizard_partner_ids = [self.id * 10000 + company_id]
followup_ids = self.env['followup.followup'].search(
[('company_id', '=', company_id)])
if not followup_ids:
raise ValidationError(_(
"There is no followup plan defined for the current company."))
data = {
'date': fields.date.today(),
'followup_id': followup_ids[0].id,
}
return self.do_partner_print(wizard_partner_ids, data)
def _get_amounts_and_date(self):
company = self.env.user.company_id
current_date = fields.Date.today()
for partner in self:
worst_due_date = False
amount_due = amount_overdue = 0.0
for aml in partner.unreconciled_aml_ids:
if (aml.company_id == company):
date_maturity = aml.date_maturity or aml.date
if not worst_due_date or date_maturity < worst_due_date:
worst_due_date = date_maturity
amount_due += aml.result
if (date_maturity <= current_date):
amount_overdue += aml.result
partner.payment_amount_due = amount_due
partner.payment_amount_overdue = amount_overdue
partner.payment_earliest_due_date = worst_due_date
def _get_followup_overdue_query(self, args, overdue_only=False):
company_id = self.env.user.company_id.id
having_where_clause = ' AND '.join(
map(lambda x: '(SUM(bal2) %s %%s)' % (x[1]), args))
having_values = [x[2] for x in args]
having_where_clause = having_where_clause % (having_values[0])
overdue_only_str = overdue_only and 'AND date_maturity <= NOW()' or ''
return ('''SELECT pid AS partner_id, SUM(bal2) FROM
(SELECT CASE WHEN bal IS NOT NULL THEN bal
ELSE 0.0 END AS bal2, p.id as pid FROM
(SELECT (debit-credit) AS bal, partner_id
FROM account_move_line l
LEFT JOIN account_account a ON a.id = l.account_id
WHERE a.account_type = 'asset_receivable'
%s AND full_reconcile_id IS NULL
AND l.company_id = %s) AS l
RIGHT JOIN res_partner p
ON p.id = partner_id ) AS pl
GROUP BY pid HAVING %s''') % (
overdue_only_str, company_id, having_where_clause)
def _payment_overdue_search(self, operator, operand):
args = [('payment_amount_overdue', operator, operand)]
query = self._get_followup_overdue_query(args, overdue_only=True)
self._cr.execute(query)
res = self._cr.fetchall()
if not res:
return [('id', '=', '0')]
return [('id', 'in', [x[0] for x in res])]
def _payment_earliest_date_search(self, operator, operand):
args = [('payment_earliest_due_date', operator, operand)]
company_id = self.env.user.company_id.id
having_where_clause = ' AND '.join(
map(lambda x: "(MIN(l.date_maturity) %s '%%s')" % (x[1]), args))
having_values = [x[2] for x in args]
having_where_clause = having_where_clause % (having_values[0])
query = """SELECT partner_id FROM account_move_line l
LEFT JOIN account_account a ON a.id = l.account_id
WHERE a.account_type = 'asset_receivable'
AND l.company_id = %s
AND l.full_reconcile_id IS NULL
AND partner_id IS NOT NULL GROUP BY partner_id"""
query = query % (company_id)
if having_where_clause:
query += ' HAVING %s ' % (having_where_clause)
self._cr.execute(query)
res = self._cr.fetchall()
if not res:
return [('id', '=', '0')]
return [('id', 'in', [x[0] for x in res])]
def _payment_due_search(self, operator, operand):
args = [('payment_amount_due', operator, operand)]
query = self._get_followup_overdue_query(args, overdue_only=False)
self._cr.execute(query)
res = self._cr.fetchall()
if not res:
return [('id', '=', '0')]
return [('id', 'in', [x[0] for x in res])]
def _get_partners(self):
partners = set()
for aml in self:
if aml.partner_id:
partners.add(aml.partner_id.id)
return list(partners)
payment_responsible_id = fields.Many2one('res.users', ondelete='set null',
string='Follow-up Responsible',
tracking=True, copy=False,
help="Optionally you can assign a user to this field, which will make "
"him responsible for the action.", )
payment_note = fields.Text('Customer Payment Promise', help="Payment Note", copy=False)
payment_next_action = fields.Text('Next Action', copy=False, tracking=True,
help="This is the next action to be taken. It will automatically be "
"set when the partner gets a follow-up level that requires a manual action. ")
payment_next_action_date = fields.Date('Next Action Date', copy=False,
help="This is when the manual follow-up is needed. The date will be "
"set to the current date when the partner gets a follow-up level "
"that requires a manual action. Can be practical to set manually "
"e.g. to see if he keeps his promises.")
unreconciled_aml_ids = fields.One2many('account.move.line', 'partner_id',
domain=[('full_reconcile_id', '=', False),
('account_id.account_type', '=', 'asset_receivable')])
latest_followup_date = fields.Date(compute='_get_latest', string="Latest Follow-up Date", compute_sudo=True,
help="Latest date that the follow-up level of the partner was changed")
latest_followup_level_id = fields.Many2one('followup.line', compute='_get_latest', compute_sudo=True,
string="Latest Follow-up Level", help="The maximum follow-up level")
latest_followup_sequence = fields.Integer('Sequence', help="Gives the sequence order when displaying a list of follow-up lines.", default=0)
latest_followup_level_id_without_lit = fields.Many2one('followup.line',
compute='_get_latest', store=True, compute_sudo=True,
string="Latest Follow-up Level without litigation",
help="The maximum follow-up level without taking into "
"account the account move lines with litigation")
payment_amount_due = fields.Float(compute='_get_amounts_and_date',
string="Amount Due", search='_payment_due_search')
payment_amount_overdue = fields.Float(compute='_get_amounts_and_date',
string="Amount Overdue", search='_payment_overdue_search')
payment_earliest_due_date = fields.Date(compute='_get_amounts_and_date', string="Worst Due Date",
search='_payment_earliest_date_search')