forked from Mapan/odoo17e
514 lines
22 KiB
Python
514 lines
22 KiB
Python
# -*- coding: utf-8 -*-
|
|
from contextlib import contextmanager
|
|
from freezegun import freeze_time
|
|
from unittest.mock import patch
|
|
|
|
from odoo.tests import Form, tagged
|
|
from odoo.addons.account_reports.tests.common import TestAccountReportsCommon
|
|
from odoo import Command
|
|
|
|
|
|
@tagged('post_install', '-at_install')
|
|
class TestAccountFollowupReports(TestAccountReportsCommon):
|
|
|
|
@classmethod
|
|
def setUpClass(cls, chart_template_ref=None):
|
|
super().setUpClass(chart_template_ref=chart_template_ref)
|
|
|
|
cls.partner_a.email = 'partner_a@mypartners.xyz'
|
|
|
|
def test_followup_report(self):
|
|
''' Test report lines when printing the follow-up report. '''
|
|
# Init options.
|
|
report = self.env['account.followup.report']
|
|
options = {
|
|
'partner_id': self.partner_a.id,
|
|
'multi_currency': True,
|
|
}
|
|
|
|
# 2016-01-01: First invoice, partially paid.
|
|
|
|
invoice_1 = self.env['account.move'].create({
|
|
'move_type': 'out_invoice',
|
|
'invoice_date': '2016-01-01',
|
|
'partner_id': self.partner_a.id,
|
|
'invoice_line_ids': [Command.create({
|
|
'quantity': 1,
|
|
'price_unit': 500,
|
|
'tax_ids': [],
|
|
})]
|
|
})
|
|
invoice_1.action_post()
|
|
|
|
payment_1 = self.env['account.move'].create({
|
|
# pylint: disable=C0326
|
|
'move_type': 'entry',
|
|
'date': '2016-01-01',
|
|
'journal_id': self.company_data['default_journal_misc'].id,
|
|
'line_ids': [
|
|
(0, 0, {'debit': 0.0, 'credit': 200.0, 'account_id': self.company_data['default_account_receivable'].id}),
|
|
(0, 0, {'debit': 200.0, 'credit': 0.0, 'account_id': self.company_data['default_journal_bank'].default_account_id.id}),
|
|
],
|
|
})
|
|
payment_1.action_post()
|
|
|
|
(payment_1 + invoice_1).line_ids\
|
|
.filtered(lambda line: line.account_id == self.company_data['default_account_receivable'])\
|
|
.reconcile()
|
|
|
|
with freeze_time('2016-01-01'):
|
|
self.assertLinesValues(
|
|
# pylint: disable=C0326
|
|
report._get_followup_report_lines(options),
|
|
# Name Date, Due Date, Doc. Total Due
|
|
[ 0, 1, 2, 3, 5],
|
|
[
|
|
('INV/2016/00001', '01/01/2016', '01/01/2016', '', 300.0),
|
|
('', '', '', '', 300.0),
|
|
],
|
|
options,
|
|
)
|
|
|
|
# 2016-01-05: Credit note due at 2016-01-10.
|
|
|
|
invoice_2 = self.env['account.move'].create({
|
|
'move_type': 'out_refund',
|
|
'invoice_date': '2016-01-05',
|
|
'invoice_date_due': '2016-01-10',
|
|
'partner_id': self.partner_a.id,
|
|
'invoice_payment_term_id': False,
|
|
'invoice_line_ids': [Command.create({
|
|
'quantity': 1,
|
|
'price_unit': 200,
|
|
'tax_ids': [],
|
|
})]
|
|
})
|
|
invoice_2.action_post()
|
|
|
|
with freeze_time('2016-01-05'):
|
|
self.assertLinesValues(
|
|
# pylint: disable=C0326
|
|
report._get_followup_report_lines(options),
|
|
# Name Date, Due Date, Doc. Total Due
|
|
[ 0, 1, 2, 3, 5],
|
|
[
|
|
('RINV/2016/00001', '01/05/2016', '01/10/2016', '', -200.0),
|
|
('INV/2016/00001', '01/01/2016', '01/01/2016', '', 300.0),
|
|
('', '', '', '', 100.0),
|
|
('', '', '', '', 300.0),
|
|
],
|
|
options,
|
|
)
|
|
|
|
# 2016-01-15: Draft invoice + previous credit note reached the date_maturity + first invoice reached the delay
|
|
# of the first followup level.
|
|
|
|
self.env['account.move'].create({
|
|
'move_type': 'out_refund',
|
|
'invoice_date': '2016-01-15',
|
|
'partner_id': self.partner_a.id,
|
|
'invoice_line_ids': [Command.create({
|
|
'quantity': 1,
|
|
'price_unit': 1000,
|
|
'tax_ids': [],
|
|
})]
|
|
})
|
|
|
|
with freeze_time('2016-01-15'):
|
|
self.assertLinesValues(
|
|
# pylint: disable=C0326
|
|
report._get_followup_report_lines(options),
|
|
# Name Date, Due Date, Doc. Total Due
|
|
[ 0, 1, 2, 3, 5],
|
|
[
|
|
('RINV/2016/00001', '01/05/2016', '01/10/2016', '', -200.0),
|
|
('INV/2016/00001', '01/01/2016', '01/01/2016', '', 300.0),
|
|
('', '', '', '', 100.0),
|
|
('', '', '', '', 100.0),
|
|
],
|
|
options,
|
|
)
|
|
|
|
# Trigger the followup report notice.
|
|
|
|
invoice_attachments = self.env['ir.attachment']
|
|
for invoice in invoice_1 + invoice_2:
|
|
invoice_attachment = self.env['ir.attachment'].create({
|
|
'name': 'some_attachment.pdf',
|
|
'res_id': invoice.id,
|
|
'res_model': 'account.move',
|
|
'datas': 'test',
|
|
'type': 'binary',
|
|
})
|
|
invoice_attachments += invoice_attachment
|
|
invoice._message_set_main_attachment_id(invoice_attachment.ids)
|
|
|
|
self.partner_a._compute_unpaid_invoices()
|
|
options['attachment_ids'] = invoice_attachments.ids
|
|
with patch.object(type(self.env['mail.mail']), 'unlink', lambda self: None):
|
|
self.env['account.followup.report']._send_email(options)
|
|
sent_attachments = self.env['mail.message'].search([('partner_ids', '=', self.partner_a.id)]).attachment_ids
|
|
|
|
self.assertEqual(invoice_attachments, sent_attachments)
|
|
|
|
attachaments_domain = [('attachment_ids', '=', attachment.id) for attachment in invoice_attachments]
|
|
mail = self.env['mail.mail'].search([('recipient_ids', '=', self.partner_a.id)] + attachaments_domain)
|
|
self.assertTrue(mail, "A payment reminder email should have been sent.")
|
|
|
|
def test_followup_lines_branches(self):
|
|
branch = self.env['res.company'].create({
|
|
'name': 'branch',
|
|
'parent_id': self.env.company.id
|
|
})
|
|
self.cr.precommit.run() # load the COA
|
|
|
|
report = self.env['account.followup.report']
|
|
options = {
|
|
'partner_id': self.partner_a.id,
|
|
}
|
|
|
|
invoice = self.env['account.move'].create({
|
|
'move_type': 'out_invoice',
|
|
'invoice_date': '2016-01-01',
|
|
'partner_id': self.partner_a.id,
|
|
'company_id': branch.id,
|
|
'invoice_line_ids': [Command.create({
|
|
'quantity': 1,
|
|
'price_unit': 500,
|
|
'tax_ids': [],
|
|
})]
|
|
})
|
|
invoice.action_post()
|
|
|
|
self.assertLinesValues(
|
|
# pylint: disable=C0326
|
|
report._get_followup_report_lines(options),
|
|
# Name Date, Due Date, Doc. Total Due
|
|
[ 0, 1, 2, 3, 5],
|
|
[
|
|
('INV/2016/00001', '01/01/2016', '01/01/2016', '', '$\xa0500.00'),
|
|
('', '', '', '', '$\xa0500.00'),
|
|
('', '', '', '', '$\xa0500.00'),
|
|
],
|
|
options,
|
|
)
|
|
|
|
def test_followup_report_address_1(self):
|
|
''' Test child contact priorities: the company will be used when there is no followup or billing contacts
|
|
'''
|
|
|
|
Partner = self.env['res.partner']
|
|
self.partner_a.is_company = True
|
|
options = {
|
|
'partner_id': self.partner_a.id,
|
|
}
|
|
|
|
child_partner = Partner.create({
|
|
'name': "Child contact",
|
|
'type': "contact",
|
|
'parent_id': self.partner_a.id,
|
|
})
|
|
|
|
mail = self.env['mail.mail'].search([('recipient_ids', '=', self.partner_a.id)])
|
|
self.init_invoice('out_invoice', partner=child_partner, invoice_date='2016-01-01', amounts=[500], post=True)
|
|
self.partner_a._compute_unpaid_invoices()
|
|
with patch.object(type(self.env['mail.mail']), 'unlink', lambda self: None):
|
|
self.env['account.followup.report']._send_email(options)
|
|
|
|
mail = self.env['mail.mail'].search([('recipient_ids', '=', self.partner_a.id)])
|
|
self.assertTrue(mail, "The payment reminder email should have been sent to the company.")
|
|
|
|
def test_followup_report_address_2(self):
|
|
''' Test child contact priorities: the follow up contact will be preferred over the billing contact
|
|
'''
|
|
|
|
Partner = self.env['res.partner']
|
|
self.partner_a.is_company = True
|
|
options = {
|
|
'partner_id': self.partner_a.id,
|
|
}
|
|
|
|
# Testing followup sent to billing address if used in invoice
|
|
|
|
child_partner = Partner.create({
|
|
'name': "Child contact",
|
|
'type': "contact",
|
|
'parent_id': self.partner_a.id,
|
|
})
|
|
invoice_partner = Partner.create({
|
|
'name' : "Child contact invoice",
|
|
'type' : "invoice",
|
|
'email' : "test-invoice@example.com",
|
|
'parent_id': child_partner.id,
|
|
})
|
|
|
|
self.init_invoice('out_invoice', partner=invoice_partner, invoice_date='2016-01-01', amounts=[500], post=True)
|
|
|
|
self.partner_a._compute_unpaid_invoices()
|
|
with patch.object(type(self.env['mail.mail']), 'unlink', lambda self: None):
|
|
self.env['account.followup.report']._send_email(options)
|
|
|
|
mail = self.env['mail.mail'].search([('recipient_ids', '=', invoice_partner.id)])
|
|
self.assertTrue(mail, "The payment reminder email should have been sent to the invoice partner.")
|
|
mail.unlink()
|
|
|
|
# Testing followup partner priority
|
|
|
|
followup_partner = Partner.create({
|
|
'name' : "Child contact followup",
|
|
'type' : "followup",
|
|
'email' : "test-followup@example.com",
|
|
'parent_id': self.partner_a.id,
|
|
})
|
|
|
|
self.partner_a._compute_unpaid_invoices()
|
|
with patch.object(type(self.env['mail.mail']), 'unlink', lambda self: None):
|
|
self.env['account.followup.report']._send_email(options)
|
|
|
|
mail = self.env['mail.mail'].search([('recipient_ids', '=', followup_partner.id)])
|
|
self.assertTrue(mail, "The payment reminder email should have been sent to the followup partner.")
|
|
|
|
def test_followup_invoice_no_amount(self):
|
|
# Init options.
|
|
report = self.env['account.followup.report']
|
|
options = {
|
|
'partner_id': self.partner_a.id,
|
|
}
|
|
|
|
invoice_move = self.env['account.move'].create({
|
|
'move_type': 'out_invoice',
|
|
'partner_id': self.partner_a.id,
|
|
'date': '2022-01-01',
|
|
'invoice_line_ids': [
|
|
(0, 0, {'quantity': 0, 'price_unit': 30}),
|
|
],
|
|
})
|
|
invoice_move.action_post()
|
|
|
|
lines = report._get_followup_report_lines(options)
|
|
self.assertEqual(len(lines), 0, "There should be no line displayed")
|
|
|
|
def test_negative_followup_report(self):
|
|
''' Test negative or null followup reports: if a contact has an overdue invoice but has a negative of null total due, no action is needed.
|
|
'''
|
|
self.env['account_followup.followup.line'].create({
|
|
'company_id': self.env.company.id,
|
|
'name': 'First Reminder',
|
|
'delay': 15,
|
|
'send_email': False,
|
|
})
|
|
self.env['account.move'].create({
|
|
'move_type': 'out_invoice',
|
|
'invoice_date': '2016-01-01',
|
|
'partner_id': self.partner_a.id,
|
|
'invoice_line_ids': [Command.create({
|
|
'quantity': 1,
|
|
'price_unit': 500,
|
|
'tax_ids': [],
|
|
})]
|
|
}).action_post()
|
|
|
|
self.env['account.move'].create({
|
|
'move_type': 'out_refund',
|
|
'invoice_date': '2016-01-15',
|
|
'partner_id': self.partner_a.id,
|
|
'invoice_line_ids': [Command.create({
|
|
'quantity': 1,
|
|
'price_unit': 300,
|
|
'tax_ids': [],
|
|
})]
|
|
}).action_post()
|
|
self.assertEqual(self.partner_a.total_due, 200)
|
|
self.assertEqual(self.partner_a.followup_status, 'in_need_of_action')
|
|
|
|
self.env['account.payment'].create({
|
|
'move_type': 'inbound',
|
|
'partner_id': self.partner_a.id,
|
|
'amount': 400,
|
|
}).action_post()
|
|
self.assertEqual(self.partner_a.total_due, -200)
|
|
self.assertEqual(self.partner_a.followup_status, 'no_action_needed')
|
|
|
|
def test_followup_report_style(self):
|
|
"""
|
|
This report is often broken in terms of styling, this test will check the styling of the lines.
|
|
(This test will not work if we modify the template it self)
|
|
"""
|
|
report = self.env['account.followup.report']
|
|
options = {
|
|
'partner_id': self.partner_a.id,
|
|
'multi_currency': True,
|
|
}
|
|
|
|
# 2016-01-01: First invoice, partially paid.
|
|
|
|
invoice_1 = self.env['account.move'].create({
|
|
'move_type': 'out_invoice',
|
|
'invoice_date': '2016-01-01',
|
|
'partner_id': self.partner_a.id,
|
|
'invoice_line_ids': [Command.create({
|
|
'quantity': 1,
|
|
'price_unit': 500,
|
|
'tax_ids': [],
|
|
})]
|
|
})
|
|
invoice_1.action_post()
|
|
|
|
payment_1 = self.env['account.move'].create({
|
|
'move_type': 'entry',
|
|
'date': '2016-01-01',
|
|
'journal_id': self.company_data['default_journal_misc'].id,
|
|
'line_ids': [
|
|
Command.create({
|
|
'debit': 0.0,
|
|
'credit': 200.0,
|
|
'account_id': self.company_data['default_account_receivable'].id
|
|
}),
|
|
Command.create({
|
|
'debit': 200.0,
|
|
'credit': 0.0,
|
|
'account_id': self.company_data['default_journal_bank'].default_account_id.id
|
|
}),
|
|
],
|
|
})
|
|
payment_1.action_post()
|
|
|
|
(payment_1 + invoice_1).line_ids \
|
|
.filtered(lambda line: line.account_id == self.company_data['default_account_receivable']) \
|
|
.reconcile()
|
|
|
|
lines = report._get_followup_report_lines(options)
|
|
# The variable lines is composed of 3 lines, the line of the move and two lines of total (due and overdue)
|
|
# First line
|
|
line_0_style = [
|
|
'white-space:nowrap;text-align:left;',
|
|
'white-space:nowrap;text-align:left;color: red;',
|
|
'text-align:center; white-space:normal;',
|
|
'text-align:left; white-space:normal;',
|
|
'text-align:right; white-space:normal;',
|
|
]
|
|
for expected_style, column in zip(line_0_style, lines[0]['columns']):
|
|
self.assertEqual(expected_style, column['style'])
|
|
|
|
# Second line
|
|
self.assertEqual(lines[1]['columns'][3].get('style'), 'text-align:right; white-space:normal; font-weight: bold;') # Total due title
|
|
self.assertEqual(lines[1]['columns'][4].get('style'), 'text-align:right; white-space:normal; font-weight: bold;') # Total due value
|
|
|
|
# Third line
|
|
self.assertEqual(lines[2]['columns'][3].get('style'), 'text-align:right; white-space:normal; font-weight: bold;') # Total overdue title
|
|
self.assertEqual(lines[2]['columns'][4].get('style'), 'text-align:right; white-space:normal; font-weight: bold;') # Total overdue value
|
|
|
|
# Check template used
|
|
for line in lines:
|
|
for column in line['columns']:
|
|
self.assertEqual(column['template'], 'account_followup.line_template')
|
|
|
|
def test_followup_send_email(self):
|
|
""" Tests that the email address in the mail.template is used to send the followup email."""
|
|
self.env['account.move'].create({
|
|
'move_type': 'out_invoice',
|
|
'invoice_date': '2016-01-01',
|
|
'partner_id': self.partner_a.id,
|
|
'invoice_line_ids': [Command.create({
|
|
'quantity': 1,
|
|
'price_unit': 500,
|
|
'tax_ids': [],
|
|
})]
|
|
}).action_post()
|
|
|
|
@contextmanager
|
|
def create_and_send_email(email_from, subject):
|
|
""" Create a mail.template, open the followup wizard and send the followup email."""
|
|
mail_template = self.env['mail.template'].create({
|
|
'name': "Payment Reminder",
|
|
'model_id': self.env.ref('base.model_res_partner').id,
|
|
'email_from': email_from,
|
|
'partner_to': '{{ object.id }}',
|
|
'subject': subject,
|
|
})
|
|
wizard = self.env['account_followup.manual_reminder'].with_context(
|
|
active_model='res.partner',
|
|
active_ids=self.partner_a.ids,
|
|
).create({})
|
|
wizard.email = True # tick the 'email' checkbox
|
|
wizard.template_id = mail_template
|
|
wizard.process_followup()
|
|
yield
|
|
self.assertEqual(len(message), 1)
|
|
self.assertEqual(message.author_id, self.env.user.partner_id)
|
|
|
|
# case 1: the email_from is dynamically set
|
|
with create_and_send_email(
|
|
email_from="{{ object._get_followup_responsible().email_formatted }}",
|
|
subject="{{ (object.company_id or object._get_followup_responsible().company_id).name }} Pay me now !",
|
|
):
|
|
message = self.env['mail.message'].search([('subject', 'like', "Pay me now !")])
|
|
self.assertEqual(message.email_from, self.env.user.partner_id.email_formatted)
|
|
|
|
# case 2: the email_from is hardcoded in the template
|
|
with create_and_send_email(
|
|
email_from="test@odoo.com",
|
|
subject="{{ (object.company_id or object._get_followup_responsible().company_id).name }} Pay me noooow !",
|
|
):
|
|
message = self.env['mail.message'].search([('subject', 'like', "Pay me noooow !")])
|
|
self.assertEqual(message.email_from, "test@odoo.com")
|
|
|
|
def test_process_automatic_followup_send_email(self):
|
|
""" Tests that the email address in the mail.template is used to send the followup email from the cron."""
|
|
self.env['account_followup.followup.line'].create({
|
|
'company_id': self.env.company.id,
|
|
'name': 'First Reminder',
|
|
'delay': 15,
|
|
'send_email': True,
|
|
})
|
|
self.env['account.move'].create({
|
|
'move_type': 'out_invoice',
|
|
'invoice_date': '2016-01-01',
|
|
'partner_id': self.partner_a.id,
|
|
'invoice_line_ids': [Command.create({
|
|
'quantity': 1,
|
|
'price_unit': 500,
|
|
'tax_ids': [],
|
|
})]
|
|
}).action_post()
|
|
|
|
@contextmanager
|
|
def create_and_send_email(email_from, subject):
|
|
""" Create a mail.template, link it with the followup line and execute followups."""
|
|
mail_template = self.env['mail.template'].create({
|
|
'name': "Payment Reminder",
|
|
'model_id': self.env.ref('base.model_res_partner').id,
|
|
'email_from': email_from,
|
|
'partner_to': '{{ object.id }}',
|
|
'subject': subject,
|
|
})
|
|
self.partner_a.followup_line_id.mail_template_id = mail_template
|
|
self.partner_a.followup_next_action_date = False
|
|
self.partner_a._execute_followup_partner(options={'snailmail': False})
|
|
yield
|
|
self.assertEqual(len(message), 1)
|
|
self.assertEqual(message.author_id, self.partner_a._get_followup_responsible().partner_id, "Automatic followups should have the followup responsible as the author.")
|
|
|
|
# case 1: the email_from is dynamically set
|
|
with create_and_send_email(
|
|
email_from="{{ object._get_followup_responsible().email_formatted }}",
|
|
subject="{{ (object.company_id or object._get_followup_responsible().company_id).name }} Pay me now !",
|
|
):
|
|
message = self.env['mail.message'].search([('subject', 'like', "Pay me now !")])
|
|
self.assertEqual(message.email_from, self.env.user.partner_id.email_formatted)
|
|
|
|
# case 2: the email_from is hardcoded in the template
|
|
with create_and_send_email(
|
|
email_from="test@odoo.com",
|
|
subject="{{ (object.company_id or object._get_followup_responsible().company_id).name }} Pay me noooow !",
|
|
):
|
|
message = self.env['mail.message'].search([('subject', 'like', "Pay me noooow !")])
|
|
self.assertEqual(message.email_from, "test@odoo.com")
|
|
|
|
def test_compute_render_model(self):
|
|
with Form(self.env['account_followup.manual_reminder'].with_context(
|
|
active_model='res.partner',
|
|
active_ids=self.partner_a.ids,
|
|
)) as wizard:
|
|
self.assertEqual(wizard.render_model, "res.partner")
|