[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:
@@ -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>
|
||||
@@ -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:
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user