[ADD] l10n_it_edi: Simplified invoice

This provides the possibility to import/export simplified invoices.
Currently, the import of simplified invoices would not work and the export of invoice without customer address would be blocked.
Simplified invoice will be able to be imported and exported if the customer address is incomplete, it's a domestic invoice and the total amount is below 400€

freeze_time was removed as it was unnecesarry and posting invoice would not be done directly as the invoicing date is in the future wrt the frozen time.

closes odoo/odoo#91215

Task: 2800967
X-original-commit: 8dc17c29c7e032d94f45ec5e2e82f9dd871e4952
Signed-off-by: Josse Colpaert <jco@odoo.com>
This commit is contained in:
Davan CHIEM DAO
2022-05-12 16:05:07 +02:00
committed by dbkosky
parent d8935b5001
commit 5e73d59c7e
6 changed files with 401 additions and 28 deletions
+1
View File
@@ -21,6 +21,7 @@ E-invoice implementation
'security/ir.model.access.csv',
'data/account_edi_data.xml',
'data/invoice_it_template.xml',
'data/invoice_it_simplified_template.xml',
'views/l10n_it_view.xml',
],
'demo': [
@@ -0,0 +1,102 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<template id="account_invoice_line_it_simplified_FatturaPA">
<DatiBeniServizi>
<Descrizione>
<t t-esc="format_alphanumeric(line.name[:1000])"/>
<t t-if="not line.name" t-esc="'NO NAME'"/>
</Descrizione>
<Importo t-esc="format_monetary(line.price_total, currency)"/>
<DatiIVA>
<Imposta t-esc="format_monetary(line.price_total - line.price_subtotal, currency)"/>
</DatiIVA>
<Natura t-if="line.tax_ids.l10n_it_has_exoneration" t-esc="line.tax_ids.l10n_it_kind_exoneration"/>
</DatiBeniServizi>
</template>
<template id="account_invoice_it_simplified_FatturaPA_export">
<t t-set="currency" t-value="record.currency_id or record.company_currency_id"/>
<t t-set="bank" t-value="record.partner_bank_id"/>
<p:FatturaElettronicaSemplificata t-att-versione="formato_trasmissione" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:p="http://ivaservizi.agenziaentrate.gov.it/docs/xsd/fatture/v1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://ivaservizi.agenziaentrate.gov.it/docs/xsd/fatture/v1.0 http://www.fatturapa.gov.it/export/fatturazione/sdi/fatturapa/v1.0/Schema_del_file_xml_FatturaPA_versione_1.0.xsd">
<FatturaElettronicaHeader>
<DatiTrasmissione>
<IdTrasmittente>
<IdPaese t-esc="get_vat_country(record.company_id.vat)"/>
<IdCodice t-esc="record.company_id.l10n_it_codice_fiscale or get_vat_number(record.company_id.vat)"/>
</IdTrasmittente>
<ProgressivoInvio t-esc="format_alphanumeric(record.name.replace('/','')[-10:])"/>
<FormatoTrasmissione t-esc="formato_trasmissione"/>
<CodiceDestinatario t-if="record.commercial_partner_id.l10n_it_pa_index" t-esc="record.commercial_partner_id.l10n_it_pa_index.upper()"/>
<CodiceDestinatario t-if="not record.commercial_partner_id.l10n_it_pa_index" t-esc="'0000000'"/>
<PECDestinatario t-if="record.commercial_partner_id.l10n_it_pec_email" t-esc="record.commercial_partner_id.l10n_it_pec_email"/>
</DatiTrasmissione>
<CedentePrestatore>
<IdFiscaleIVA>
<IdPaese t-esc="get_vat_country(record.company_id.vat)"/>
<IdCodice t-esc="get_vat_number(record.company_id.vat)"/>
</IdFiscaleIVA>
<CodiceFiscale t-if="record.company_id.l10n_it_codice_fiscale" t-esc="record.company_id.l10n_it_codice_fiscale"/>
<Denominazione t-esc="format_alphanumeric(record.company_id.partner_id.display_name[:80])"/>
<t t-call="l10n_it_edi.account_invoice_it_FatturaPA_sede">
<t t-set="partner" t-value="record.company_id.partner_id"/>
</t>
<RappresentanteFiscale t-if="record.company_id.l10n_it_has_tax_representative">
<IdFiscaleIVA>
<IdPaese t-esc="get_vat_country(record.company_id.l10n_it_tax_representative_partner_id.vat)"/>
<IdCodice t-esc="get_vat_number(record.company_id.l10n_it_tax_representative_partner_id.vat)"/>
</IdFiscaleIVA>
<Anagrafica>
<Denominazione t-if="record.commercial_partner_id.is_company" t-esc="format_alphanumeric(record.commercial_partner_id.display_name[:80])"/>
<Nome t-if="not record.commercial_partner_id.is_company" t-esc="format_alphanumeric(' '.join(record.commercial_partner_id.name.split()[:1])[:60])"/>
<Cognome t-if="not record.commercial_partner_id.is_company" t-esc="format_alphanumeric(' '.join(record.commercial_partner_id.name.split()[1:])[:60])"/>
</Anagrafica>
</RappresentanteFiscale>
<IscrizioneREA t-if="record.company_id.l10n_it_has_eco_index">
<Ufficio t-esc="record.company_id.l10n_it_eco_index_office.code"/>
<NumeroREA t-esc="format_alphanumeric(record.company_id.l10n_it_eco_index_number)"/>
<CapitaleSociale t-if="record.company_id.l10n_it_eco_index_share_capital != 0" t-esc="format_numbers_two(record.company_id.l10n_it_eco_index_share_capital)"/>
<SocioUnico t-if="record.company_id.l10n_it_eco_index_sole_shareholder != 'NO'" t-esc="record.company_id.l10n_it_eco_index_sole_shareholder"/>
<StatoLiquidazione t-esc="record.company_id.l10n_it_eco_index_liquidation_state"/>
</IscrizioneREA>
<RegimeFiscale t-esc="record.company_id.l10n_it_tax_system"/>
</CedentePrestatore>
<CessionarioCommittente>
<IdentificativiFiscali>
<IdFiscaleIVA t-if="record.commercial_partner_id.vat and in_eu(record.commercial_partner_id)">
<IdPaese t-esc="get_vat_country(record.commercial_partner_id.vat)"/>
<IdCodice t-esc="get_vat_number(record.commercial_partner_id.vat)"/>
</IdFiscaleIVA>
<CodiceFiscale t-if="record.commercial_partner_id.l10n_it_codice_fiscale" t-esc="record.commercial_partner_id.l10n_it_codice_fiscale"/>
</IdentificativiFiscali>
</CessionarioCommittente>
</FatturaElettronicaHeader>
<FatturaElettronicaBody>
<DatiGenerali>
<DatiGeneraliDocumento>
<!--2.1.1-->
<TipoDocumento t-esc="document_type"/>
<Divisa t-esc="currency.name"/>
<Data t-esc="format_date(record.invoice_date)"/>
<Numero t-esc="format_alphanumeric(record.name[-20:])"/>
</DatiGeneraliDocumento>
<DatiFatturaRettificata t-if="record.move_type == 'out_refund' and record.reversed_entry_id">
<NumeroFR t-esc="format_alphanumeric(record.reversed_entry_id.name[-20:])"/>
<DataFR t-esc="format_date(record.reversed_entry_id.invoice_date)"/>
<ElementiRettificati t-esc="format_alphanumeric(record.ref[:1000])"/>
</DatiFatturaRettificata>
</DatiGenerali>
<!-- Invoice lines. -->
<t t-foreach="record.invoice_line_ids.filtered(lambda l: not l.display_type)" t-as="line">
<t t-call="l10n_it_edi.account_invoice_line_it_simplified_FatturaPA"/>
</t>
<Allegati t-if="pdf">
<NomeAttachment t-esc="format_alphanumeric(pdf_name[:60])"/>
<FormatoAttachment>PDF</FormatoAttachment>
<Attachment t-esc="pdf"/>
</Allegati>
</FatturaElettronicaBody>
</p:FatturaElettronicaSemplificata>
</template>
</data>
</odoo>
+96 -17
View File
@@ -59,6 +59,14 @@ class AccountEdiFormat(models.Model):
}
def _l10n_it_edi_check_invoice_configuration(self, invoice):
errors = self._l10n_it_edi_check_ordinary_invoice_configuration(invoice)
if not errors:
errors = self._l10n_it_edi_check_simplified_invoice_configuration(invoice)
return errors
def _l10n_it_edi_check_ordinary_invoice_configuration(self, invoice):
errors = []
seller = invoice.company_id
buyer = invoice.commercial_partner_id
@@ -98,7 +106,35 @@ class AccountEdiFormat(models.Model):
# <1.4.1>
if not buyer.vat and not buyer.l10n_it_codice_fiscale and buyer.country_id.code == 'IT':
errors.append(_("The buyer, %s, or his company must have either a VAT number either a tax code (Codice Fiscale).", buyer.display_name))
errors.append(_("The buyer, %s, or his company must have a VAT number and/or a tax code (Codice Fiscale).", buyer.display_name))
# <2.2.1>
for invoice_line in invoice.invoice_line_ids:
if not invoice_line.display_type and len(invoice_line.tax_ids) != 1:
raise UserError(_("You must select one and only one tax by line."))
for tax_line in invoice.line_ids.filtered(lambda line: line.tax_line_id):
if not tax_line.tax_line_id.l10n_it_kind_exoneration and tax_line.tax_line_id.amount == 0:
errors.append(_("%s has an amount of 0.0, you must indicate the kind of exoneration.", tax_line.name))
return errors
def _l10n_it_edi_check_simplified_invoice_configuration(self, invoice):
errors = self._l10n_it_edi_check_buyer_invoice_configuration(invoice)
template = self.env.ref('l10n_it_edi.account_invoice_it_simplified_FatturaPA_export', raise_if_not_found=False)
if errors and template:
buyer = invoice.commercial_partner_id
if ((not buyer.country_id or buyer.country_id.code == 'IT')
and (buyer.l10n_it_codice_fiscale or (buyer.vat and (buyer.vat[:2].upper() == 'IT' or buyer.vat[:2].isdecimal())))
and invoice.amount_total <= 400):
return []
return errors
def _l10n_it_edi_check_buyer_invoice_configuration(self, invoice):
errors = []
buyer = invoice.commercial_partner_id
# <1.4.2>
if not buyer.street and not buyer.street2:
@@ -112,11 +148,6 @@ class AccountEdiFormat(models.Model):
if not buyer.country_id:
errors.append(_("%s must have a country.", buyer.display_name))
# <2.2.1>
for invoice_line in invoice.invoice_line_ids:
if not invoice_line.display_type and len(invoice_line.tax_ids) != 1:
raise UserError(_("You must select one and only one tax by line."))
if any(line.quantity < 0 for line in invoice.invoice_line_ids):
errors.append(_("All quantities should be positive."))
@@ -126,6 +157,22 @@ class AccountEdiFormat(models.Model):
return errors
def _l10n_it_get_document_type(self, invoice):
errors_buyer = self._l10n_it_edi_check_buyer_invoice_configuration(invoice)
if errors_buyer: # Simplified invoice case
if invoice.move_type == 'out_invoice':
return 'TD07'
elif invoice.move_type == 'out_refund':
return 'TD08'
if invoice.move_type == 'out_invoice':
return 'TD01'
elif invoice.move_type == 'out_refund':
return 'TD04'
def _l10n_it_is_simplified_document_type(self, document_type):
return document_type in ['TD07', 'TD08', 'TD09']
# -------------------------------------------------------------------------
# Export
# -------------------------------------------------------------------------
@@ -285,14 +332,20 @@ class AccountEdiFormat(models.Model):
# TD04 == credit note
# TD05 == debit note
# TD06 == fee
# TD07 == simplified invoice
# TD08 == simplified credit note
# TD09 == simplified debit note
# For unsupported document types, just assume in_invoice, and log that the type is unsupported
elements = tree.xpath('//DatiGeneraliDocumento/TipoDocumento')
move_type = 'in_invoice'
if elements and elements[0].text and elements[0].text == 'TD04':
document_type = elements[0].text if elements else ''
if document_type and document_type in ['TD04', 'TD08']:
move_type = 'in_refund'
elif elements and elements[0].text and elements[0].text != 'TD01':
elif document_type and document_type not in ['TD01', 'TD07']:
_logger.info('Document type not managed: %s. Invoice type is set by default.', elements[0].text)
simplified = self._l10n_it_is_simplified_document_type(document_type)
# Setup the context for the Invoice Form
invoice_ctx = invoice.with_company(company) \
.with_context(default_move_type=move_type)
@@ -431,7 +484,11 @@ class AccountEdiFormat(models.Model):
invoice._compose_info_message(body_tree, './/DatiPagamento')))
# Invoice lines. <2.2.1>
elements = body_tree.xpath('.//DettaglioLinee')
if not simplified:
elements = body_tree.xpath('.//DettaglioLinee')
else:
elements = body_tree.xpath('.//DatiBeniServizi')
if elements:
for element in elements:
with invoice_form.invoice_line_ids.new() as invoice_line_form:
@@ -470,11 +527,6 @@ class AccountEdiFormat(models.Model):
if line_elements:
invoice_line_form.name = " ".join(line_elements[0].text.split())
# Price Unit.
line_elements = element.xpath('.//PrezzoUnitario')
if line_elements:
invoice_line_form.price_unit = float(line_elements[0].text)
# Quantity.
line_elements = element.xpath('.//Quantita')
if line_elements:
@@ -483,11 +535,30 @@ class AccountEdiFormat(models.Model):
invoice_line_form.quantity = 1
# Taxes
tax_element = element.xpath('.//AliquotaIVA')
percentage = None
price_subtotal = 0
if not simplified:
tax_element = element.xpath('.//AliquotaIVA')
if tax_element and tax_element[0].text:
percentage = float(tax_element[0].text)
else:
amount_element = element.xpath('.//Importo')
if amount_element and amount_element[0].text:
amount = float(amount_element[0].text)
tax_element = element.xpath('.//Aliquota')
if tax_element and tax_element[0].text:
percentage = float(tax_element[0].text)
price_subtotal = amount / (1 + percentage / 100)
else:
tax_element = element.xpath('.//Imposta')
if tax_element and tax_element[0].text:
tax_amount = float(tax_element[0].text)
price_subtotal = amount - tax_amount
percentage = round(tax_amount / price_subtotal * 100)
natura_element = element.xpath('.//Natura')
invoice_line_form.tax_ids.clear()
if tax_element and tax_element[0].text:
percentage = float(tax_element[0].text)
if percentage is not None:
if natura_element and natura_element[0].text:
l10n_it_kind_exoneration = natura_element[0].text
tax = self.env['account.tax'].search([
@@ -519,6 +590,14 @@ class AccountEdiFormat(models.Model):
percentage,
invoice_line_form.name))
# Price Unit.
if not simplified:
line_elements = element.xpath('.//PrezzoUnitario')
if line_elements:
invoice_line_form.price_unit = float(line_elements[0].text)
else:
invoice_line_form.price_unit = price_subtotal
# Discounts
discount_elements = element.xpath('.//ScontoMaggiorazione')
if discount_elements:
+9 -7
View File
@@ -147,12 +147,9 @@ class AccountMove(models.Model):
formato_trasmissione = "FPA12" if self._is_commercial_partner_pa() else "FPR12"
if self.move_type == 'out_invoice':
document_type = 'TD01'
elif self.move_type == 'out_refund':
document_type = 'TD04'
else:
document_type = 'TD0X'
document_type = self.env['account.edi.format']._l10n_it_get_document_type(self)
if self.env['account.edi.format']._l10n_it_is_simplified_document_type(document_type):
formato_trasmissione = "FSM10"
# b64encode returns a bytestring, the template tries to turn it to string,
# but only gets the repr(pdf) --> "b'<base64_data>'"
@@ -202,7 +199,12 @@ class AccountMove(models.Model):
:return: The XML content as str.
'''
template_values = self._prepare_fatturapa_export_values()
content = self.env['ir.qweb']._render('l10n_it_edi.account_invoice_it_FatturaPA_export', template_values)
if not self.env['account.edi.format']._l10n_it_is_simplified_document_type(template_values['document_type']):
content = self.env['ir.qweb']._render('l10n_it_edi.account_invoice_it_FatturaPA_export', template_values)
else:
content = self.env['ir.qweb']._render('l10n_it_edi.account_invoice_it_simplified_FatturaPA_export', template_values)
self.message_post(body=_("A simplified invoice was created instead of an ordinary one. This is because the invoice \
is a domestic invoice with a total amount of less than or equal to 400€ and the customer's address is incomplete."))
return content
def _post(self, soft=True):
@@ -0,0 +1,42 @@
<p:FatturaElettronicaSemplificata xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:p="http://ivaservizi.agenziaentrate.gov.it/docs/xsd/fatture/v1.0" xsi:schemaLocation="http://ivaservizi.agenziaentrate.gov.it/docs/xsd/fatture/v1.0 http://www.fatturapa.gov.it/export/fatturazione/sdi/fatturapa/v1.0/Schema_del_file_xml_FatturaPA_versione_1.0.xsd" versione="FSM10">
<FatturaElettronicaHeader>
<DatiTrasmissione>
<IdTrasmittente>
<IdPaese>IT</IdPaese>
<IdCodice>01234560157</IdCodice>
</IdTrasmittente>
<ProgressivoInvio>___ignore___</ProgressivoInvio>
<FormatoTrasmissione>FSM10</FormatoTrasmissione>
<CodiceDestinatario>0000000</CodiceDestinatario>
</DatiTrasmissione>
<CedentePrestatore>
<IdFiscaleIVA>
<IdPaese>IT</IdPaese>
<IdCodice>01234560157</IdCodice>
</IdFiscaleIVA>
<CodiceFiscale>01234560157</CodiceFiscale>
<Denominazione>company_2_data</Denominazione>
<Sede>
<Indirizzo>1234 Test Street </Indirizzo>
<CAP>12345</CAP>
<Comune>Prova</Comune>
<Nazione>IT</Nazione>
</Sede>
<RegimeFiscale>RF01</RegimeFiscale>
</CedentePrestatore>
<CessionarioCommittente>
</CessionarioCommittente>
</FatturaElettronicaHeader>
<FatturaElettronicaBody>
<DatiGenerali>
<DatiGeneraliDocumento>
<TipoDocumento>TD07</TipoDocumento>
<Divisa>EUR</Divisa>
<Data>2022-03-24</Data>
<Numero>___ignore___</Numero>
</DatiGeneraliDocumento>
</DatiGenerali>
<DatiBeniServizi>
</DatiBeniServizi>
</FatturaElettronicaBody>
</p:FatturaElettronicaSemplificata>
@@ -9,6 +9,7 @@ from freezegun import freeze_time
from odoo import tools
from odoo.tests import tagged
from odoo.addons.account_edi.tests.common import AccountEdiTestCommon
from odoo.exceptions import UserError
_logger = logging.getLogger(__name__)
@@ -58,6 +59,25 @@ class TestItEdi(AccountEdiTestCommon):
'is_company': True,
})
cls.italian_partner_no_address_codice = cls.env['res.partner'].create({
'name': 'Alessi',
'l10n_it_codice_fiscale': '00465840031',
'is_company': True,
})
cls.italian_partner_no_address_VAT = cls.env['res.partner'].create({
'name': 'Alessi',
'vat': 'IT00465840031',
'is_company': True,
})
cls.american_partner = cls.env['res.partner'].create({
'name': 'Alessi',
'vat': '00465840031',
'country_id': cls.env.ref('base.us').id,
'is_company': True,
})
cls.standard_line = {
'name': 'standard_line',
'quantity': 1,
@@ -65,6 +85,20 @@ class TestItEdi(AccountEdiTestCommon):
'tax_ids': [(6, 0, [cls.company.account_sale_tax_id.id])]
}
cls.standard_line_below_400 = {
'name': 'cheap_line',
'quantity': 1,
'price_unit': 100.00,
'tax_ids': [(6, 0, [cls.company.account_sale_tax_id.id])]
}
cls.standard_line_400 = {
'name': '400_line',
'quantity': 1,
'price_unit': 327.87,
'tax_ids': [(6, 0, [cls.company.account_sale_tax_id.id])]
}
cls.price_included_invoice = cls.env['account.move'].with_company(cls.company).create({
'move_type': 'out_invoice',
'invoice_date': datetime.date(2022, 3, 24),
@@ -146,6 +180,56 @@ class TestItEdi(AccountEdiTestCommon):
],
})
cls.below_400_codice_simplified_invoice = cls.env['account.move'].with_company(cls.company).create({
'move_type': 'out_invoice',
'invoice_date': datetime.date(2022, 3, 24),
'partner_id': cls.italian_partner_no_address_codice.id,
'invoice_line_ids': [
(0, 0, {
**cls.standard_line_below_400,
}),
(0, 0, {
**cls.standard_line_below_400,
'name': 'cheap_line_2',
'quantity': 2,
'price_unit': 10.0,
}),
],
})
cls.total_400_VAT_simplified_invoice = cls.env['account.move'].with_company(cls.company).create({
'move_type': 'out_invoice',
'invoice_date': datetime.date(2022, 3, 24),
'partner_id': cls.italian_partner_no_address_VAT.id,
'invoice_line_ids': [
(0, 0, {
**cls.standard_line_400,
}),
],
})
cls.more_400_simplified_invoice = cls.env['account.move'].with_company(cls.company).create({
'move_type': 'out_invoice',
'invoice_date': datetime.date(2022, 3, 24),
'partner_id': cls.italian_partner_no_address_codice.id,
'invoice_line_ids': [
(0, 0, {
**cls.standard_line,
}),
],
})
cls.non_domestic_simplified_invoice = cls.env['account.move'].with_company(cls.company).create({
'move_type': 'out_invoice',
'invoice_date': datetime.date(2022, 3, 24),
'partner_id': cls.american_partner.id,
'invoice_line_ids': [
(0, 0, {
**cls.standard_line_below_400,
}),
],
})
# We create this because we are unable to post without a proxy user existing
cls.proxy_user = cls.env['account_edi_proxy_client.user'].create({
'id_client': 'l10n_it_edi_sdicoop_test',
@@ -160,8 +244,11 @@ class TestItEdi(AccountEdiTestCommon):
cls.partial_discount_invoice._post()
cls.full_discount_invoice._post()
cls.non_latin_and_latin_invoice._post()
cls.below_400_codice_simplified_invoice._post()
cls.total_400_VAT_simplified_invoice._post()
cls.edi_basis_xml = cls._get_test_file_content('IT00470550013_basis.xml')
cls.edi_simplified_basis_xml = cls._get_test_file_content('IT00470550013_simpl.xml')
@classmethod
def _get_test_file_content(cls, filename):
@@ -170,7 +257,6 @@ class TestItEdi(AccountEdiTestCommon):
with tools.file_open(path, mode='rb') as test_file:
return test_file.read()
@freeze_time('2020-03-24')
def test_price_included_taxes(self):
""" When the tax is price included, there should be a rounding value added to the xml, if the sum(subtotals) * tax_rate is not
equal to taxable base * tax rate (there is a constraint in the edi where taxable base * tax rate = tax amount, but also
@@ -247,7 +333,6 @@ class TestItEdi(AccountEdiTestCommon):
invoice_etree = self.with_applied_xpath(invoice_etree, "<xpath expr='.//Allegati' position='replace'/>")
self.assertXmlTreeEqual(invoice_etree, expected_etree)
@freeze_time('2020-03-24')
def test_partially_discounted_invoice(self):
# The EDI can account for discounts, but a line with, for example, a 100% discount should still have
# a corresponding tax with a base amount of 0
@@ -305,7 +390,6 @@ class TestItEdi(AccountEdiTestCommon):
invoice_etree = self.with_applied_xpath(invoice_etree, "<xpath expr='.//Allegati' position='replace'/>")
self.assertXmlTreeEqual(invoice_etree, expected_etree)
@freeze_time('2020-03-24')
def test_fully_discounted_inovice(self):
invoice_etree = etree.fromstring(self.full_discount_invoice._export_as_xml())
expected_etree = self.with_applied_xpath(
@@ -340,7 +424,6 @@ class TestItEdi(AccountEdiTestCommon):
invoice_etree = self.with_applied_xpath(invoice_etree, "<xpath expr='.//Allegati' position='replace'/>")
self.assertXmlTreeEqual(invoice_etree, expected_etree)
@freeze_time('2020-03-24')
def test_non_latin_and_latin_inovice(self):
invoice_etree = etree.fromstring(self.non_latin_and_latin_invoice._export_as_xml())
expected_etree = self.with_applied_xpath(
@@ -386,3 +469,67 @@ class TestItEdi(AccountEdiTestCommon):
''')
invoice_etree = self.with_applied_xpath(invoice_etree, "<xpath expr='.//Allegati' position='replace'/>")
self.assertXmlTreeEqual(invoice_etree, expected_etree)
def test_below_400_codice_simplified_invoice(self):
invoice_etree = etree.fromstring(self.below_400_codice_simplified_invoice._export_as_xml())
expected_etree = self.with_applied_xpath(
etree.fromstring(self.edi_simplified_basis_xml),
'''
<xpath expr="//FatturaElettronicaHeader//CessionarioCommittente" position="inside">
<IdentificativiFiscali>
<CodiceFiscale>00465840031</CodiceFiscale>
</IdentificativiFiscali>
</xpath>
<xpath expr="//FatturaElettronicaBody//DatiBeniServizi" position="replace">
<DatiBeniServizi>
<Descrizione>cheap_line</Descrizione>
<Importo>122.00</Importo>
<DatiIVA>
<Imposta>22.00</Imposta>
</DatiIVA>
</DatiBeniServizi>
<DatiBeniServizi>
<Descrizione>cheap_line_2</Descrizione>
<Importo>24.40</Importo>
<DatiIVA>
<Imposta>4.40</Imposta>
</DatiIVA>
</DatiBeniServizi>
</xpath>
''')
invoice_etree = self.with_applied_xpath(invoice_etree, "<xpath expr='.//Allegati' position='replace'/>")
self.assertXmlTreeEqual(invoice_etree, expected_etree)
def test_total_400_VAT_simplified_invoice(self):
invoice_etree = etree.fromstring(self.total_400_VAT_simplified_invoice._export_as_xml())
expected_etree = self.with_applied_xpath(
etree.fromstring(self.edi_simplified_basis_xml),
'''
<xpath expr="//FatturaElettronicaHeader//CessionarioCommittente" position="inside">
<IdentificativiFiscali>
<IdFiscaleIVA>
<IdPaese>IT</IdPaese>
<IdCodice>00465840031</IdCodice>
</IdFiscaleIVA>
</IdentificativiFiscali>
</xpath>
<xpath expr="//FatturaElettronicaBody//DatiBeniServizi" position="replace">
<DatiBeniServizi>
<Descrizione>400_line</Descrizione>
<Importo>400.00</Importo>
<DatiIVA>
<Imposta>72.13</Imposta>
</DatiIVA>
</DatiBeniServizi>
</xpath>
''')
invoice_etree = self.with_applied_xpath(invoice_etree, "<xpath expr='.//Allegati' position='replace'/>")
self.assertXmlTreeEqual(invoice_etree, expected_etree)
def test_more_400_simplified_invoice(self):
with self.assertRaises(UserError):
self.more_400_simplified_invoice._post()
def test_non_domestic_simplified_invoice(self):
with self.assertRaises(UserError):
self.non_domestic_simplified_invoice._post()