1
0
forked from Mapan/odoo17e
odoo17e-kedaikipas58/addons/l10n_ke_edi_oscu/tests/common.py
2024-12-10 09:04:09 +07:00

229 lines
9.1 KiB
Python

# Part of Odoo. See LICENSE file for full copyright and licensing details.
import os
import json
from unittest import mock
import contextlib
import requests
import logging
import time
from odoo import Command, fields
from odoo.tools.misc import file_open
from odoo.addons.account.tests.test_account_move_send import TestAccountMoveSendCommon
from odoo.addons.base.models.ir_cron import ir_cron
_logger = logging.getLogger(__name__)
class TestKeEdiCommon(TestAccountMoveSendCommon):
@classmethod
def setUpClass(cls, chart_template_ref='ke'):
super().setUpClass(chart_template_ref=chart_template_ref)
cls.is_live_test = False
cls.parent_cmc_key = os.getenv('KE_PARENT_CMC_KEY', 'cmc_key')
cls.branch_cmc_key = os.getenv('KE_BRANCH_CMC_KEY', 'cmc_key')
cls.company_data['company'].write({
'vat': 'P052112956W',
'l10n_ke_branch_code': '00',
'l10n_ke_server_mode': 'test',
'l10n_ke_oscu_cmc_key': cls.parent_cmc_key,
'l10n_ke_oscu_user_agreement': True,
})
# 20 * (seconds since epoch) modulo 2^31-1 is a pretty good initializer that leaves space for 20 invoices
# and also happens to fit in an int32. Note that 2^31 - 1 is a prime number! ;)
cls.sale_sequence_number = (int(time.time()) * 10) % (2**31 - 1)
cls.partner_a.write({
'name': 'Ralph Jr',
'street': 'The Cucumber Lounge',
'city': 'Vineland',
'zip': '00500',
'country_id': cls.env.ref('base.ke').id,
'vat': 'A000123456F',
})
cls.standard_rate_sales_tax = cls.env['account.chart.template'].ref('ST16')
cls.standard_rate_purchase_tax = cls.env['account.chart.template'].ref('PT16')
cls.reduced_rate_sales_tax = cls.env['account.chart.template'].ref('ST8')
cls.reduced_rate_purchase_tax = cls.env['account.chart.template'].ref('PT8')
cls.product_service = cls.env['product.product'].create([{
'name': 'Fiscal Optimization Consultancy',
'type': 'service',
'taxes_id': [Command.set(cls.standard_rate_sales_tax.ids)],
'supplier_taxes_id': [Command.set(cls.standard_rate_purchase_tax.ids)],
'standard_price': 100.0,
'l10n_ke_product_type_code': '3',
'l10n_ke_origin_country_id': cls.env.ref('base.ke').id,
'unspsc_code_id': cls.env['product.unspsc.code'].search([
('code', '=', '81121500'),
], limit=1).id,
'l10n_ke_packaging_unit_id': cls.env.ref('l10n_ke_edi_oscu.code_17_OU').id,
'l10n_ke_packaging_quantity': 1,
}])
currency_eur = cls.env.ref('base.EUR')
currency_eur.active = True
cls.env['res.currency.rate'].create({
'name': fields.Date.today(),
'currency_id': currency_eur.id,
'company_id': cls.company_data['company'].id,
'inverse_company_rate': '100.00',
})
cls.excise_tax = cls.env['account.tax'].create({
'name': 'Excise tax',
'amount_type': 'percent',
'amount': 30.0,
'country_id': cls.company_data['company'].account_fiscal_country_id.id,
'tax_exigibility': 'on_invoice',
'price_include': True,
'include_base_amount': True,
'invoice_repartition_line_ids': [
Command.create({'repartition_type': 'base'}),
Command.create({
'repartition_type': 'tax',
'account_id': cls.company_data['default_account_tax_sale'].id,
}),
],
'refund_repartition_line_ids': [
Command.create({'repartition_type': 'base'}),
Command.create({
'repartition_type': 'tax',
'account_id': cls.company_data['default_account_tax_sale'].id,
}),
],
})
def _test_create_branches(self):
parent = self.company_data['company']
parent.action_l10n_ke_create_branches()
branches = self.env['res.company'].search([('parent_id', '=', parent.id)])
self.assertTrue(branches[0].name.startswith('KAKAMEGA'))
self.assertTrue(branches[1].name.startswith('MOMBASA'))
expected_branches = [
{
'vat': 'P052112956W',
'l10n_ke_branch_code': '02',
},
{
'vat': 'P052112956W',
'l10n_ke_branch_code': '01',
},
]
self.assertRecordValues(branches, expected_branches)
for branch in branches:
branch.write({'country_id': self.env.ref('base.ke')})
(self.product_a | self.product_b).with_company(branch).write({'standard_price': 30})
@contextlib.contextmanager
def patch_session(self, responses):
""" Patch requests.Session in l10n_ke_edi_oscu/models/company.py """
def replace_ignore(dict_to_replace):
""" Replace `___ignore___` in the expected request JSONs by unittest.mock.ANY,
which is equal to everything. """
for k, v in dict_to_replace.items():
if v == '___ignore___':
dict_to_replace[k] = mock.ANY
return dict_to_replace
self.maxDiff = None
test_case = self
json_module = json
responses = iter(responses)
class MockedSession:
def __init__(self):
self.headers = {}
def post(self, url, json=None, timeout=None):
expected_service, expected_request_filename, response_filename = next(responses)
_, _, service = url.rpartition('/')
test_case.assertEqual(service, expected_service)
stock_services = (
'insertStockIO',
'saveStockMaster',
'selectImportItemList',
'selectItemList',
'updateImportItem',
)
module = 'l10n_ke_edi_oscu_stock' if service in stock_services else 'l10n_ke_edi_oscu'
with file_open(f'{module}/tests/expected_requests/{expected_request_filename}.json', 'rb') as expected_request_file:
try:
test_case.assertEqual(json_module.loads(expected_request_file.read(), object_hook=replace_ignore), json)
except AssertionError:
_logger.error('Unexpected request JSON for service %s', service)
raise
mock_response = mock.Mock(spec=requests.Response)
mock_response.status_code = 200
mock_response.headers = ''
with file_open(f'{module}/tests/mocked_responses/{response_filename}.json', 'rb') as response_file:
mock_response.content = response_file.read()
mock_response.text = mock_response.content.decode()
mock_response.json.side_effect = lambda: json_module.loads(mock_response.content)
return mock_response
with mock.patch('odoo.addons.l10n_ke_edi_oscu.models.res_company.requests.Session', side_effect=MockedSession, autospec=True) as mock_session:
yield mock_session
try:
next(responses)
except StopIteration:
pass
else:
test_case.fail('Not all expected calls were made!')
@contextlib.contextmanager
def patch_cron_trigger(self):
""" Decorator for patching ir.cron.trigger so that the cron gets run right after this context manager's exit. """
crons_to_trigger = []
def mock_trigger(cron, at=None):
crons_to_trigger.append(cron)
with mock.patch.object(ir_cron, '_trigger', side_effect=mock_trigger, autospec=True) as mocked_trigger:
yield mocked_trigger
# Run cron as current user (not superuser) to limit ourselves to the test company
self.env.invalidate_all()
self.env['ir.cron'].union(*crons_to_trigger).ir_actions_server_id.run()
def create_reversal(self, invoice, is_modify=False):
""" Create a credit note that reverses an invoice. """
wizard_vals = {'journal_id': invoice.journal_id.id}
wizard_reverse = self.env['account.move.reversal'].with_context(active_ids=invoice.ids, active_model='account.move').create(wizard_vals)
wizard_reverse.write({
'reason': 'Return',
'l10n_ke_reason_code_id': self.env.ref('l10n_ke_edi_oscu.code_32_06').id,
})
wizard_reverse.reverse_moves(is_modify=is_modify)
return wizard_reverse.new_move_ids
@classmethod
@contextlib.contextmanager
def set_invoice_number(cls, invoice):
if not invoice.is_sale_document():
raise NotImplementedError('`set_invoice_number` is only needed for out_invoice / out_refund!')
sequence = invoice._l10n_ke_get_invoice_sequence()
sequence.number_next_actual = cls.sale_sequence_number
try:
yield
finally:
cls.sale_sequence_number = sequence.number_next