Files
enterprise-addons/account_followup/tests/test_account_followup.py
T

666 lines
31 KiB
Python

from unittest.mock import patch
from freezegun import freeze_time
from odoo import Command, fields
from odoo.tests import tagged
from odoo.addons.account_followup.tests.common import TestAccountFollowupCommon
from odoo.addons.mail.tests.common import MailCommon
from dateutil.relativedelta import relativedelta
@tagged('post_install', '-at_install')
class TestAccountFollowupReports(TestAccountFollowupCommon, MailCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.env['account_followup.followup.line'].search([]).unlink()
wkhtmltopdf_patcher = patch.object(
cls.env.registry['ir.actions.report'],
'_run_wkhtmltopdf',
lambda *args, **kwargs: b"0"
)
wkhtmltopdf_patcher.start()
cls.addClassCleanup(wkhtmltopdf_patcher.stop)
def create_followup(self, delay):
return self.env['account_followup.followup.line'].create({
'name': f'followup {delay}',
'delay': delay,
'send_email': False,
'company_id': self.company_data['company'].id
})
def create_invoice(self, date, partner = None):
invoice = self.env['account.move'].create({
'move_type': 'out_invoice',
'invoice_date': date,
'invoice_date_due': date,
'partner_id': partner.id if partner else self.partner_a.id,
'invoice_line_ids': [Command.create({
'quantity': 1,
'price_unit': 500,
'tax_ids': [],
})]
})
invoice.action_post()
return invoice
def test_followup_responsible(self):
"""
Test that the responsible is correctly set
"""
self.first_followup_line = self.create_followup(delay=-10)
user1 = self.env['res.users'].create({
'name': 'A User',
'login': 'a_user',
'email': 'a@user.com',
'group_ids': [(6, 0, [self.env.ref('account.group_account_user').id])]
})
user2 = self.env['res.users'].create({
'name': 'Another User',
'login': 'another_user',
'email': 'another@user.com',
'group_ids': [(6, 0, [self.env.ref('account.group_account_user').id])]
})
# 1- no info, use current user
self.assertEqual(self.partner_a._get_followup_responsible(), self.env.user)
# 2- set invoice user
invoice1 = self.init_invoice('out_invoice', partner=self.partner_a,
invoice_date=fields.Date.from_string('2000-01-01'),
amounts=[2000])
invoice2 = self.init_invoice('out_invoice', partner=self.partner_a,
invoice_date=fields.Date.from_string('2000-01-01'),
amounts=[1000])
invoice1.invoice_user_id = user1
invoice2.invoice_user_id = user2
(invoice1 + invoice2).action_post()
# Should pick invoice_user_id of the most delayed move, with highest residual amount in case of tie (invoice1)
self.assertEqual(self.partner_a._get_followup_responsible(), user1)
# If user1 is archived, it shouldn't be selected as responsible
user1.active = False
self.assertEqual(self.partner_a._get_followup_responsible(), self.env.user)
user1.active = True
self.partner_a.followup_line_id = self.first_followup_line
# 3- A followup responsible user has been set on the partner
self.partner_a.followup_responsible_id = user2
self.assertEqual(self.partner_a._get_followup_responsible(), user2)
# 4- Modify the default responsible on followup level
self.partner_a.followup_line_id.activity_default_responsible_type = 'salesperson'
self.assertEqual(self.partner_a._get_followup_responsible(), user1)
self.assertEqual(self.partner_a._get_followup_responsible(multiple_responsible=True), (user1 + user2))
self.partner_a.followup_line_id.activity_default_responsible_type = 'account_manager'
self.partner_a.user_id = user2
self.assertEqual(self.partner_a._get_followup_responsible(), self.partner_a.user_id)
def test_followup_activity(self):
first_followup_line = self.create_followup(delay=10)
first_followup_line.create_activity = True
first_followup_line.activity_default_responsible_type = 'salesperson'
user1 = self.env['res.users'].create({
'name': 'A User',
'login': 'a_user',
'email': 'a@user.com',
'group_ids': [Command.set([self.env.ref('account.group_account_user').id])]
})
user2 = self.env['res.users'].create({
'name': 'Another User',
'login': 'another_user',
'email': 'another@user.com',
'group_ids': [Command.set([self.env.ref('account.group_account_user').id])]
})
inv1 = self.create_invoice('2022-01-02')
inv1.invoice_user_id = user1
inv2 = self.create_invoice('2022-01-02')
inv2.invoice_user_id = user2
with freeze_time('2022-01-13'):
self.partner_a._execute_followup_partner(options={'snailmail': False})
self.assertEqual(self.partner_a.activity_ids.user_id, (user1 + user2))
def test_followup_no_invoice_user_id(self):
first_followup_line = self.create_followup(delay=10)
first_followup_line.create_activity = True
first_followup_line.activity_default_responsible_type = 'salesperson'
inv1 = self.create_invoice('2022-01-02')
inv1.invoice_user_id = None
with freeze_time('2022-01-13'):
self.assertEqual(self.partner_a._get_followup_responsible(), self.user)
def test_followup_line_and_status(self):
self.first_followup_line = self.create_followup(delay=-10)
self.second_followup_line = self.create_followup(delay=10)
self.third_followup_line = self.create_followup(delay=15)
self.create_invoice('2022-01-02')
with freeze_time('2021-12-20'):
# Today < due date + delay first followup level (negative delay -> reminder before due date)
self.assertPartnerFollowup(self.partner_a, 'no_action_needed', self.first_followup_line)
with freeze_time('2021-12-24'):
# Today = due date + delay first followup level
self.assertPartnerFollowup(self.partner_a, 'in_need_of_action', self.first_followup_line)
# followup_next_action_date not exceeded but no invoice is overdue,
# we should not be in status 'with_overdue_invoices' but 'no action needed'
self.partner_a.followup_next_action_date = fields.Date.from_string('2021-12-25')
self.assertPartnerFollowup(self.partner_a, 'no_action_needed', self.first_followup_line)
with freeze_time('2022-01-13'):
# Today > due date + delay second followup level but first followup level not processed yet
self.assertPartnerFollowup(self.partner_a, 'in_need_of_action', self.first_followup_line)
self.partner_a._execute_followup_partner(options={'snailmail': False})
# Due date exceeded but first followup level processed
# followup_next_action_date set in 20 days (delay 2nd level - delay 1st level = 10 - (-10) = 20)
self.assertPartnerFollowup(self.partner_a, 'with_overdue_invoices', self.second_followup_line)
self.assertEqual(self.partner_a.followup_next_action_date, fields.Date.from_string('2022-02-02'))
with freeze_time('2022-02-03'):
# followup_next_action_date exceeded and invoice not reconciled yet
self.assertPartnerFollowup(self.partner_a, 'in_need_of_action', self.second_followup_line)
# execute second followup
self.partner_a._execute_followup_partner(options={'snailmail': False})
self.assertPartnerFollowup(self.partner_a, 'with_overdue_invoices', self.third_followup_line)
self.assertEqual(self.partner_a.followup_next_action_date, fields.Date.from_string('2022-02-08'))
with freeze_time('2022-02-09'):
# followup_next_action_date exceeded and invoice not reconciled yet
self.assertPartnerFollowup(self.partner_a, 'in_need_of_action', self.third_followup_line)
# execute third followup
self.partner_a._execute_followup_partner(options={'snailmail': False})
self.assertPartnerFollowup(self.partner_a, 'with_overdue_invoices', self.third_followup_line)
self.assertEqual(self.partner_a.followup_next_action_date, fields.Date.from_string('2022-02-14'))
with freeze_time('2022-02-15'):
# followup_next_action_date exceeded and invoice not reconciled yet
self.assertPartnerFollowup(self.partner_a, 'with_overdue_invoices', self.third_followup_line)
# executing the third followup again should do nothing as all the aml are linked to it
followup_executed = self.partner_a._execute_followup_partner(options={'snailmail': False})
self.assertFalse(followup_executed)
# create a new overdue invoice
self.create_invoice('2022-01-03')
self.assertPartnerFollowup(self.partner_a, 'in_need_of_action', self.third_followup_line)
aml_ids = self.partner_a.unreconciled_aml_ids
# Exclude every unreconciled invoice line.
aml_ids.no_followup = True
# Every unreconciled invoice line is excluded, so the result should be `no_action_needed`.
self.assertPartnerFollowup(self.partner_a, 'no_action_needed', self.third_followup_line)
# It resets if we don't exclude them anymore.
aml_ids.no_followup = False
self.assertPartnerFollowup(self.partner_a, 'in_need_of_action', self.third_followup_line)
self.env['account.payment.register'].create({
'line_ids': self.partner_a.unreconciled_aml_ids,
})._create_payments()
self.assertPartnerFollowup(self.partner_a, None, None)
def test_followup_multiple_invoices(self):
followup_10 = self.create_followup(delay=10)
followup_15 = self.create_followup(delay=15)
followup_30 = self.create_followup(delay=30)
self.create_invoice('2022-01-01')
self.create_invoice('2022-01-02')
# 9 days are not passed yet for the first followup level, current delay is 10-0=10
with freeze_time('2022-01-10'):
self.assertPartnerFollowup(self.partner_a, 'with_overdue_invoices', followup_10)
# 10 days passed, current delay is 10-0=10, need to take action
with freeze_time('2022-01-11'):
self.assertPartnerFollowup(self.partner_a, 'in_need_of_action', followup_10)
self.partner_a._execute_followup_partner(options={'snailmail': False})
self.assertPartnerFollowup(self.partner_a, 'with_overdue_invoices', followup_15)
# action taken 4 days ago, current delay is 15-10=5, nothing needed
with freeze_time('2022-01-15'):
self.assertPartnerFollowup(self.partner_a, 'with_overdue_invoices', followup_15)
# action taken 5 days ago, current delay is 15-10=5, need to take action
with freeze_time('2022-01-16'):
self.assertPartnerFollowup(self.partner_a, 'in_need_of_action', followup_15)
self.partner_a._execute_followup_partner(options={'snailmail': False})
self.assertPartnerFollowup(self.partner_a, 'with_overdue_invoices', followup_30)
# action taken 14 days ago, current delay is 30-15=15, nothing needed
with freeze_time('2022-01-30'):
self.assertPartnerFollowup(self.partner_a, 'with_overdue_invoices', followup_30)
# action taken 15 days ago, current delay is 30-15=15, need to take action
with freeze_time('2022-01-31'):
self.assertPartnerFollowup(self.partner_a, 'in_need_of_action', followup_30)
self.partner_a._execute_followup_partner(options={'snailmail': False})
self.assertPartnerFollowup(self.partner_a, 'with_overdue_invoices', followup_30)
# action taken 13 days ago, current delay is 15 (same on repeat), nothing needed
with freeze_time('2022-02-14'):
self.assertPartnerFollowup(self.partner_a, 'with_overdue_invoices', followup_30)
# action taken 14 days ago, current delay is 15 (same on repeat), need to take action
with freeze_time('2022-02-15'):
self.assertPartnerFollowup(self.partner_a, 'in_need_of_action', followup_30)
self.partner_a._execute_followup_partner(options={'snailmail': False})
self.assertPartnerFollowup(self.partner_a, 'with_overdue_invoices', followup_30)
def test_followup_multiple_invoices_with_first_payment(self):
# Test the behavior of multiple invoices when the first one is paid
followup_10 = self.create_followup(delay=10)
followup_15 = self.create_followup(delay=15)
invoice_01 = self.create_invoice('2022-01-01')
self.create_invoice('2022-01-02')
# 9 days are not passed yet for the first followup level, current delay is 10-0=10
with freeze_time('2022-01-10'):
self.assertPartnerFollowup(self.partner_a, 'with_overdue_invoices', followup_10)
# 10 days passed, current delay is 10-0=10, need to take action
with freeze_time('2022-01-11'):
self.assertPartnerFollowup(self.partner_a, 'in_need_of_action', followup_10)
# followup level for the second invoice shouldn't change since it's only 9 days overdue
self.partner_a._execute_followup_partner(options={'snailmail': False})
self.assertPartnerFollowup(self.partner_a, 'with_overdue_invoices', followup_15)
self.env['account.payment.register'].create({
'line_ids': invoice_01.line_ids.filtered(lambda l: l.display_type == 'payment_term'),
})._create_payments()
# partner followup level goes back to 10 days after paying the first invoice
self.assertPartnerFollowup(self.partner_a, 'with_overdue_invoices', followup_10)
# action taken 4 days ago, current delay is 15-10=5, nothing needed
with freeze_time('2022-01-15'):
self.assertPartnerFollowup(self.partner_a, 'with_overdue_invoices', followup_10)
# action taken 5 days ago, current delay is 15-10=5, need to take action
with freeze_time('2022-01-16'):
self.assertPartnerFollowup(self.partner_a, 'in_need_of_action', followup_10)
self.partner_a._execute_followup_partner(options={'snailmail': False})
self.assertPartnerFollowup(self.partner_a, 'with_overdue_invoices', followup_15)
def test_followup_multiple_invoices_with_last_payment(self):
# Test the behavior of multiple invoices when the last one is paid
# Should behave exactly like test_followup_multiple_invoices_with_first_payment
# because the followup is done at the same time.
followup_10 = self.create_followup(delay=10)
followup_15 = self.create_followup(delay=15)
followup_30 = self.create_followup(delay=30)
self.create_invoice('2022-01-01')
invoice_02 = self.create_invoice('2022-01-02')
# 9 days are not passed yet for the first followup level, current delay is 10-0=10
with freeze_time('2022-01-10'):
self.assertPartnerFollowup(self.partner_a, 'with_overdue_invoices', followup_10)
# 10 days passed, current delay is 10-0=10, need to take action
with freeze_time('2022-01-11'):
self.assertPartnerFollowup(self.partner_a, 'in_need_of_action', followup_10)
self.partner_a._execute_followup_partner(options={'snailmail': False})
self.assertPartnerFollowup(self.partner_a, 'with_overdue_invoices', followup_15)
self.env['account.payment.register'].create({
'line_ids': invoice_02.line_ids.filtered(lambda l: l.display_type == 'payment_term'),
})._create_payments()
self.assertPartnerFollowup(self.partner_a, 'with_overdue_invoices', followup_15)
# action taken 4 days ago, current delay is 15-10=5, nothing needed
with freeze_time('2022-01-15'):
self.assertPartnerFollowup(self.partner_a, 'with_overdue_invoices', followup_15)
# action taken 5 days ago, current delay is 15-10=5, need to take action
with freeze_time('2022-01-16'):
self.assertPartnerFollowup(self.partner_a, 'in_need_of_action', followup_15)
self.partner_a._execute_followup_partner(options={'snailmail': False})
self.assertPartnerFollowup(self.partner_a, 'with_overdue_invoices', followup_30)
def test_followup_status_entry_lines(self):
"""
Creating an entry should not affect the followups as there is no concept of due date with this flow.
"""
self.followup_line = self.create_followup(delay=10)
with freeze_time('2022-01-02'):
invoice = self.env['account.move'].create({
'move_type': 'entry',
'date': fields.Date.from_string('2022-01-02'),
'partner_id': self.partner_a.id,
'invoice_line_ids': [
Command.create({
'name': 'line1',
'account_id': self.company_data['default_account_revenue'].id,
'debit': 500.0,
'credit': 0.0,
}),
Command.create({
'name': 'counterpart line',
'account_id': self.company_data['default_account_receivable'].id,
'debit': 0.0,
'credit': 500.0,
})
]
})
invoice.action_post()
with freeze_time('2022-01-13'):
self.assertPartnerFollowup(self.partner_a, 'no_action_needed', self.followup_line)
def test_followup_status_residual(self):
"""
Payments for partially paid invoices should contribute their residual to the due amount.
This is required because the paid invoice's receivable line is reconciled, and thus is
not considered in the query calculating followup_status.
"""
self.followup_line = self.create_followup(delay=10)
with freeze_time('2022-01-02'):
invoice_1 = self.create_invoice('2022-01-02')
self.create_invoice('2022-01-02')
misc_payment_1 = self.env['account.move'].create({
'move_type': 'entry',
'date': fields.Date.from_string('2022-01-02'),
'partner_id': self.partner_a.id,
'invoice_line_ids': [
Command.create({
'name': 'line1',
'account_id': self.company_data['default_account_revenue'].id,
'debit': 600.0,
'credit': 0.0,
}),
Command.create({
'name': 'counterpart line',
'account_id': self.company_data['default_account_receivable'].id,
'debit': 0.0,
'credit': 600.0,
})
]
})
misc_payment_1.action_post()
(invoice_1 + misc_payment_1).line_ids.filtered(lambda l: l.account_type == 'asset_receivable').reconcile()
with freeze_time('2022-01-13'):
self.assertPartnerFollowup(self.partner_a, 'in_need_of_action', self.followup_line)
def test_followup_contacts(self):
followup_contacts = self.partner_a._get_all_followup_contacts()
billing_contact = self.env['res.partner'].browse(self.partner_a.address_get(['invoice'])['invoice'])
self.assertEqual(billing_contact, followup_contacts)
def test_followup_cron(self):
cron = self.env.ref('account_followup.ir_cron_follow_up')
followup_10 = self.create_followup(delay=10)
followup_10.auto_execute = True
self.create_invoice('2022-01-01')
# Check that no followup is automatically done if there is no action needed
with (
freeze_time('2022-01-10'),
patch.object(self.env.registry['res.partner'], '_send_followup') as patched,
self.enter_registry_test_mode(),
):
self.assertPartnerFollowup(self.partner_a, 'with_overdue_invoices', followup_10)
cron.method_direct_trigger()
patched.assert_not_called()
self.assertPartnerFollowup(self.partner_a, 'with_overdue_invoices', followup_10)
# Check that the action is taken one and only one time when there is an action needed
with (
freeze_time('2022-01-11'),
patch.object(self.env.registry['res.partner'], '_send_followup') as patched,
self.enter_registry_test_mode(),
):
self.assertPartnerFollowup(self.partner_a, 'in_need_of_action', followup_10)
cron.method_direct_trigger()
patched.assert_called_once()
self.assertPartnerFollowup(self.partner_a, 'with_overdue_invoices', followup_10)
def test_onchange_residual_amount(self):
'''
Test residual onchange on account move lines: the residual amount is
computed using an sql query. This test makes sure the computation also
works properly during onchange (on records having a NewId).
'''
invoice = self.create_invoice('2016-01-01')
self.create_invoice('2016-01-02')
self.env['account.payment.register'].with_context(active_ids=invoice.ids, active_model='account.move').create({
'payment_date': invoice.date,
'amount': 100,
})._create_payments()
self.assertRecordValues(self.partner_a, [{'total_due': 900.0}])
self.assertRecordValues(self.partner_a.unreconciled_aml_ids.sorted(), [
{'amount_residual_currency': 500.0},
{'amount_residual_currency': 400.0},
])
self.assertRecordValues(self.partner_a.unreconciled_aml_ids.sorted(), [
{'amount_residual_currency': 500.0},
{'amount_residual_currency': 400.0},
])
def test_compute_total_due(self):
self.create_invoice('2016-01-01')
self.create_invoice('2017-01-01')
self.create_invoice(fields.Date.today() + relativedelta(months=1))
self.env['account.move'].create([{
'move_type': 'in_invoice',
'invoice_date': date,
'partner_id': self.partner_a.id,
'invoice_line_ids': [Command.create({
'quantity': 1,
'price_unit': 500,
'tax_ids': [],
})]
} for date in ('2016-01-01', '2017-01-01', fields.Date.today() + relativedelta(months=1))]).action_post()
self.assertRecordValues(self.partner_a, [{
'total_due': 1500.0,
'total_overdue': 1000.0,
'total_all_due': 0.0,
'total_all_overdue': 0.0,
}])
def test_send_followup_no_due_date(self):
"""
test sending a followup report with an empty due date field
"""
self.create_followup(delay=0)
self.create_invoice('2022-01-01')
self.partner_a.unreconciled_aml_ids.write({
'date_maturity': False,
})
self.partner_a._execute_followup_partner(options={
'partner_id': self.partner_a.id,
'manual_followup': True,
'snailmail': False,
})
def test_followup_copy_data(self):
"""
Test followup report by:
- Duplicating a single record with no default
- Duplicating several records at the same time
"""
followup_50 = self.create_followup(delay=50)
followup_60 = self.create_followup(delay=60)
# Duplicate single record with no default values
followup_50_duplicate = followup_50.copy()
self.assertTrue(followup_50_duplicate)
self.assertEqual(followup_50_duplicate.name, f'75 days (copy of {followup_50.name})')
# Duplicate multiple records at the same time with no default values
multiple_followup_records = followup_50 + followup_60
multiple_followup_records_duplicate = multiple_followup_records.copy()
self.assertTrue(multiple_followup_records_duplicate)
self.assertEqual(multiple_followup_records_duplicate[0].name, f'90 days (copy of {followup_50.name})')
self.assertEqual(multiple_followup_records_duplicate[1].name, f'105 days (copy of {followup_60.name})')
def test_manual_reminder_get_template_mail_addresses(self):
"""
When opening account_followup.manual_reminder, the partner should always be in `email_recipients_ids`
When adding a template, the template's partner_to, email_cc and email_to should be added to `email_recipient_ids` as well
"""
mail_partner = self.env['res.partner'].create({
'name': 'Mai Lang',
'email': 'mail.ang@test.com',
})
mail_cc = self.env['res.partner'].create({
'name': 'John Carmac',
'email': 'john.carmac@example.me',
})
mail_template = self.env['mail.template'].create({
'name': 'reminder',
'model_id': self.env['ir.model']._get_id('res.partner'),
'email_cc': mail_cc.email,
'use_default_to': False,
})
reminder = self.env['account_followup.manual_reminder'].with_context(
active_model='res.partner',
active_ids=mail_partner.ids,
).create({'template_id': mail_template.id})
self.assertTrue(mail_partner in reminder.email_recipient_ids, "Mai Lang should be in the Email Recipients List")
reminder.template_id = mail_template
self.assertTrue(mail_cc in reminder.email_recipient_ids, "John Carmac should be in the Email Recipients list.")
self.assertTrue(mail_partner in reminder.email_recipient_ids, "Mai Lang should still be in the Email Recipients List")
def test_overdue_invoices_action_domain_includes_children_partners(self):
"""
When checking overdue invoices for a company (partner), the action [action_open_overdue_entries] domain should also include
the overdue invoices of its children partners.
"""
# Step 1: Create contacts
parent_contact = self.env['res.partner'].create({
'name': 'Parent Contact',
'is_company': True,
})
child_contact = self.env['res.partner'].create({
'name': 'Child Contact',
'parent_id': parent_contact.id,
})
# Step 2: Create an invoice for the child contact
invoice_date = fields.Date.today() - relativedelta(months=1)
invoice = self.create_invoice(invoice_date, child_contact)
invoice.invoice_date_due = invoice_date
# Step 3: Verify follow-up status and overdue invoices of the parent contact
action = parent_contact.action_open_overdue_entries()
overdue_invoices = self.env['account.move'].search(action['domain'])
self.assertIn(invoice.id, overdue_invoices.ids)
self.assertEqual(parent_contact.followup_status, 'with_overdue_invoices')
def test_journal_items_action_domain_filters_partner_posted_entries(self):
"""
The action [action_open_partner_followup_journal_items] should return journal items
belonging to the selected partner, only from posted moves, and limited
to payable/receivable accounts marked as trade (non_trade = False).
"""
test_partner = self.env['res.partner'].create({
'name': 'Journal Items with Partner',
'is_company': True,
'customer_rank': 1,
})
invoice_date = fields.Date.today() - relativedelta(months=1)
# Create invoices
normal_invoice = self.create_invoice(fields.Date.today(), test_partner)
overdue_invoice = self.create_invoice(invoice_date, test_partner)
action = test_partner.action_open_partner_followup_journal_items()
# Validate context
ctx = action["context"]
self.assertEqual(ctx.get("search_default_partner_id"), [test_partner.id])
self.assertEqual(ctx.get("search_default_posted"), 1)
# Either receivable or payable should be set to 1
self.assertEqual(ctx.get("search_default_trade_receivable"), 1)
# Non-trade filters should be disabled
self.assertEqual(ctx.get("search_default_non_trade_receivable"), 0)
self.assertEqual(ctx.get("search_default_non_trade_payable", 0), 0)
# Validate domain and results
aml = self.env["account.move.line"].search(action["domain"])
self.assertEqual(aml.ids, normal_invoice.line_ids.ids + overdue_invoice.line_ids.ids)
def test_followup_template_recipients_with_cron(self):
"""
tests that when a mail_cc is defined on a template,
even if the action is ran from a cron (and de facto from `res.partner.send_followup_email`)
the email is correctly sent
Completes `test_manual_reminder_get_template_mail_addresses`
"""
self.partner_a.email = "test@test.com"
mail_cc = self.env['res.partner'].create({
'name': 'John Carmac',
'email': 'john.carmac@example.me',
})
mail_template = self.env['mail.template'].create({
'name': 'reminder',
'model_id': self.env['ir.model']._get_id('res.partner'),
'email_cc': mail_cc.email,
'use_default_to': False,
})
followup_10 = self.create_followup(delay=10)
followup_10.mail_template_id = mail_template
self.create_invoice('2025-05-01')
with freeze_time('2025-05-12'), self.mock_mail_gateway(mail_unlink_sent=False):
options = {
'followup_line': followup_10,
'partner_id': self.partner_a.id,
}
self.partner_a.send_followup_email(options=options)
self.assertMailMail(mail_cc, 'sent', author=self.env.user.partner_id)
def test_has_moves_without_invoice(self):
"""
Test that has_moves is True for a partner referenced
only at account.move.line level (no partner at move level)
"""
partner = self.env['res.partner'].create({'name': 'Test Partner'})
move = self.env['account.move'].create({
'move_type': 'entry',
'line_ids': [
Command.create({
'partner_id': partner.id,
'account_id': self.company_data['default_account_receivable'].id,
'debit': 100.0,
'credit': 0.0,
}),
Command.create({
'account_id': self.company_data['default_account_revenue'].id,
'debit': 0.0,
'credit': 100.0,
}),
],
})
move.action_post()
self.assertTrue(partner.has_moves)