Files
enterprise-addons/account_sepa_direct_debit/tests/test_sdd.py
T

359 lines
19 KiB
Python

# Part of Odoo. See LICENSE file for full copyright and licensing details.
import datetime
from odoo import Command, fields
from odoo.exceptions import AccessError, RedirectWarning, UserError, ValidationError
from odoo.tests import freeze_time, tagged
from odoo.tests.common import new_test_user
from odoo.addons.account_sepa_direct_debit.tests.common import SDDTestCommon
@tagged('post_install', '-at_install')
class SDDTest(SDDTestCommon):
def test_sdd(self):
# The invoices should have payments and in payment state thanks to the mandate
payments_agrolait = self.invoice_agrolait.reconciled_payment_ids
self.assertEqual(self.invoice_agrolait.payment_state, self.env['account.move']._get_invoice_in_payment_state(), 'This invoice should have payments and in payment state thanks to the mandate')
self.assertEqual(payments_agrolait.sdd_mandate_id, self.mandate_agrolait)
self.assertEqual(self.mandate_agrolait.payment_ids, payments_agrolait, 'The mandate should be linked to the payment')
payments_china_export = self.invoice_china_export.reconciled_payment_ids
self.assertEqual(self.invoice_china_export.payment_state, self.env['account.move']._get_invoice_in_payment_state(), 'This invoice should have have payments and in payment state thanks to the mandate')
self.assertEqual(payments_china_export.sdd_mandate_id, self.mandate_china_export)
self.assertEqual(self.mandate_china_export.payment_ids, payments_china_export, 'The mandate should be linked to the payment')
payments_no_bic = self.invoice_no_bic.reconciled_payment_ids
self.assertEqual(self.invoice_no_bic.payment_state, self.env['account.move']._get_invoice_in_payment_state(), 'This invoice should have payments and in payment state thanks to the mandate')
self.assertEqual(payments_no_bic.sdd_mandate_id, self.mandate_no_bic)
self.assertEqual(self.mandate_no_bic.payment_ids, payments_no_bic, 'The mandate should be linked to the payment')
# Reconcile the payments, to have the invoices fully paid
payments = (self.invoice_agrolait + self.invoice_china_export + self.invoice_no_bic).reconciled_payment_ids
self.reconcile_payments(payments)
self.env.invalidate_all() # Since field is used only in UI, Invalidate the cache for field recomputation, simulating UI view change
self.assertEqual(self.mandate_agrolait.paid_invoice_ids, self.invoice_agrolait, 'The mandate should be linked to the paid invoice')
self.assertEqual(self.mandate_china_export.paid_invoice_ids, self.invoice_china_export, 'The mandate should be linked to the paid invoice')
self.assertEqual(self.mandate_no_bic.paid_invoice_ids, self.invoice_no_bic, 'The mandate should be linked to the paid invoice')
# The 'one-off' mandate should now be closed
self.assertEqual(self.mandate_agrolait.state, 'active', 'A recurrent mandate should stay confirmed after accepting a payment')
self.assertEqual(self.mandate_china_export.state, 'closed', 'A one-off mandate should be closed after accepting a payment')
self.assertEqual(self.mandate_no_bic.state, 'closed', 'A one-off mandate should be closed after accepting a payment')
# Test when cancelling a payment
payment_agrolait = self.invoice_agrolait.reconciled_payment_ids
payment_agrolait.action_draft()
payment_agrolait.move_id.line_ids.remove_move_reconcile()
self.assertEqual(self.invoice_agrolait.payment_state, 'not_paid')
def test_xml_pain_008_001_08_generation(self):
self.sdd_company_bank_journal.debit_sepa_pain_version = 'pain.008.001.08'
for invoice in (self.invoice_agrolait, self.invoice_no_bic):
payment = invoice.reconciled_payment_ids
payment.generate_xml(self.sdd_company, fields.Date.today(), True)
payment = self.invoice_china_export.reconciled_payment_ids
# Checks that an error is thrown if the city name is missing
self.partner_china_export.write({'city': False, 'country_id': self.country_china})
with self.assertRaises(UserError):
payment.generate_xml(self.sdd_company, fields.Date.today(), True)
# Checks that the xml is correctly generated when both the city_name and country are set
self.partner_china_export.write({'city': 'China Town', 'country_id': self.country_china})
payment.generate_xml(self.sdd_company, fields.Date.today(), True)
@freeze_time('2019-01-01')
def test_expiry(self):
self.mandate_agrolait.action_revoke_mandate() # We will use a new one here
self.assertEqual(self.mandate_agrolait.state, 'revoked')
mandate = self.create_mandate(self.partner_agrolait, self.partner_bank_agrolait, False, self.sdd_company, 'CORE')
mandate.start_date = '2020-01-01'
mandate.end_date = '2025-01-30'
self.assertEqual(mandate.state, 'draft')
mandate.action_validate_mandate()
mandate.cron_update_mandates_states()
self.assertEqual(mandate.state, 'active') # The mandate should stay active even if the start_date is in the future
with freeze_time('2022-12-02'): # In the 36-month without any use automatic close 30-days warning period (to be closed the 2023-01-01)
mandates_per_validity = mandate._update_and_partition_state_by_validity()
self.assertTrue(mandates_per_validity['expiring'], 'The mandate is expiring soon')
new_invoice = self.create_invoice(self.partner_agrolait)
payment = self.pay_with_mandate(new_invoice) # Should reset the 36-month up to 2025-12-02
payment.action_validate()
mandates_per_validity = mandate._update_and_partition_state_by_validity()
self.assertTrue(mandates_per_validity['valid'], 'The mandate should not be expiring soon anymore, as we have reset the period')
new_expiry_date = next(iter(mandate._get_expiry_date_per_mandate().values())).isoformat()
self.assertEqual(new_expiry_date, '2025-01-30', 'The new expiry date is the end date, as the last collection + 36 month is after that date')
with freeze_time('2025-01-01'): # Entered the 30-days before end date warning
mandates_per_validity = mandate._update_and_partition_state_by_validity()
self.assertTrue(mandates_per_validity['expiring'], 'The mandate is expiring soon')
with freeze_time('2025-01-31'): # Passed the end_date, mandate must be closed
mandates_per_validity = mandate._update_and_partition_state_by_validity()
self.assertTrue(mandates_per_validity['invalid'], 'The mandate is expired')
self.assertEqual(mandate.state, 'closed')
def test_required_data(self):
stateless_partner = self.env['res.partner'].create({
'name': 'stateless partner',
})
stateless_iban_account = self.env['res.partner.bank'].create({
'acc_number': 'NL61INGB6008851617',
'partner_id': stateless_partner.id,
})
stateless_mandate = self.env['sdd.mandate'].create({
'partner_id': stateless_partner.id,
'partner_bank_id': stateless_iban_account.id,
})
bankless_partner = self.env['res.partner'].create({
'name': 'no bank partner',
'country_id': self.env.ref('base.nl').id,
})
not_iban_bank_account = self.env['res.partner.bank'].create({
'acc_number': '01',
'partner_id': bankless_partner.id,
})
bankless_mandate = self.env['sdd.mandate'].create({
'partner_id': bankless_partner.id,
})
# Check send and print action
with self.assertRaises(RedirectWarning, msg="The country of the partner should be set to go forward"):
stateless_mandate.action_send_and_print()
stateless_mandate.partner_id.country_id = self.env.ref('base.nl')
stateless_mandate.action_send_and_print()
bankless_mandate.action_send_and_print() # You can send a mandate request without a bank, the customer should fill the field
# Check validation action
stateless_mandate.action_validate_mandate()
with self.assertRaises(UserError, msg="No partner bank should raise an error when going forward"):
bankless_mandate.action_validate_mandate()
bankless_mandate.partner_bank_id = not_iban_bank_account
with self.assertRaises(UserError, msg="The bank account isn't an iban bank account"):
bankless_mandate.action_validate_mandate()
bankless_mandate.partner_bank_id = self.env['res.partner.bank'].create({
'acc_number': 'NL43INGB9822994664',
'partner_id': bankless_partner.id,
})
bankless_mandate.action_validate_mandate()
@freeze_time('2024-01-01')
def test_collection_date(self):
"""
Tests that the batch payment collection date doesn't fall inside the minimum period required for the customer and the bank to act
The basic idea is that these rule should all apply:
- the minimum period for the bank to react is,
- 5 days in the case of a new mandate (here we only show a warning, for users coming from other software)
- 2 days in all other cases
- the minimum period for the user to react (pre-notification period) is 14 days by SEPA default, overridden to 2 days in Odoo.
in these tests it's changed back to 14 days to test all periods separately
"""
partner = self.env['res.partner'].create({
'name': 'partner',
'country_id': self.env.ref('base.nl').id,
})
iban_account = self.env['res.partner.bank'].create({
'acc_number': 'NL61INGB6008851617',
'partner_id': partner.id,
})
# Test minimal pre-notification period
with self.assertRaises(UserError, msg="Cannot have a pre-notification period under 2 days"):
self.env['sdd.mandate'].create({
'partner_id': partner.id,
'partner_bank_id': iban_account.id,
'pre_notification_period': 1,
})
mandate = self.env['sdd.mandate'].create({
'partner_id': partner.id,
'partner_bank_id': iban_account.id,
'pre_notification_period': 2,
})
mandate.pre_notification_period = 14 # Changed to 14 to simplify the following test
mandate.action_validate_mandate()
# Test minimal "new mandates" collection date
invoice = self.create_invoice(partner)
payment = self.pay_with_mandate(invoice)
unchanged_data = {
'payment_ids': [Command.set(payment.ids)],
'journal_id': payment.journal_id.id,
}
with self.assertRaises(ValidationError, msg="Collection date should not be in the 2 day period required for the bank to process"):
self.env['account.batch.payment'].create({
**unchanged_data,
'sdd_required_collection_date': fields.Date.context_today(mandate) + datetime.timedelta(days=1)
})
# Test minimal pre-notification period collection date
batch_payment = self.env['account.batch.payment'].create({
**unchanged_data,
'sdd_required_collection_date': fields.Date.context_today(mandate) + datetime.timedelta(days=2)
})
# Check the values used to display the warning.
self.assertEqual(
batch_payment.sdd_first_time_payment_ids,
payment,
"The first-time payments should be set correctly.",
)
self.assertEqual(
batch_payment.sdd_min_required_collection_date,
fields.Date.context_today(mandate) + datetime.timedelta(days=5),
"The minimum required collection date should be set at 5 days after today.",
)
batch_payment.validate_batch()
# Test that after a mandate has been used once, the minimal period for the bank is 2 days.
# It's now possible to have batches collected 2 days in the future.
# Reconcile the previous payment with a bank statement line
self.reconcile_payments(payment)
self.assertTrue(payment.is_matched)
new_invoice = self.create_invoice(partner)
new_payment = self.pay_with_mandate(new_invoice)
unchanged_data['payment_ids'] = [Command.set(new_payment.ids)]
with self.assertRaises(
ValidationError,
msg="Collection date should not be in the 2 day period required for the bank to process if all the mandates are already used at least once"
):
self.env['account.batch.payment'].create({
**unchanged_data,
'sdd_required_collection_date': fields.Date.context_today(mandate) + datetime.timedelta(days=1)
})
# 2 days is ok
new_batch_payment = self.env['account.batch.payment'].create({
**unchanged_data,
'sdd_required_collection_date': fields.Date.context_today(mandate) + datetime.timedelta(days=2)
})
# Check the values used to display the warning.
self.assertFalse(batch_payment.sdd_first_time_payment_ids, "The first-time payments should be empty.")
self.assertFalse(
batch_payment.sdd_min_required_collection_date,
"The minimum required collection date should not be set.",
)
new_batch_payment.validate_batch()
def test_batch_register_payment_all_valids(self):
""" Test the register payment when two invoices belonging to different partners are selected and they all have valid mandates """
# China export doesn't have a valid mandate
mandate_china_export = self.create_mandate(self.partner_china_export, self.partner_bank_china_export, False, self.sdd_company)
mandate_china_export.action_validate_mandate()
invoices = self.create_invoice(self.partner_agrolait) + self.create_invoice(self.partner_china_export)
journal = self.sdd_company_bank_journal
wizard = (
self.env['account.payment.register']
.with_context({'active_ids': invoices.line_ids.ids, 'active_model': 'account.move.line'})
.create({
'journal_id': journal.id,
'payment_method_line_id': journal.inbound_payment_method_line_ids.filtered(lambda l: l.code == 'sdd').id
})
)
wizard.action_create_payments()
self.assertRecordValues(invoices.reconciled_payment_ids.sorted('partner_id'), [
{'partner_id': self.partner_agrolait.id},
{'partner_id': self.partner_china_export.id},
])
def test_batch_register_payment_mixed_valids_invalids(self):
"""
Test the register payment when two invoices belonging to different partners are selected and they don't all have valid mandates
"""
# China export doesn't have a valid mandate
invoices = self.create_invoice(self.partner_agrolait) + self.create_invoice(self.partner_china_export)
journal = self.sdd_company_bank_journal
wizard = (
self.env['account.payment.register']
.with_context({'active_ids': invoices.line_ids.ids, 'active_model': 'account.move.line'})
.create({
'journal_id': journal.id,
'payment_method_line_id': journal.inbound_payment_method_line_ids.filtered(lambda l: l.code == 'sdd').id
})
)
wizard.action_create_payments()
payments = invoices.reconciled_payment_ids
self.assertEqual(len(payments), 1, "Only one payment should be created.")
self.assertEqual(payments.partner_id, self.partner_agrolait, "The payment should be for the 'Agrolait' partner since it have a valid mandate.")
def test_batch_register_payment_all_invalids(self):
"""
Test the register payment when two invoices belonging to different partners are selected and none has a valid mandate
"""
# China export doesn't have a valid mandate
self.mandate_agrolait.action_revoke_mandate()
invoices = self.create_invoice(self.partner_agrolait) + self.create_invoice(self.partner_china_export)
journal = self.sdd_company_bank_journal
wizard = (
self.env['account.payment.register']
.with_context({'active_ids': invoices.line_ids.ids, 'active_model': 'account.move.line'})
.create({
'journal_id': journal.id,
'payment_method_line_id': journal.inbound_payment_method_line_ids.filtered(lambda l: l.code == 'sdd').id
})
)
with self.assertRaises(UserError, msg="As there is no payment that can be generated, we raise an error when trying to do so"):
wizard.action_create_payments()
def test_batch_payment_group(self):
"""
Tests that whenever we group_payments, they are indeed grouped
Two invoices with Partner A -> valid mandate
One invoice with Partner B -> valid mandate
Select the three invoices and created a grouped sepa payment
-> there should be two payments
"""
mandate_china_export = self.create_mandate(self.partner_china_export, self.partner_bank_china_export, False, self.sdd_company)
mandate_china_export.action_validate_mandate()
invoices = self.create_invoice(self.partner_agrolait) + self.create_invoice(self.partner_agrolait) + self.create_invoice(self.partner_china_export)
journal = self.sdd_company_bank_journal
wizard = self.env['account.payment.register'].with_context({'active_ids': invoices.line_ids.ids, 'active_model': 'account.move.line'}).create({
'journal_id': journal.id,
'payment_method_line_id': journal.inbound_payment_method_line_ids.filtered(lambda l: l.code == 'sdd').id,
'group_payment': True,
})
res = wizard.action_create_payments()
payments = self.env['account.payment'].search(res.get('domain', []))
self.assertEqual(len(payments), 2, "There should be two payments")
def test_register_payment_other_journal(self):
""" Test payment from a different journal than the default one """
bank_journal_copy = self.sdd_company_bank_journal.copy()
bank_journal_copy.bank_acc_number = 'CH9300762011623852958'
invoices = self.create_invoice(self.partner_agrolait)
wizard = (
self.env['account.payment.register']
.with_context({'active_ids': invoices.line_ids.ids, 'active_model': 'account.move.line'})
.create({
'journal_id': bank_journal_copy.id,
'payment_method_line_id': bank_journal_copy.inbound_payment_method_line_ids.filtered(lambda l: l.code == 'sdd').id
})
)
res = wizard.action_create_payments()
payments = self.env['account.payment'].search(res.get('domain', []))
self.assertTrue(payments, 'A payment should have been generated')
def test_user_without_bank_account_validation_role_cannot_validate_mandates(self):
"""User should have `group_validate_bank_account` group to validate SDD mandates."""
invoicing_banks_user = new_test_user(
self.env, 'invoicing_banks_user', groups='account.group_account_basic'
)
with self.assertRaisesRegex(AccessError, "validate SDD"):
self.mandate_agrolait.with_user(invoicing_banks_user).action_validate_mandate()