Files
enterprise-addons/l10n_co_dian/tests/common.py
T

300 lines
13 KiB
Python

from freezegun import freeze_time
import random
import requests
from base64 import b64encode
from contextlib import contextmanager
from datetime import datetime, date
from os.path import join as opj
from unittest.mock import Mock, patch
import uuid
from odoo import Command
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
from odoo.tools import file_open
from odoo.addons.l10n_co_dian.models.res_partner import ResPartner
from odoo.addons.l10n_co_edi.models.res_partner import FINAL_CONSUMER_VAT
class TestCoDianCommon(AccountTestInvoicingCommon):
@classmethod
@AccountTestInvoicingCommon.setup_country('co')
def setUpClass(cls):
super().setUpClass()
cls.frozen_today = datetime(year=2024, month=1, day=30)
cls.document_path = 'odoo.addons.l10n_co_dian.models.l10n_co_dian_document.L10n_Co_DianDocument'
cls.utils_path = 'odoo.addons.l10n_co_dian.xml_utils'
with freeze_time(cls.frozen_today):
cls.key_demo = cls.env["certificate.key"].create({
"name": "Test DIAN Key",
"content": b64encode(cls._read_file('l10n_co_dian/demo/demo_key.pem', 'rb')),
"company_id": cls.company_data['company'].id,
})
cls.certificate_demo = cls.env["certificate.certificate"].create({
"name": "Test DIAN certificate",
"content": b64encode(cls._read_file('l10n_co_dian/demo/demo_cert.crt', 'rb')),
"private_key_id": cls.key_demo.id,
"company_id": cls.company_data['company'].id,
})
city_bogota = cls.env.ref('l10n_co_edi.city_co_150')
cls.company_data['company'].write({
'name': "CO Company Test",
'vat': "1018419008-5",
'zip': "110110",
'street': "CL 12A",
'city': city_bogota.name,
'state_id': city_bogota.state_id.id,
'country_id': cls.env.ref('base.co').id,
'l10n_co_edi_header_actividad_economica': '0114',
'l10n_co_dian_operation_mode_ids': [
Command.create({
'dian_software_operation_mode': 'invoice',
'dian_software_id': 'Odoo',
'dian_software_security_code': '12345',
'dian_testing_id': 'test_id',
'company_id': cls.company_data['company'].id,
}),
Command.create({
'dian_software_operation_mode': 'bill',
'dian_software_id': 'Odoo',
'dian_software_security_code': '12345',
'dian_testing_id': 'test_id',
'company_id': cls.company_data['company'].id,
}),
],
'l10n_co_dian_certificate_ids': [Command.set(cls.certificate_demo.ids)],
'l10n_co_dian_test_environment': True,
'l10n_co_dian_provider': 'dian',
})
cls.company_data['company'].partner_id.write({
'city_id': city_bogota.id,
'l10n_co_edi_obligation_type_ids': [Command.set(cls.env.ref('l10n_co_edi.obligation_type_1').ids)],
'l10n_latam_identification_type_id': cls.env.ref('l10n_co.rut').id,
})
# Journals
dian_vals = {
'l10n_co_edi_dian_authorization_number': "18760000001",
'l10n_co_edi_dian_authorization_date': date(2020, 1, 19),
'l10n_co_edi_dian_authorization_end_date': date(2030, 1, 19),
'l10n_co_edi_min_range_number': "990000000",
'l10n_co_edi_max_range_number': "995000000",
'l10n_co_dian_technical_key': "dummy_journal_technical_key",
}
cls.company_data['default_journal_sale'].write({
'code': 'SETP',
**dian_vals,
})
cls.debit_note_journal = cls.env['account.journal'].create({
'name': "DIAN Debit Note",
'l10n_co_edi_debit_note': True,
'code': "DN",
'type': "sale",
**dian_vals,
})
cls.support_document_journal = cls.env['account.journal'].create({
'name': "DIAN Support Document",
'l10n_co_edi_is_support_document': True,
'code': "DS",
'type': "purchase",
**dian_vals,
})
# Partners
cls.partner_co = cls.env['res.partner'].create({
'name': "ADQUIRIENTE DE EJEMPLO",
'property_account_receivable_id': cls.company_data['default_account_receivable'].id,
'property_account_payable_id': cls.company_data['default_account_payable'].id,
'street': "CARRERA 8 No 20-14/40",
'city': city_bogota.name,
'city_id': city_bogota.id,
'state_id': city_bogota.state_id.id,
'zip': "110110",
'country_id': cls.env.ref('base.co').id,
'vat': "900108281",
'bank_ids': [Command.create({'acc_number': "0123456789"})],
'l10n_co_edi_obligation_type_ids': cls.env.ref('l10n_co_edi.obligation_type_1'),
'l10n_latam_identification_type_id': cls.env.ref('l10n_co.rut').id,
})
cls.final_consumer = cls.env['res.partner'].create({
'name': "consumidor o usuario final",
'country_id': cls.env.ref('base.co').id,
'l10n_latam_identification_type_id': cls.env.ref('l10n_co.national_citizen_id').id,
'vat': FINAL_CONSUMER_VAT,
'l10n_co_edi_obligation_type_ids': cls.env.ref('l10n_co_edi.obligation_type_5'),
'l10n_co_edi_fiscal_regimen': '49',
})
# Taxes
cls.tax_iva_5 = cls.env["account.chart.template"].ref('l10n_co_tax_0')
cls.tax_iva_19 = cls.env["account.chart.template"].ref('l10n_co_tax_8')
cls.tax_ret_ica_1_104 = cls.env["account.chart.template"].ref('l10n_co_tax_45')
cls.tax_ret_ica_0414 = cls.env["account.chart.template"].ref('l10n_co_tax_57')
cls.tax_ret_iva_2_85 = cls.env["account.chart.template"].ref('l10n_co_tax_12')
# Sugar Taxes (need to fill 'l10n_co_edi_ref_nominal_tax' on the product !)
cls.sugar_tax_1 = cls.env['account.tax'].create({
'name': "IBUA >10gr 3500ml",
'amount_type': 'fixed',
'amount': 35 * 35, # rate of the tax = 35 (for a product with >10gr of sugar per 100ml)
'l10n_co_edi_type': cls.env.ref('l10n_co_edi.tax_type_20').id, # IBUA
})
cls.sugar_tax_2 = cls.sugar_tax_1.copy({
'name': "IBUA >6gr & <10gr 100ml",
'amount': 36, # rate of the tax = 36 (for a product with >10gr of sugar per 100ml)
})
# Products
cls.product_a.barcode = "562438192"
cls.product_b.default_code = "prod_b"
cls.product_sugar_1 = cls._create_product(
name="Coca cola 3.5L",
l10n_co_edi_ref_nominal_tax=3500,
default_code='P1111',
)
cls.product_sugar_2 = cls._create_product(
name="Sprite 100mL",
l10n_co_edi_ref_nominal_tax=100,
default_code='P2222',
)
@classmethod
def _create_move(cls, **kwargs):
with freeze_time(kwargs.pop('freeze_date', None) or cls.frozen_today):
invoice = cls.env['account.move'].create({
'partner_id': cls.partner_co.id,
'move_type': 'out_invoice',
'journal_id': cls.company_data['default_journal_sale'].id,
'invoice_line_ids': [
Command.create({'product_id': cls.product_a.id, 'tax_ids': [Command.set(cls.tax_iva_5.ids)]}),
],
**kwargs,
})
invoice.action_post()
return invoice
@classmethod
def _create_product(cls, **kwargs):
return cls.env['product.product'].create({
'name': 'Product',
'uom_id': cls.env.ref('uom.product_uom_unit').id,
'property_account_income_id': cls.company_data['default_account_revenue'].id,
'property_account_expense_id': cls.company_data['default_account_expense'].id,
**kwargs,
})
def _generate_xml(self, invoice, with_error=False):
with self._disable_get_acquirer_call():
result = self.env['account.edi.xml.ubl_dian']._export_invoice(invoice)
if with_error:
return result
else:
return result[0]
def _assert_document_dian(self, xml, file):
expected_dian = self._read_file(file, 'rb')
self.assertXmlTreeEqual(
self.get_xml_tree_from_string(xml),
self.get_xml_tree_from_string(expected_dian),
)
@classmethod
def _get_last_sent_document(cls, move):
move.ensure_one()
return cls.env['l10n_co_dian.document'] if not move.l10n_co_dian_document_ids else move.l10n_co_dian_document_ids.sorted()[0]
@classmethod
def _read_file(cls, path, *args):
with file_open(path, *args) as f:
content = f.read()
return content
def _update_invoice_from_file(self, module_name, subfolder, filename, invoice):
""" Create an attachment from a file and post it on the invoice
"""
file_path = opj(module_name, subfolder, filename)
file = self._read_file(file_path, 'rb')
attachment = self.env['ir.attachment'].create({
'name': filename,
'datas': b64encode(file),
'res_id': invoice.id,
'res_model': 'account.move',
})
invoice.message_post(message_type='comment', attachment_ids=[attachment.id])
def _assert_imported_invoice_from_file(self, subfolder, filename, invoice_vals):
""" Create an empty account.move, update the xml file, and then check the invoice values. """
journal = self.company_data['default_journal_purchase']
invoice = self.env['account.move'].create({'move_type': 'in_invoice', 'journal_id': journal.id})
self._update_invoice_from_file(
module_name='l10n_co_dian',
subfolder=subfolder,
filename=filename,
invoice=invoice,
)
invoice_vals = invoice_vals.copy()
invoice_lines = invoice_vals.pop('invoice_lines', False)
self.assertRecordValues(invoice, [invoice_vals])
if invoice_lines:
self.assertRecordValues(invoice.invoice_line_ids, invoice_lines)
@contextmanager
def _mock_uuid_generation(self):
rnd = random.Random(42)
with patch(f'{self.utils_path}._uuid1', lambda: uuid.UUID(int=rnd.getrandbits(128))):
yield
@contextmanager
def patched_document(self, method_name, return_value):
with patch.object(
self.registry['l10n_co_dian.document'],
method_name,
return_value=return_value,
):
yield
def _mocked_request(self, response_file, status_code):
mock_response = Mock(spec=requests.Response)
mock_response.status_code = status_code
mock_response.text = self._read_file(f'l10n_co_dian/tests/responses/{response_file}')
return mock_response
def _mocked_response(self, response_file, status_code):
return {
'response': self._read_file(f'l10n_co_dian/tests/responses/{response_file}'),
'status_code': status_code,
}
def _mock_get_status(self):
return self.patched_document('_get_status', self._mocked_response('GetStatus_invoice.xml', 200))
def _mock_build_and_send_request(self, response_file, status_code=200):
return patch(f'{self.utils_path}._build_and_send_request', return_value=self._mocked_response(response_file, status_code))
def _disable_get_acquirer_call(self):
return patch.object(ResPartner, attribute='_l10n_co_dian_call_get_acquirer', return_value=dict())
def _mock_send_and_print(self, move, response_file, response_code=200):
with (
self._mock_get_status(),
self._mock_build_and_send_request(response_file, response_code),
self._disable_get_acquirer_call(),
):
self.env['account.move.send.wizard'] \
.with_context(active_model=move._name, active_ids=move.ids) \
.create({}) \
.action_send_and_print()
def _mock_get_status_zip(self, move, response_file, response_code=200):
with patch.object(requests, 'post', return_value=self._mocked_request(response_file, response_code)):
move.l10n_co_dian_document_ids._get_status_zip()
def _mock_button_l10n_co_dian_fetch_numbering_range(self, journal, response_file, response_code=200):
with patch.object(requests, 'post', return_value=self._mocked_request(response_file, response_code)):
return journal.button_l10n_co_dian_fetch_numbering_range()