193 lines
8.8 KiB
Python
193 lines
8.8 KiB
Python
import base64
|
|
import json
|
|
import requests
|
|
|
|
from unittest import mock
|
|
|
|
from odoo import _, release, Command
|
|
from odoo.tools import file_open, html_sanitize, misc, zeep
|
|
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
|
|
|
|
|
|
class TestL10nEsEdiVerifactuCommon(AccountTestInvoicingCommon):
|
|
|
|
@classmethod
|
|
@AccountTestInvoicingCommon.setup_country('es')
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
|
|
fake_db_identifier = '7244834601315494189'
|
|
db_identifier_function = 'odoo.addons.l10n_es_edi_verifactu.models.verifactu_document.L10nEsEdiVerifactuDocument._get_db_identifier'
|
|
patch_db_identifier = mock.patch(db_identifier_function, lambda _self: fake_db_identifier)
|
|
cls.startClassPatcher(patch_db_identifier)
|
|
|
|
# Allow to see the full result of AssertionError.
|
|
cls.maxDiff = None
|
|
|
|
cls.other_currency = cls.setup_other_currency('USD')
|
|
|
|
certificate_path = 'l10n_es_edi_verifactu/demo/certificates/Certificado_RPJ_A39200019_CERTIFICADO_ENTIDAD_PRUEBAS_5_Pre.p12'
|
|
cls.certificate = cls.env['certificate.certificate'].create({
|
|
'content': base64.encodebytes(misc.file_open(certificate_path, 'rb').read()),
|
|
'pkcs12_password': '1234',
|
|
'scope': 'verifactu',
|
|
})
|
|
|
|
cls.company = cls.company_data['company']
|
|
cls.company.write({
|
|
'country_id': cls.env.ref('base.es').id,
|
|
'state_id': cls.env.ref('base.state_es_z').id,
|
|
# Use the VAT / NIF that was used to generate the responses
|
|
# This is needed to have the correct record identifiers on the invoices
|
|
'vat': 'ESA39200019',
|
|
'l10n_es_edi_verifactu_required': True,
|
|
'l10n_es_edi_verifactu_certificate_ids': [Command.set(cls.certificate.ids)],
|
|
'l10n_es_edi_verifactu_test_environment': True,
|
|
})
|
|
|
|
cls.partner_a.write({
|
|
'vat': 'BE0477472701',
|
|
'country_id': cls.env.ref('base.be').id,
|
|
})
|
|
|
|
cls.partner_b.write({
|
|
'vat': 'ESF35999705',
|
|
})
|
|
|
|
cls.product_1 = cls.env['product.product'].create({
|
|
'name': "Product 1",
|
|
})
|
|
|
|
ChartTemplate = cls.env['account.chart.template'].with_company(cls.company)
|
|
cls.tax21_goods = ChartTemplate.ref('account_tax_template_s_iva21b')
|
|
cls.tax21_services = ChartTemplate.ref('account_tax_template_s_iva21s')
|
|
cls.tax10_goods = ChartTemplate.ref('account_tax_template_s_iva10b')
|
|
cls.tax10_services = ChartTemplate.ref('account_tax_template_s_iva10s')
|
|
cls.tax1p4_services_recargo = ChartTemplate.ref('account_tax_template_s_req014')
|
|
cls.tax5p2_services_recargo = ChartTemplate.ref('account_tax_template_s_req52')
|
|
cls.tax1_withholding = ChartTemplate.ref('account_tax_template_s_irpf1')
|
|
cls.tax0_no_sujeto_loc = ChartTemplate.ref('account_tax_template_s_iva_ns')
|
|
cls.tax0_isp = ChartTemplate.ref('account_tax_template_s_iva0_isp')
|
|
cls.tax0_exento = ChartTemplate.ref('account_tax_template_s_iva0')
|
|
cls.tax0_exento_export = ChartTemplate.ref('account_tax_template_s_iva0_g_e')
|
|
# We create a 'no_sujeto' tax since there is currently no such tax in the standard chart
|
|
cls.tax0_no_sujeto = cls.tax0_no_sujeto_loc.copy()
|
|
cls.tax0_no_sujeto.l10n_es_type = 'no_sujeto'
|
|
|
|
# Everything in the tests should be possible without being administrator.
|
|
# We do not want to hide access errors the user may have in production (i.e. with access to the certificates)
|
|
cls.user.groups_id = [Command.unlink(cls.env.ref('base.group_system').id)]
|
|
|
|
# Do not do any zeep operations by default.
|
|
# I.e. do not do xml / xsd validation during tests (needs network connection to create the client).
|
|
cls.startClassPatcher(cls._mock_get_zeep_operation(None, None, None))
|
|
|
|
# Do avoid updating the test files for every version we just assume that we are in 17.0
|
|
cls.startClassPatcher(mock.patch.object(release, 'version', '17.0+e'))
|
|
|
|
@classmethod
|
|
def _read_file(cls, path, *args):
|
|
with file_open(path, *args) as f:
|
|
content = f.read()
|
|
return content
|
|
|
|
def _json_file_to_dict(self, json_file):
|
|
json_string = self._read_file(json_file, 'rb')
|
|
return json.loads(json_string)
|
|
|
|
def _mock_response(self, status_code, response_file, content_type='text/xml;charset=UTF-8'):
|
|
response = mock.Mock(spec=requests.Response)
|
|
response.status_code = status_code
|
|
response.text = self._read_file(response_file)
|
|
response.content = response.text.encode()
|
|
response.headers = {
|
|
'content-type': content_type,
|
|
}
|
|
return response
|
|
|
|
def _mock_request(self, mock_response):
|
|
request_function_path = 'odoo.addons.l10n_es_edi_verifactu.models.verifactu_document.L10nEsEdiVerifactuDocument._soap_request'
|
|
return mock.patch(request_function_path, return_value=mock_response)
|
|
|
|
def _mock_get_zeep_operation(self, registration_return_value=None, registration_xml_return_value=None):
|
|
request_function_path = 'odoo.addons.l10n_es_edi_verifactu.models.verifactu_document._get_zeep_operation'
|
|
|
|
def mocked_get_zeep_operation(company, operation):
|
|
if operation not in ('registration', 'registration_xml'):
|
|
raise NotImplementedError()
|
|
function = registration_return_value if operation == 'registration' else registration_xml_return_value
|
|
return (function, {})
|
|
|
|
return mock.patch(request_function_path, mocked_get_zeep_operation)
|
|
|
|
def _mock_zeep_registration_operation(self, response_file_json):
|
|
# Note: The real result is of type 'odoo.tools.zeep.client.SerialProxy'; here it is a dict
|
|
zeep_response_dict = json.loads(self._read_file(response_file_json))
|
|
return self._mock_get_zeep_operation(registration_return_value=lambda *args, **kwargs: zeep_response_dict)
|
|
|
|
def _mock_zeep_registration_operation_certificate_issue(self):
|
|
def _raise_certificate_error(*args, **kwargs):
|
|
certificate_error = "No autorizado. Se ha producido un error al verificar el certificado presentado"
|
|
raise zeep.exceptions.TransportError(certificate_error)
|
|
|
|
return self._mock_get_zeep_operation(registration_return_value=_raise_certificate_error)
|
|
|
|
def _mock_cron_trigger(self, cron_trigger_result_dict):
|
|
trigger_function_path = 'odoo.addons.base.models.ir_cron.ir_cron._trigger'
|
|
|
|
def _put_at_in_dict(self, at=None):
|
|
cron_trigger_result_dict['at'] = at
|
|
|
|
return mock.patch(trigger_function_path, _put_at_in_dict)
|
|
|
|
def _mock_format_document_errors(self, errors, title):
|
|
"""Mock the computation of field `errors` of model 'l10n_es_edi_verifactu.document' from a list of translated errors."""
|
|
error = {
|
|
'error_title': title,
|
|
'errors': errors,
|
|
}
|
|
return html_sanitize(self.env['account.move.send']._format_error_html(error))
|
|
|
|
def _mock_format_document_generic_errors(self, errors):
|
|
title = _("Error")
|
|
return self._mock_format_document_errors(errors, title)
|
|
|
|
def _mock_format_document_aeat_errors(self, errors):
|
|
title = _("The Veri*Factu document contains the following errors according to the AEAT")
|
|
return self._mock_format_document_errors(errors, title)
|
|
|
|
def _mock_format_document_generation_errors(self, errors):
|
|
title = _("The Veri*Factu document could not be created")
|
|
return self._mock_format_document_errors(errors, title)
|
|
|
|
def _mock_last_document(self, document):
|
|
# Note: returns the same document for all companies
|
|
function_path = 'odoo.addons.l10n_es_edi_verifactu.models.res_company.ResCompany._l10n_es_edi_verifactu_get_last_document'
|
|
return mock.patch(function_path, return_value=(document or self.env['l10n_es_edi_verifactu.document']))
|
|
|
|
def _mock_create_date(self, date):
|
|
return mock.patch.object(self.env.cr, 'now', lambda: date)
|
|
|
|
def _create_dummy_invoice(self, name=None, invoice_date=None):
|
|
# The only values we care about are the ones relevant for the record identifier.
|
|
invoice_vals = {
|
|
'move_type': 'out_invoice',
|
|
'invoice_date': '2019-01-30',
|
|
'date': '2019-01-30',
|
|
'partner_id': self.partner_b.id, # Spanish customer
|
|
'invoice_line_ids': [
|
|
Command.create({'product_id': self.product_1.id, 'price_unit': 100.0, 'tax_ids': [Command.set(self.tax21_goods.ids)]}),
|
|
],
|
|
}
|
|
|
|
# Adjust some values to give the record the needed record identifier
|
|
if invoice_date is not None:
|
|
invoice_vals['invoice_date'] = invoice_date
|
|
if name is not None:
|
|
invoice_vals['name'] = name
|
|
|
|
invoice = self.env['account.move'].create(invoice_vals)
|
|
invoice.action_post()
|
|
|
|
return invoice
|