mirror of
https://github.com/bringout/oca-ocb-accounting.git
synced 2026-04-22 03:02:10 +02:00
Initial commit: Accounting packages
This commit is contained in:
commit
4ef34c2317
2661 changed files with 1709616 additions and 0 deletions
|
|
@ -0,0 +1,4 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import test_account_payment
|
||||
from . import test_payment_flows
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from unittest.mock import patch
|
||||
from contextlib import contextmanager
|
||||
|
||||
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
|
||||
from odoo.addons.payment.tests.common import PaymentCommon
|
||||
|
||||
|
||||
class AccountPaymentCommon(PaymentCommon, AccountTestInvoicingCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls, *kw):
|
||||
# chart_template_ref is dropped on purpose because not needed for account_payment tests.
|
||||
super().setUpClass()
|
||||
|
||||
with cls.mocked_get_payment_method_information(cls):
|
||||
cls.dummy_provider_method = cls.env['account.payment.method'].sudo().create({
|
||||
'name': 'Dummy method',
|
||||
'code': 'none',
|
||||
'payment_type': 'inbound'
|
||||
})
|
||||
cls.dummy_provider.journal_id = cls.company_data['default_journal_bank']
|
||||
|
||||
cls.account = cls.company.account_journal_payment_credit_account_id
|
||||
cls.invoice = cls.env['account.move'].create({
|
||||
'move_type': 'entry',
|
||||
'date': '2019-01-01',
|
||||
'currency_id': cls.currency_euro.id,
|
||||
'partner_id': cls.partner.id,
|
||||
'line_ids': [
|
||||
(0, 0, {
|
||||
'account_id': cls.account.id,
|
||||
'debit': 100.0,
|
||||
'credit': 0.0,
|
||||
'amount_currency': 200.0,
|
||||
}),
|
||||
(0, 0, {
|
||||
'account_id': cls.account.id,
|
||||
'debit': 0.0,
|
||||
'credit': 100.0,
|
||||
'amount_currency': -200.0,
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
def setUp(self):
|
||||
self.enable_reconcile_after_done_patcher = False
|
||||
super().setUp()
|
||||
|
||||
#=== Utils ===#
|
||||
|
||||
@contextmanager
|
||||
def mocked_get_payment_method_information(self):
|
||||
Method_get_payment_method_information = self.env['account.payment.method']._get_payment_method_information
|
||||
|
||||
def _get_payment_method_information(*args, **kwargs):
|
||||
res = Method_get_payment_method_information()
|
||||
res['none'] = {'mode': 'electronic', 'domain': [('type', '=', 'bank')]}
|
||||
return res
|
||||
|
||||
with patch.object(self.env.registry['account.payment.method'], '_get_payment_method_information', _get_payment_method_information):
|
||||
yield
|
||||
|
||||
@contextmanager
|
||||
def mocked_get_default_payment_method_id(self):
|
||||
|
||||
def _get_default_payment_method_id(*args, **kwargs):
|
||||
return self.dummy_provider_method.id
|
||||
|
||||
with patch.object(self.env.registry['payment.provider'], '_get_default_payment_method_id', _get_default_payment_method_id):
|
||||
yield
|
||||
|
||||
@classmethod
|
||||
def _prepare_provider(cls, provider_code='none', company=None, update_values=None):
|
||||
""" Override of `payment` to prepare and return the first provider matching the given
|
||||
provider and company.
|
||||
|
||||
If no provider is found in the given company, we duplicate the one from the base company.
|
||||
All other providers belonging to the same company are disabled to avoid any interferences.
|
||||
|
||||
:param str provider_code: The code of the provider to prepare.
|
||||
:param recordset company: The company of the provider to prepare, as a `res.company` record.
|
||||
:param dict update_values: The values used to update the provider.
|
||||
:return: The provider to prepare, if found.
|
||||
:rtype: recordset of `payment.provider`
|
||||
"""
|
||||
provider = super()._prepare_provider(provider_code, company, update_values)
|
||||
if not provider.journal_id:
|
||||
provider.journal_id = cls.env['account.journal'].search(
|
||||
[('company_id', '=', provider.company_id.id), ('type', '=', 'bank')],
|
||||
limit=1,
|
||||
)
|
||||
return provider
|
||||
|
|
@ -0,0 +1,205 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
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
|
||||
|
||||
|
||||
@tagged('-at_install', 'post_install')
|
||||
class TestAccountPayment(AccountPaymentCommon):
|
||||
|
||||
def test_no_amount_available_for_refund_when_not_supported(self):
|
||||
self.provider.support_refund = False
|
||||
tx = self._create_transaction('redirect', state='done')
|
||||
tx._reconcile_after_done() # Create the payment
|
||||
self.assertEqual(
|
||||
tx.payment_id.amount_available_for_refund,
|
||||
0,
|
||||
msg="The value of `amount_available_for_refund` should be 0 when the provider doesn't "
|
||||
"support refunds."
|
||||
)
|
||||
|
||||
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
|
||||
self.assertAlmostEqual(
|
||||
tx.payment_id.amount_available_for_refund,
|
||||
tx.amount,
|
||||
places=2,
|
||||
msg="The value of `amount_available_for_refund` should be that of `total` when there "
|
||||
"are no linked refunds."
|
||||
)
|
||||
|
||||
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
|
||||
})
|
||||
tx = self._create_transaction('redirect', state='done')
|
||||
tx._reconcile_after_done() # Create the payment
|
||||
for reference_index, state in enumerate(('draft', 'pending', 'authorized')):
|
||||
self._create_transaction(
|
||||
'dummy',
|
||||
amount=-tx.amount,
|
||||
reference=f'R-{tx.reference}-{reference_index + 1}',
|
||||
state=state,
|
||||
operation='refund', # Override the computed flow
|
||||
source_transaction_id=tx.id,
|
||||
)
|
||||
self.assertAlmostEqual(
|
||||
tx.payment_id.amount_available_for_refund,
|
||||
tx.payment_id.amount,
|
||||
places=2,
|
||||
msg="The value of `amount_available_for_refund` should be that of `total` when all the "
|
||||
"linked refunds are pending (not in the state 'done')."
|
||||
)
|
||||
|
||||
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
|
||||
self._create_transaction(
|
||||
'dummy',
|
||||
amount=-tx.amount,
|
||||
reference=f'R-{tx.reference}',
|
||||
state='done',
|
||||
operation='refund', # Override the computed flow
|
||||
source_transaction_id=tx.id,
|
||||
)._reconcile_after_done()
|
||||
self.assertEqual(
|
||||
tx.payment_id.amount_available_for_refund,
|
||||
0,
|
||||
msg="The value of `amount_available_for_refund` should be 0 when there is a linked "
|
||||
"refund of the full amount that is confirmed (state 'done')."
|
||||
)
|
||||
|
||||
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
|
||||
self._create_transaction(
|
||||
'dummy',
|
||||
amount=-(tx.amount / 10),
|
||||
reference=f'R-{tx.reference}',
|
||||
state='done',
|
||||
operation='refund', # Override the computed flow
|
||||
source_transaction_id=tx.id,
|
||||
)._reconcile_after_done()
|
||||
self.assertAlmostEqual(
|
||||
tx.payment_id.amount_available_for_refund,
|
||||
tx.payment_id.amount - (tx.amount / 10),
|
||||
places=2,
|
||||
msg="The value of `amount_available_for_refund` should be equal to the total amount "
|
||||
"minus the sum of the absolute amount of the refunds that are confirmed (state "
|
||||
"'done')."
|
||||
)
|
||||
|
||||
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
|
||||
for reference_index, operation in enumerate(
|
||||
('online_redirect', 'online_direct', 'online_token', 'validation', 'refund')
|
||||
):
|
||||
self._create_transaction(
|
||||
'dummy',
|
||||
reference=f'R-{tx.reference}-{reference_index + 1}',
|
||||
state='done',
|
||||
operation=operation, # Override the computed flow
|
||||
source_transaction_id=tx.id,
|
||||
)._reconcile_after_done()
|
||||
|
||||
self.assertEqual(
|
||||
tx.payment_id.refunds_count,
|
||||
1,
|
||||
msg="The refunds count should only consider transactions with operation 'refund'."
|
||||
)
|
||||
|
||||
def test_action_post_calls_send_payment_request_only_once(self):
|
||||
payment_token = self._create_token()
|
||||
payment_without_token = self.env['account.payment'].create({
|
||||
'payment_type': 'inbound',
|
||||
'partner_type': 'customer',
|
||||
'amount': 2000.0,
|
||||
'date': '2019-01-01',
|
||||
'currency_id': self.currency.id,
|
||||
'partner_id': self.partner.id,
|
||||
'journal_id': self.provider.journal_id.id,
|
||||
'payment_method_line_id': self.inbound_payment_method_line.id,
|
||||
})
|
||||
payment_with_token = payment_without_token.copy()
|
||||
payment_with_token.payment_token_id = payment_token.id
|
||||
|
||||
with patch(
|
||||
'odoo.addons.payment.models.payment_transaction.PaymentTransaction'
|
||||
'._send_payment_request'
|
||||
) as patched:
|
||||
payment_without_token.action_post()
|
||||
patched.assert_not_called()
|
||||
payment_with_token.action_post()
|
||||
patched.assert_called_once()
|
||||
|
||||
def test_no_payment_for_validations(self):
|
||||
tx = self._create_transaction(flow='dummy', operation='validation') # Overwrite the flow
|
||||
tx._reconcile_after_done()
|
||||
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_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.
|
||||
"""
|
||||
self.assertEqual(self.dummy_provider.state, 'test')
|
||||
with self.assertRaises(UserError):
|
||||
self.dummy_provider.journal_id.inbound_payment_method_line_ids.unlink()
|
||||
|
||||
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),
|
||||
])
|
||||
|
||||
with self.mocked_get_payment_method_information(), self.mocked_get_default_payment_method_id():
|
||||
journal = self.company_data['default_journal_bank']
|
||||
provider = self.provider
|
||||
self.assertRecordValues(provider, [{'journal_id': journal.id}])
|
||||
|
||||
# Test changing the journal.
|
||||
copy_journal = journal.copy()
|
||||
payment_method_line = get_payment_method_line(provider)
|
||||
provider.journal_id = copy_journal
|
||||
self.assertRecordValues(provider, [{'journal_id': copy_journal.id}])
|
||||
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
|
||||
copy_provider = self.provider.copy()
|
||||
self.assertRecordValues(copy_provider, [{'journal_id': False}])
|
||||
copy_provider.state = 'test'
|
||||
self.assertRecordValues(copy_provider, [{'journal_id': journal.id}])
|
||||
self.assertRecordValues(get_payment_method_line(copy_provider), [{
|
||||
'journal_id': journal.id,
|
||||
'payment_account_id': payment_method_line.payment_account_id.id,
|
||||
}])
|
||||
|
||||
# We are able to have both on the same journal...
|
||||
with self.assertRaises(ValidationError):
|
||||
# ...but not having both with the same name.
|
||||
provider.journal_id = journal
|
||||
|
||||
method_line = get_payment_method_line(copy_provider)
|
||||
method_line.name = "dummy (copy)"
|
||||
provider.journal_id = journal
|
||||
|
||||
# You can't have twice the same acquirer on the same journal.
|
||||
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})]
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests import tagged
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
from odoo.addons.payment.tests.http_common import PaymentHttpCommon
|
||||
from odoo.addons.account_payment.tests.common import AccountPaymentCommon
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestFlows(AccountPaymentCommon, PaymentHttpCommon):
|
||||
|
||||
def test_invoice_payment_flow(self):
|
||||
"""Test the payment of an invoice through the payment/pay route"""
|
||||
|
||||
# Pay for this invoice (no impact even if amounts do not match)
|
||||
route_values = self._prepare_pay_values()
|
||||
route_values['invoice_id'] = self.invoice.id
|
||||
tx_context = self._get_tx_checkout_context(**route_values)
|
||||
self.assertEqual(tx_context['invoice_id'], self.invoice.id)
|
||||
|
||||
# payment/transaction
|
||||
route_values = {
|
||||
k: tx_context[k]
|
||||
for k in [
|
||||
'amount',
|
||||
'currency_id',
|
||||
'reference_prefix',
|
||||
'partner_id',
|
||||
'access_token',
|
||||
'landing_route',
|
||||
'invoice_id',
|
||||
]
|
||||
}
|
||||
route_values.update({
|
||||
'flow': 'direct',
|
||||
'payment_option_id': self.provider.id,
|
||||
'tokenization_requested': False,
|
||||
})
|
||||
with mute_logger('odoo.addons.payment.models.payment_transaction'):
|
||||
processing_values = self._get_processing_values(**route_values)
|
||||
tx_sudo = self._get_tx(processing_values['reference'])
|
||||
# Note: strangely, the check
|
||||
# self.assertEqual(tx_sudo.invoice_ids, invoice)
|
||||
# doesn't work, and cache invalidation doesn't work either.
|
||||
self.invoice.invalidate_recordset(['transaction_ids'])
|
||||
self.assertEqual(self.invoice.transaction_ids, tx_sudo)
|
||||
Loading…
Add table
Add a link
Reference in a new issue