mirror of
https://github.com/bringout/oca-ocb-accounting.git
synced 2026-04-24 19:42:03 +02:00
19.0 vanilla
This commit is contained in:
parent
ba20ce7443
commit
768b70e05e
2357 changed files with 1057103 additions and 712486 deletions
|
|
@ -4,17 +4,28 @@ from unittest.mock import patch
|
|||
|
||||
from odoo import Command
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from odoo.addons.account_payment.tests.common import AccountPaymentCommon
|
||||
from odoo.tests import tagged
|
||||
|
||||
from odoo.addons.account_payment.tests.common import AccountPaymentCommon
|
||||
from odoo.addons.base.models.ir_qweb import QWebError
|
||||
|
||||
|
||||
@tagged('-at_install', 'post_install')
|
||||
class TestAccountPayment(AccountPaymentCommon):
|
||||
|
||||
def test_no_amount_available_for_refund_when_no_tx(self):
|
||||
payment = self.env['account.payment'].create({'amount': 10})
|
||||
self.assertEqual(
|
||||
payment.amount_available_for_refund,
|
||||
0,
|
||||
msg="The value of `amount_available_for_refund` should be 0 when the payment was not"
|
||||
" created by a transaction."
|
||||
)
|
||||
|
||||
def test_no_amount_available_for_refund_when_not_supported(self):
|
||||
self.provider.support_refund = False
|
||||
self.provider.support_refund = 'none'
|
||||
tx = self._create_transaction('redirect', state='done')
|
||||
tx._reconcile_after_done() # Create the payment
|
||||
tx._post_process() # Create the payment
|
||||
self.assertEqual(
|
||||
tx.payment_id.amount_available_for_refund,
|
||||
0,
|
||||
|
|
@ -25,7 +36,7 @@ class TestAccountPayment(AccountPaymentCommon):
|
|||
def test_full_amount_available_for_refund_when_not_yet_refunded(self):
|
||||
self.provider.support_refund = 'full_only' # Should simply not be False
|
||||
tx = self._create_transaction('redirect', state='done')
|
||||
tx._reconcile_after_done() # Create the payment
|
||||
tx._post_process() # Create the payment
|
||||
self.assertAlmostEqual(
|
||||
tx.payment_id.amount_available_for_refund,
|
||||
tx.amount,
|
||||
|
|
@ -37,10 +48,10 @@ class TestAccountPayment(AccountPaymentCommon):
|
|||
def test_full_amount_available_for_refund_when_refunds_are_pending(self):
|
||||
self.provider.write({
|
||||
'support_refund': 'full_only', # Should simply not be False
|
||||
'support_manual_capture': True, # To create transaction in the 'authorized' state
|
||||
'support_manual_capture': 'partial', # To create transaction in the 'authorized' state
|
||||
})
|
||||
tx = self._create_transaction('redirect', state='done')
|
||||
tx._reconcile_after_done() # Create the payment
|
||||
tx._post_process() # Create the payment
|
||||
for reference_index, state in enumerate(('draft', 'pending', 'authorized')):
|
||||
self._create_transaction(
|
||||
'dummy',
|
||||
|
|
@ -61,7 +72,7 @@ class TestAccountPayment(AccountPaymentCommon):
|
|||
def test_no_amount_available_for_refund_when_fully_refunded(self):
|
||||
self.provider.support_refund = 'full_only' # Should simply not be False
|
||||
tx = self._create_transaction('redirect', state='done')
|
||||
tx._reconcile_after_done() # Create the payment
|
||||
tx._post_process() # Create the payment
|
||||
self._create_transaction(
|
||||
'dummy',
|
||||
amount=-tx.amount,
|
||||
|
|
@ -69,7 +80,7 @@ class TestAccountPayment(AccountPaymentCommon):
|
|||
state='done',
|
||||
operation='refund', # Override the computed flow
|
||||
source_transaction_id=tx.id,
|
||||
)._reconcile_after_done()
|
||||
)._post_process()
|
||||
self.assertEqual(
|
||||
tx.payment_id.amount_available_for_refund,
|
||||
0,
|
||||
|
|
@ -80,7 +91,7 @@ class TestAccountPayment(AccountPaymentCommon):
|
|||
def test_no_full_amount_available_for_refund_when_partially_refunded(self):
|
||||
self.provider.support_refund = 'partial'
|
||||
tx = self._create_transaction('redirect', state='done')
|
||||
tx._reconcile_after_done() # Create the payment
|
||||
tx._post_process() # Create the payment
|
||||
self._create_transaction(
|
||||
'dummy',
|
||||
amount=-(tx.amount / 10),
|
||||
|
|
@ -88,7 +99,7 @@ class TestAccountPayment(AccountPaymentCommon):
|
|||
state='done',
|
||||
operation='refund', # Override the computed flow
|
||||
source_transaction_id=tx.id,
|
||||
)._reconcile_after_done()
|
||||
)._post_process()
|
||||
self.assertAlmostEqual(
|
||||
tx.payment_id.amount_available_for_refund,
|
||||
tx.payment_id.amount - (tx.amount / 10),
|
||||
|
|
@ -101,7 +112,7 @@ class TestAccountPayment(AccountPaymentCommon):
|
|||
def test_refunds_count(self):
|
||||
self.provider.support_refund = 'full_only' # Should simply not be False
|
||||
tx = self._create_transaction('redirect', state='done')
|
||||
tx._reconcile_after_done() # Create the payment
|
||||
tx._post_process() # Create the payment
|
||||
for reference_index, operation in enumerate(
|
||||
('online_redirect', 'online_direct', 'online_token', 'validation', 'refund')
|
||||
):
|
||||
|
|
@ -111,7 +122,7 @@ class TestAccountPayment(AccountPaymentCommon):
|
|||
state='done',
|
||||
operation=operation, # Override the computed flow
|
||||
source_transaction_id=tx.id,
|
||||
)._reconcile_after_done()
|
||||
)._post_process()
|
||||
|
||||
self.assertEqual(
|
||||
tx.payment_id.refunds_count,
|
||||
|
|
@ -119,6 +130,21 @@ class TestAccountPayment(AccountPaymentCommon):
|
|||
msg="The refunds count should only consider transactions with operation 'refund'."
|
||||
)
|
||||
|
||||
def test_refund_message_author_is_logged_in_user(self):
|
||||
"""Ensure that the chatter message author is the user processing the refund."""
|
||||
self.provider.support_refund = 'full_only'
|
||||
|
||||
tx = self._create_transaction('redirect', state='done')
|
||||
tx._post_process()
|
||||
|
||||
with patch.object(
|
||||
self.env.registry['account.payment'], 'message_post', autospec=True
|
||||
) as message_post_mock:
|
||||
tx.action_refund()
|
||||
author_id = message_post_mock.call_args[1].get("author_id")
|
||||
|
||||
self.assertEqual(author_id, self.user.partner_id.id)
|
||||
|
||||
def test_action_post_calls_send_payment_request_only_once(self):
|
||||
payment_token = self._create_token()
|
||||
payment_without_token = self.env['account.payment'].create({
|
||||
|
|
@ -136,7 +162,7 @@ class TestAccountPayment(AccountPaymentCommon):
|
|||
|
||||
with patch(
|
||||
'odoo.addons.payment.models.payment_transaction.PaymentTransaction'
|
||||
'._send_payment_request'
|
||||
'._charge_with_token'
|
||||
) as patched:
|
||||
payment_without_token.action_post()
|
||||
patched.assert_not_called()
|
||||
|
|
@ -145,12 +171,39 @@ class TestAccountPayment(AccountPaymentCommon):
|
|||
|
||||
def test_no_payment_for_validations(self):
|
||||
tx = self._create_transaction(flow='dummy', operation='validation') # Overwrite the flow
|
||||
tx._reconcile_after_done()
|
||||
tx._post_process()
|
||||
payment_count = self.env['account.payment'].search_count(
|
||||
[('payment_transaction_id', '=', tx.id)]
|
||||
)
|
||||
self.assertEqual(payment_count, 0, msg="validation transactions should not create payments")
|
||||
|
||||
def test_payments_for_source_tx_with_children(self):
|
||||
self.provider.support_manual_capture = 'partial'
|
||||
source_tx = self._create_transaction(flow='direct', state='authorized')
|
||||
child_tx_1 = source_tx._create_child_transaction(100)
|
||||
child_tx_1._set_done()
|
||||
child_tx_2 = source_tx._create_child_transaction(source_tx.amount - 100)
|
||||
self.assertEqual(
|
||||
source_tx.state,
|
||||
'authorized',
|
||||
msg="The source transaction should be authorized when the total processed amount of its"
|
||||
" children is not equal to the source amount.",
|
||||
)
|
||||
child_tx_2._set_canceled()
|
||||
self.assertEqual(
|
||||
source_tx.state,
|
||||
'done',
|
||||
msg="The source transaction should be done when the total processed amount of its"
|
||||
" children is equal to the source amount.",
|
||||
)
|
||||
child_tx_1._post_process()
|
||||
self.assertTrue(child_tx_1.payment_id, msg="Child transactions should create payments.")
|
||||
source_tx._post_process()
|
||||
self.assertFalse(
|
||||
source_tx.payment_id,
|
||||
msg="source transactions with done or cancel children should not create payments.",
|
||||
)
|
||||
|
||||
def test_prevent_unlink_apml_with_active_provider(self):
|
||||
""" Deleting an account.payment.method.line that is related to a provider in 'test' or 'enabled' state
|
||||
should raise an error.
|
||||
|
|
@ -162,12 +215,9 @@ class TestAccountPayment(AccountPaymentCommon):
|
|||
def test_provider_journal_assignation(self):
|
||||
""" Test the computation of the 'journal_id' field and so, the link with the accounting side. """
|
||||
def get_payment_method_line(provider):
|
||||
return self.env['account.payment.method.line'].search([
|
||||
('code', '=', provider.code),
|
||||
('payment_provider_id', '=', provider.id),
|
||||
])
|
||||
return self.env['account.payment.method.line'].search([('payment_provider_id', '=', provider.id)])
|
||||
|
||||
with self.mocked_get_payment_method_information(), self.mocked_get_default_payment_method_id():
|
||||
with self.mocked_get_payment_method_information():
|
||||
journal = self.company_data['default_journal_bank']
|
||||
provider = self.provider
|
||||
self.assertRecordValues(provider, [{'journal_id': journal.id}])
|
||||
|
|
@ -180,7 +230,7 @@ class TestAccountPayment(AccountPaymentCommon):
|
|||
self.assertRecordValues(payment_method_line, [{'journal_id': copy_journal.id}])
|
||||
|
||||
# Test duplication of the provider.
|
||||
payment_method_line.payment_account_id = self.env.company.account_journal_payment_debit_account_id
|
||||
payment_method_line.payment_account_id = self.inbound_payment_method_line.payment_account_id
|
||||
copy_provider = self.provider.copy()
|
||||
self.assertRecordValues(copy_provider, [{'journal_id': False}])
|
||||
copy_provider.state = 'test'
|
||||
|
|
@ -203,3 +253,213 @@ class TestAccountPayment(AccountPaymentCommon):
|
|||
copy_provider_pml = get_payment_method_line(copy_provider)
|
||||
with self.assertRaises(ValidationError):
|
||||
journal.inbound_payment_method_line_ids = [Command.update(copy_provider_pml.id, {'payment_provider_id': provider.id})]
|
||||
|
||||
def test_generate_payment_link_with_no_invoice_line(self):
|
||||
invoice = self.invoice
|
||||
invoice.line_ids.unlink()
|
||||
payment_values = invoice._get_default_payment_link_values()
|
||||
|
||||
self.assertDictEqual(payment_values, {
|
||||
'currency_id': invoice.currency_id.id,
|
||||
'partner_id': invoice.partner_id.id,
|
||||
'open_installments': [],
|
||||
'amount': None,
|
||||
'amount_max': None,
|
||||
})
|
||||
|
||||
def test_payment_invoice_same_receivable(self):
|
||||
"""
|
||||
Test that when creating a payment transaction, the payment uses the same account_id as the related invoice
|
||||
and not the partner accound_id
|
||||
"""
|
||||
payment_term = self.env['account.payment.term'].create({
|
||||
'name': "early_payment_term",
|
||||
'company_id': self.company_data['company'].id,
|
||||
'discount_percentage': 10,
|
||||
'discount_days': 10,
|
||||
'early_discount': True,
|
||||
})
|
||||
invoice = self.env['account.move'].create({
|
||||
'move_type': 'out_invoice',
|
||||
'partner_id': self.partner.id,
|
||||
'currency_id': self.currency.id,
|
||||
'invoice_payment_term_id': payment_term.id,
|
||||
'invoice_line_ids': [
|
||||
Command.create({
|
||||
'name': 'test line',
|
||||
'price_unit': 100.0,
|
||||
'tax_ids': [Command.set(self.company_data['default_tax_sale'].ids)],
|
||||
}),
|
||||
Command.create({
|
||||
'name': 'test line 2',
|
||||
'price_unit': 100.0,
|
||||
'tax_ids': [Command.set(self.company_data['default_tax_sale'].ids)],
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
self.partner.property_account_receivable_id = self.env['account.account'].search([('name', '=', 'Account Payable')], limit=1)
|
||||
payment = self._create_transaction(
|
||||
reference='payment_3',
|
||||
flow='direct',
|
||||
state='done',
|
||||
amount=invoice.invoice_payment_term_id._get_amount_due_after_discount(
|
||||
total_amount=invoice.amount_residual,
|
||||
untaxed_amount=invoice.amount_tax,
|
||||
),
|
||||
invoice_ids=[invoice.id],
|
||||
partner_id=self.partner.id,
|
||||
)._create_payment()
|
||||
|
||||
self.assertNotEqual(self.partner.property_account_receivable_id, payment.destination_account_id)
|
||||
self.assertEqual(payment.destination_account_id, invoice.line_ids[-1].account_id)
|
||||
|
||||
def test_vendor_payment_name_remains_same_after_repost(self):
|
||||
"""
|
||||
Test that modifying and reposting a vendor payment does not change its name, except when the journal is changed.
|
||||
"""
|
||||
journal = self.company_data['default_journal_bank']
|
||||
|
||||
payment = self.env['account.payment'].create({
|
||||
'partner_id': self.partner.id,
|
||||
'partner_type': 'supplier',
|
||||
'payment_type': 'outbound',
|
||||
'amount': 10,
|
||||
'journal_id': journal.id,
|
||||
'payment_method_line_id': journal.inbound_payment_method_line_ids[0].id,
|
||||
})
|
||||
payment.action_post()
|
||||
|
||||
original_name = payment.move_id.name
|
||||
|
||||
payment2 = self.env['account.payment'].create({
|
||||
'partner_id': self.partner.id,
|
||||
'partner_type': 'supplier',
|
||||
'payment_type': 'outbound',
|
||||
'amount': 20,
|
||||
'journal_id': journal.id,
|
||||
'payment_method_line_id': journal.inbound_payment_method_line_ids[0].id,
|
||||
})
|
||||
|
||||
payment2.action_post()
|
||||
payment.move_id.button_draft()
|
||||
payment.move_id.line_ids.unlink()
|
||||
payment.amount = 30
|
||||
payment.move_id._compute_name()
|
||||
payment.move_id._post()
|
||||
|
||||
self.assertEqual(
|
||||
payment.move_id.name,
|
||||
original_name,
|
||||
"Payment name should remain the same after reposting"
|
||||
)
|
||||
|
||||
# Now try to change the journal, and check if the name is now updated
|
||||
payment.move_id.button_draft()
|
||||
new_journal = journal.copy()
|
||||
new_payment_method_line = new_journal.outbound_payment_method_line_ids[0]
|
||||
new_payment_method_line.write({'payment_account_id': payment.payment_method_line_id.payment_account_id.id})
|
||||
payment.write({
|
||||
'journal_id': new_journal.id,
|
||||
'payment_method_line_id': new_payment_method_line.id,
|
||||
})
|
||||
payment.move_id.action_post()
|
||||
self.assertNotEqual(
|
||||
payment.move_id.name,
|
||||
original_name,
|
||||
"Payment name should be updated after changing the journal"
|
||||
)
|
||||
|
||||
def test_post_process_does_not_fail_on_cancelled_invoice(self):
|
||||
""" If the payment state is 'pending' and the invoice gets cancelled, and later the payment is confirmed,
|
||||
ensure that the _post_process() method does not raise an error.
|
||||
"""
|
||||
invoice = self.env['account.move'].create({
|
||||
'move_type': 'out_invoice',
|
||||
'partner_id': self.partner.id,
|
||||
'invoice_line_ids': [
|
||||
Command.create({
|
||||
'name': 'test line',
|
||||
'price_unit': 100.0,
|
||||
}),
|
||||
],
|
||||
})
|
||||
tx = self._create_transaction(
|
||||
flow='direct',
|
||||
state='pending',
|
||||
invoice_ids=[invoice.id],
|
||||
)
|
||||
invoice.button_cancel()
|
||||
tx._set_done()
|
||||
# _post_process() shouldn't raise an error even though the invoice is cancelled
|
||||
tx._post_process()
|
||||
self.assertEqual(tx.payment_id.state, 'in_process')
|
||||
|
||||
def test_payment_token_for_invoice_partner_is_available(self):
|
||||
"""Test that the payment token of the invoice partner is available"""
|
||||
Wizard = self.env['account.payment.register'].with_context(active_model='account.move')
|
||||
with self.mocked_get_payment_method_information():
|
||||
bank_journal = self.company_data['default_journal_bank']
|
||||
payment_method_line = bank_journal.inbound_payment_method_line_ids\
|
||||
.filtered(lambda line: line.payment_provider_id == self.dummy_provider)
|
||||
self.assertTrue(payment_method_line)
|
||||
|
||||
def payment_register_wizard(invoices):
|
||||
return Wizard.with_context(active_ids=invoices.ids).create({
|
||||
'payment_method_line_id': payment_method_line.id,
|
||||
})
|
||||
|
||||
child_partner, other_child = self.env['res.partner'].create([{
|
||||
'name': name,
|
||||
'is_company': False,
|
||||
'parent_id': self.partner.id,
|
||||
} for name in ("child_partner", "other_child")])
|
||||
invoice = self.env['account.move'].create({
|
||||
'move_type': 'out_invoice',
|
||||
'partner_id': child_partner.id,
|
||||
'invoice_line_ids': [
|
||||
Command.create({
|
||||
'name': 'test line',
|
||||
'price_unit': 100.0,
|
||||
}),
|
||||
],
|
||||
})
|
||||
invoice.action_post()
|
||||
payment_token = self._create_token(partner_id=child_partner.id)
|
||||
wizard = payment_register_wizard(invoice)
|
||||
self.assertRecordValues(wizard, [{
|
||||
'suitable_payment_token_ids': payment_token.ids,
|
||||
'payment_token_id': payment_token.id,
|
||||
}])
|
||||
|
||||
# Check that tokens assigned to the specific partner as well as their
|
||||
# commercial partner can be selected.
|
||||
parent_token = self._create_token(partner_id=self.partner.id)
|
||||
wizard = payment_register_wizard(invoice)
|
||||
self.assertEqual(wizard.suitable_payment_token_ids, payment_token + parent_token)
|
||||
|
||||
# Check that payments for multiple invoices with multiple partners
|
||||
# only retrieve tokens assigned to a common commercial partner.
|
||||
other_invoice = invoice.copy({'partner_id': other_child.id})
|
||||
other_invoice.action_post()
|
||||
wizard = payment_register_wizard(invoice + other_invoice)
|
||||
self.assertEqual(wizard.suitable_payment_token_ids, parent_token)
|
||||
|
||||
def test_generate_and_send_invoice_with_qr_code(self):
|
||||
"""Test generating & sending invoices with QR codes enabled."""
|
||||
self.env.company.link_qr_code = True
|
||||
move = self.env['account.move'].create({
|
||||
'move_type': 'out_invoice',
|
||||
'partner_id': self.partner.id,
|
||||
'invoice_line_ids': [Command.create({'name': "$100", 'price_unit': 100.0})],
|
||||
})
|
||||
move.action_post()
|
||||
|
||||
with patch.object(
|
||||
move.__class__, '_generate_portal_payment_qr', wraps=move._generate_portal_payment_qr,
|
||||
) as payment_qr_mock:
|
||||
self._assert_does_not_raise(
|
||||
QWebError,
|
||||
self.env['account.move.send']._generate_and_send_invoices(move),
|
||||
)
|
||||
self.assertTrue(payment_qr_mock.called)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue