[ADD] l10n_it_edi: Electronic invoicing Italy

- Generate e-invoice in xml when validating invoice
- e-invoice is sent via PEC mail and attached to the invoice
- electronic vendor bills and error/receipt messages can
be received through an IMAP incoming pec server
(configured in settings > technical)

We use the pec mail system to send invoices.  We don't manage
signed invoices for B2G.

Configuration is done on the company and the receiving partners
should have the correct codice fiscale / TVA.  On the taxes,
you might need to configure the exonerations.

You will need to configure the government mail address on the
company.  (sdi01...)  You might need to modify after the first
incoming message.

closes odoo/odoo#30845
This commit is contained in:
jbm-odoo
2019-02-13 08:33:06 +00:00
committed by Josse Colpaert
parent 06cfdbaab8
commit 3684c8a62e
15 changed files with 2750 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import models
from . import tests
+22
View File
@@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': 'Italy - E-invoicing',
'version': '0.3',
'depends': ['l10n_it'],
'author': 'Odoo',
'description': """
E-invoice implementation
""",
'category': 'Localization',
'website': 'http://www.odoo.com/',
'data': [
'security/ir.model.access.csv',
'data/invoice_it_template.xml',
'views/l10n_it_view.xml',
],
'demo': [
'data/account_invoice_demo.xml',
],
}
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- add VAT, codice fiscal and tax system for main company -->
<record id="base.main_company" model="res.company">
<field name="vat">IT00410123456</field>
<field name="l10n_it_codice_fiscale">0123456789987654</field>
<field name="l10n_it_tax_system">RF01</field>
<field name="zip">12345</field>
</record>
<record id="base.res_partner_1" model="res.partner">
<field name="vat">IT11111111123</field>
</record>
<record id="base.res_partner_2" model="res.partner">
<field name="vat">IT11111111234</field>
</record>
<record id="base.res_partner_12" model="res.partner">
<field name="vat">IT11111112345</field>
</record>
</data>
</odoo>
@@ -0,0 +1,175 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<template id="account_invoice_line_it_FatturaPA">
<t t-set="taxes" t-value="line.invoice_line_tax_ids.compute_all(line.price_unit)"/>
<DettaglioLinee>
<NumeroLinea t-esc="line_counter"/>
<CodiceArticolo t-if="line.product_id.barcode">
<!--2.2.1.3-->
<CodiceTipo>EAN</CodiceTipo>
<CodiceValore t-esc="line.product_id.barcode"/>
</CodiceArticolo>
<CodiceArticolo t-if="line.product_id.default_code">
<CodiceTipo>INTERNAL</CodiceTipo>
<CodiceValore t-esc="line.product_id.default_code"/>
</CodiceArticolo>
<Descrizione>
<t t-esc="line.name[:1000]"/>
<t t-if="not line.name" t-esc="'NO NAME'"/>
</Descrizione>
<Quantita t-esc="format_numbers(line.quantity)"/>
<UnitaMisura t-if="line.uom_id.category_id.measure_type != 'unit'" t-esc="line.uom_id.name"/>
<PrezzoUnitario t-esc="format_monetary(taxes['total_excluded'], currency)"/>
<ScontoMaggiorazione t-if="line.discount != 0">
<!-- [2.2.1.10] -->
<Tipo t-esc="discount_type(line.discount)"/>
<Percentuale t-esc="format_numbers(abs(line.discount))"/>
</ScontoMaggiorazione>
<PrezzoTotale t-esc="format_monetary(line.price_subtotal, currency)"/>
<!-- without tax, must include any discounts and any extra charge-->
<AliquotaIVA t-if="line.invoice_line_tax_ids.amount_type == 'percent'" t-esc="format_numbers(line.invoice_line_tax_ids.amount)"/>
<AliquotaIVA t-if="line.invoice_line_tax_ids.amount_type != 'percent'" t-esc="'0.00'"/>
<Natura t-if="line.invoice_line_tax_ids.l10n_it_has_exoneration" t-esc="line.invoice_line_tax_ids.l10n_it_kind_exoneration"/>
</DettaglioLinee>
</template>
<template id="account_invoice_it_FatturaPA_sede">
<Sede>
<Indirizzo><t t-if="partner.street" t-esc="partner.street"/> <t t-if="partner.street2" t-esc="partner.street2"/></Indirizzo>
<CAP><t t-if="partner.country_id.code != 'IT'" t-esc="'00000'"/><t t-else="" t-esc="partner.zip"/></CAP>
<Comune t-esc="partner.city"/>
<Provincia t-if="partner.country_id.code == 'IT'" t-esc="partner.state_id.code"/>
<Nazione t-esc="partner.country_id.code"/>
</Sede>
</template>
<template id="account_invoice_it_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:FatturaElettronica 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.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://ivaservizi.agenziaentrate.gov.it/docs/xsd/fatture/v1.2 http://www.fatturapa.gov.it/export/fatturazione/sdi/fatturapa/v1.2/Schema_del_file_xml_FatturaPA_versione_1.2.xsd">
<FatturaElettronicaHeader>
<DatiTrasmissione>
<IdTrasmittente>
<IdPaese t-esc="get_vat_country(record.company_id.vat)"/>
<IdCodice t-esc="record.company_id.l10n_it_codice_fiscale"/>
</IdTrasmittente>
<ProgressivoInvio t-esc="record.number.replace('/','')[-10:]"/>
<FormatoTrasmissione t-esc="formato_trasmissione"/>
<CodiceDestinatario t-if="record.commercial_partner_id.l10n_it_pa_index and record.commercial_partner_id.country_id.code == 'IT'" t-esc="record.commercial_partner_id.l10n_it_pa_index.upper()"/>
<CodiceDestinatario t-if="not record.commercial_partner_id.l10n_it_pa_index and record.commercial_partner_id.country_id.code == 'IT'" t-esc="'0000000'"/>
<CodiceDestinatario t-if="record.commercial_partner_id.country_id.code != 'IT'" t-esc="'XXXXXXX'"/>
<ContattiTrasmittente>
<Telefono t-if="format_phone(record.company_id.partner_id.phone)" t-esc="format_phone(record.company_id.partner_id.phone)"/>
<Telefono t-if="not format_phone(record.company_id.partner_id.phone) and format_phone(record.company_id.partner_id.mobile)" t-esc="format_phone(record.company_id.partner_id.mobile)"/>
<Email t-if="record.company_id.email" t-esc="record.company_id.email"/>
</ContattiTrasmittente>
<PECDestinatario t-if="record.commercial_partner_id.l10n_it_pec_email" t-esc="record.commercial_partner_id.l10n_it_pec_email"/>
</DatiTrasmissione>
<CedentePrestatore>
<DatiAnagrafici>
<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"/>
<Anagrafica>
<Denominazione t-esc="record.company_id.partner_id.display_name"/>
</Anagrafica>
<RegimeFiscale t-esc="record.company_id.l10n_it_tax_system"/>
</DatiAnagrafici>
<t t-call="l10n_it_edi.account_invoice_it_FatturaPA_sede">
<t t-set="partner" t-value="record.company_id.partner_id"/>
</t>
<IscrizioneREA t-if="record.company_id.l10n_it_has_eco_index">
<!--1.2.4-->
<Ufficio t-esc="record.company_id.l10n_it_eco_index_office.code"/>
<NumeroREA t-esc="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>
</CedentePrestatore>
<RappresentanteFiscale t-if="record.company_id.l10n_it_has_tax_representative">
<!--1.3-->
<DatiAnagrafici>
<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>
<CodiceFiscale t-if="record.company_id.l10n_it_tax_representative_partner_id.l10n_it_codice_fiscale" t-esc="record.company_id.l10n_it_tax_representative_partner_id.l10n_it_codice_fiscale"/>
<Anagrafica>
<Denominazione t-if="record.company_id.l10n_it_tax_representative_partner_id.is_company" t-esc="record.company_id.l10n_it_tax_representative_partner_id.display_name"/>
<Nome t-if="not record.company_id.l10n_it_tax_representative_partner_id.is_company" t-esc="record.company_id.l10n_it_tax_representative_partner_id.name"/>
<Cognome t-if="not record.company_id.l10n_it_tax_representative_partner_id.is_company" t-esc="record.company_id.l10n_it_tax_representative_partner_id.name"/>
</Anagrafica>
</DatiAnagrafici>
</RappresentanteFiscale>
<CessionarioCommittente>
<DatiAnagrafici>
<IdFiscaleIVA t-if="record.commercial_partner_id.vat">
<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="not record.commercial_partner_id.vat" t-esc="record.commercial_partner_id.l10n_it_codice_fiscale"/>
<Anagrafica>
<Denominazione t-if="record.commercial_partner_id.is_company" t-esc="record.commercial_partner_id.display_name"/>
<Nome t-if="not record.commercial_partner_id.is_company" t-esc="record.commercial_partner_id.name"/>
<Cognome t-if="not record.commercial_partner_id.is_company" t-esc="record.commercial_partner_id.name"/>
</Anagrafica>
</DatiAnagrafici>
<t t-call="l10n_it_edi.account_invoice_it_FatturaPA_sede">
<t t-set="partner" t-value="record.commercial_partner_id"/>
</t>
</CessionarioCommittente>
</FatturaElettronicaHeader>
<FatturaElettronicaBody>
<DatiGenerali>
<DatiGeneraliDocumento>
<!--2.1.1-->
<TipoDocumento t-esc="document_type"/>
<Divisa t-esc="currency.name"/>
<Data t-esc="format_date(record.date_invoice)"/>
<Numero t-esc="record.number[-20:]"/>
<DatiBollo t-if="record.l10n_it_stamp_duty">
<!--2.1.1.6-->
<BolloVirtuale>SI</BolloVirtuale>
<ImportoBollo t-esc="format_numbers(record.l10n_it_stamp_duty)"/>
</DatiBollo>
</DatiGeneraliDocumento>
<DatiDDT t-if="record.l10n_it_ddt_id">
<!--2.1.8-->
<NumeroDDT t-esc="record.l10n_it_ddt_id.name"/>
<DataDDT t-esc="format_date(record.l10n_it_ddt_id.date)"/>
</DatiDDT>
</DatiGenerali>
<DatiBeniServizi>
<!-- Invoice lines. -->
<t t-set="line_counter" t-value="0"/>
<t t-foreach="record.invoice_line_ids.filtered(lambda l: not l.display_type)" t-as="line">
<t t-set="line_counter" t-value="line_counter + 1"/>
<t t-call="l10n_it_edi.account_invoice_line_it_FatturaPA"/>
</t>
<t t-foreach="record.tax_line_ids" t-as="tax_line">
<DatiRiepilogo>
<!--2.2.2-->
<AliquotaIVA t-esc="format_numbers(tax_line.tax_id.amount)"/>
<Natura t-if="tax_line.tax_id.l10n_it_has_exoneration" t-esc="tax_line.tax_id.l10n_it_kind_exoneration"/>
<ImponibileImporto t-esc="format_monetary(tax_line.base, currency)"/>
<Imposta t-esc="format_monetary(tax_line.amount, currency)"/>
<EsigibilitaIVA t-if="not tax_line.tax_id.l10n_it_has_exoneration or tax_line.tax_id.l10n_it_kind_exoneration=='N6'" t-esc="tax_line.tax_id.l10n_it_vat_due_date"/>
<RiferimentoNormativo t-if="tax_line.tax_id.l10n_it_has_exoneration" t-esc="tax_line.tax_id.l10n_it_law_reference"/>
</DatiRiepilogo>
</t>
</DatiBeniServizi>
<Allegati t-if="pdf">
<NomeAttachment t-esc="pdf_name"/>
<FormatoAttachment>PDF</FormatoAttachment>
<Attachment t-esc="pdf"/>
</Allegati>
</FatturaElettronicaBody>
</p:FatturaElettronica>
</template>
</data>
</odoo>
+946
View File
@@ -0,0 +1,946 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * l10n_it_edi
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 12.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-02-13 08:28+0000\n"
"PO-Revision-Date: 2019-02-13 08:28+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: l10n_it_edi
#: code:addons/l10n_it_edi/models/account_invoice.py:129
#, python-format
msgid "%s has an amount of 0.0, you must indicate the kind of exoneration."
msgstr ""
#. module: l10n_it_edi
#: code:addons/l10n_it_edi/models/account_invoice.py:264
#, python-format
msgid "%s isn't in a right state. It must be in a 'Not yet send' or 'Invalid' state."
msgstr ""
#. module: l10n_it_edi
#: code:addons/l10n_it_edi/models/account_invoice.py:82
#, python-format
msgid "%s must have a VAT number"
msgstr ""
#. module: l10n_it_edi
#: code:addons/l10n_it_edi/models/account_invoice.py:102
#: code:addons/l10n_it_edi/models/account_invoice.py:118
#, python-format
msgid "%s must have a city."
msgstr ""
#. module: l10n_it_edi
#: code:addons/l10n_it_edi/models/account_invoice.py:88
#, python-format
msgid "%s must have a codice fiscale number"
msgstr ""
#. module: l10n_it_edi
#: code:addons/l10n_it_edi/models/account_invoice.py:78
#, python-format
msgid "%s must have a country"
msgstr ""
#. module: l10n_it_edi
#: code:addons/l10n_it_edi/models/account_invoice.py:104
#: code:addons/l10n_it_edi/models/account_invoice.py:120
#, python-format
msgid "%s must have a country."
msgstr ""
#. module: l10n_it_edi
#: code:addons/l10n_it_edi/models/account_invoice.py:100
#: code:addons/l10n_it_edi/models/account_invoice.py:116
#, python-format
msgid "%s must have a post code of length 5."
msgstr ""
#. module: l10n_it_edi
#: code:addons/l10n_it_edi/models/account_invoice.py:98
#: code:addons/l10n_it_edi/models/account_invoice.py:114
#, python-format
msgid "%s must have a post code."
msgstr ""
#. module: l10n_it_edi
#: code:addons/l10n_it_edi/models/account_invoice.py:96
#: code:addons/l10n_it_edi/models/account_invoice.py:112
#, python-format
msgid "%s must have a street."
msgstr ""
#. module: l10n_it_edi
#: code:addons/l10n_it_edi/models/account_invoice.py:736
#, python-format
msgid "'Scissione dei pagamenti' is not compatible with exoneration of kind 'N6'"
msgstr ""
#. module: l10n_it_edi
#: model_terms:ir.ui.view,arch_db:l10n_it_edi.account_invoice_form_l10n_it
msgid "<i class=\"fa fa-check\" aria-label=\"Delivered\" title=\"Delivered\"/>"
msgstr ""
#. module: l10n_it_edi
#: model_terms:ir.ui.view,arch_db:l10n_it_edi.account_invoice_form_l10n_it
msgid "<i class=\"fa fa-exclamation-triangle\"/> E-Invoice check failed. You can modify the invoice, and resend it."
msgstr ""
#. module: l10n_it_edi
#: model_terms:ir.ui.view,arch_db:l10n_it_edi.account_invoice_form_l10n_it
msgid "<i class=\"fa fa-paper-plane-o\"/> E-Invoice sent, waiting for a response"
msgstr ""
#. module: l10n_it_edi
#: code:addons/l10n_it_edi/models/account_invoice.py:662
#, python-format
msgid "Attachment from XML"
msgstr ""
#. module: l10n_it_edi
#: code:addons/l10n_it_edi/models/account_invoice.py:489
#: code:addons/l10n_it_edi/models/account_invoice.py:502
#, python-format
msgid "Bank account not found, useful informations from XML file:"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,field_description:l10n_it_edi.field_res_company__l10n_it_codice_fiscale
#: model:ir.model.fields,field_description:l10n_it_edi.field_res_partner__l10n_it_codice_fiscale
#: model:ir.model.fields,field_description:l10n_it_edi.field_res_users__l10n_it_codice_fiscale
msgid "Codice Fiscale"
msgstr ""
#. module: l10n_it_edi
#: sql_constraint:res.partner:0
msgid "Codice fiscale must have between 11 and 16 characters."
msgstr ""
#. module: l10n_it_edi
#: model:ir.model,name:l10n_it_edi.model_res_company
msgid "Companies"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,field_description:l10n_it_edi.field_res_company__l10n_it_address_send_fatturapa
msgid "Company PEC-mail"
msgstr ""
#. module: l10n_it_edi
#: model_terms:ir.ui.view,arch_db:l10n_it_edi.res_company_form_l10n_it
msgid "Company have a tax representative"
msgstr ""
#. module: l10n_it_edi
#: model_terms:ir.ui.view,arch_db:l10n_it_edi.res_company_form_l10n_it
msgid "Company listed on the register of companies"
msgstr ""
#. module: l10n_it_edi
#: code:addons/l10n_it_edi/models/account_invoice.py:330
#, python-format
msgid "Company not found with codice fiscale: %s. The company's user is set by default."
msgstr ""
#. module: l10n_it_edi
#: code:addons/l10n_it_edi/models/account_invoice.py:332
#, python-format
msgid "Company not found. The company's user is set by default."
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,help:l10n_it_edi.field_res_company__l10n_it_mail_pec_server_id
msgid "Configure your PEC-mail server to send electronic invoices."
msgstr ""
#. module: l10n_it_edi
#: model:ir.model,name:l10n_it_edi.model_res_partner
msgid "Contact"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,field_description:l10n_it_edi.field_l10n_it_ddt__create_uid
msgid "Created by"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,field_description:l10n_it_edi.field_l10n_it_ddt__create_date
msgid "Created on"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,field_description:l10n_it_edi.field_account_invoice__l10n_it_ddt_id
#: model:ir.ui.menu,name:l10n_it_edi.menu_action_ddt_account
msgid "DDT"
msgstr ""
#. module: l10n_it_edi
#: code:addons/l10n_it_edi/models/account_invoice.py:627
#, python-format
msgid "DISCOUNT: "
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,field_description:l10n_it_edi.field_l10n_it_ddt__date
msgid "Data DDT"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,field_description:l10n_it_edi.field_account_invoice__l10n_it_stamp_duty
msgid "Dati Bollo"
msgstr ""
#. module: l10n_it_edi
#: model_terms:ir.ui.view,arch_db:l10n_it_edi.invoice_supplier_tree_l10n_it
msgid "Delivered"
msgstr ""
#. module: l10n_it_edi
#: selection:account.invoice,l10n_it_send_state:0
msgid "Delivery impossible, ES certify that it has received the invoice and that the file could not be delivered to the addressee"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,field_description:l10n_it_edi.field_l10n_it_ddt__display_name
msgid "Display Name"
msgstr ""
#. module: l10n_it_edi
#: code:addons/l10n_it_edi/models/account_invoice.py:314
#, python-format
msgid "Document type not managed: %s."
msgstr ""
#. module: l10n_it_edi
#: code:addons/l10n_it_edi/models/ir_mail_server.py:225
#, python-format
msgid "E-Invoice is delivery to the destinatory:<br/>%s"
msgstr ""
#. module: l10n_it_edi
#: code:addons/l10n_it_edi/models/account_invoice.py:166
#, python-format
msgid "E-Invoice is generated on %s by %s"
msgstr ""
#. module: l10n_it_edi
#: code:addons/l10n_it_edi/models/account_invoice.py:747
#, python-format
msgid "E-invoice already exist: %s"
msgstr ""
#. module: l10n_it_edi
#: model_terms:ir.ui.view,arch_db:l10n_it_edi.view_account_invoice_filter_l10n_it
msgid "E-invoice delivered"
msgstr ""
#. module: l10n_it_edi
#: model_terms:ir.ui.view,arch_db:l10n_it_edi.view_account_invoice_filter_l10n_it
msgid "E-invoice error"
msgstr ""
#. module: l10n_it_edi
#: model_terms:ir.ui.view,arch_db:l10n_it_edi.view_account_invoice_filter_l10n_it
msgid "E-invoice sent"
msgstr ""
#. module: l10n_it_edi
#: model_terms:ir.ui.view,arch_db:l10n_it_edi.view_account_invoice_filter_l10n_it
msgid "E-invoice to send"
msgstr ""
#. module: l10n_it_edi
#: model_terms:ir.ui.view,arch_db:l10n_it_edi.account_invoice_line_it_FatturaPA
msgid "EAN"
msgstr ""
#. module: l10n_it_edi
#: code:addons/l10n_it_edi/models/ir_mail_server.py:195
#, python-format
msgid "ES certify that it has received the invoice and that the file could not be delivered to the addressee. <br/>%s"
msgstr ""
#. module: l10n_it_edi
#: code:addons/l10n_it_edi/models/account_invoice.py:629
#, python-format
msgid "EXTRA CHARGE: "
msgstr ""
#. module: l10n_it_edi
#: model_terms:ir.ui.view,arch_db:l10n_it_edi.res_company_form_l10n_it
msgid "Economic and Administrative Index"
msgstr ""
#. module: l10n_it_edi
#: model_terms:ir.ui.view,arch_db:l10n_it_edi.account_invoice_form_l10n_it
#: model_terms:ir.ui.view,arch_db:l10n_it_edi.account_invoice_supplier_form_l10n_it
#: model_terms:ir.ui.view,arch_db:l10n_it_edi.res_company_form_l10n_it
msgid "Electronic Invoicing"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,field_description:l10n_it_edi.field_account_invoice__l10n_it_einvoice_id
msgid "Electronic invoice"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,help:l10n_it_edi.field_res_company__l10n_it_address_recipient_fatturapa
msgid "Enter Government PEC-mail address. Ex: sdi01@pec.fatturapa.it"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,help:l10n_it_edi.field_res_company__l10n_it_address_send_fatturapa
msgid "Enter your company PEC-mail address. Ex: yourcompany@pec.mail.it"
msgstr ""
#. module: l10n_it_edi
#: model_terms:ir.ui.view,arch_db:l10n_it_edi.invoice_supplier_tree_l10n_it
msgid "Error"
msgstr ""
#. module: l10n_it_edi
#: code:addons/l10n_it_edi/models/account_invoice.py:286
#, python-format
msgid "Error when sending mail with E-Invoice: %s"
msgstr ""
#. module: l10n_it_edi
#: code:addons/l10n_it_edi/models/account_invoice.py:258
#, python-format
msgid "Error when sending mail with E-Invoice: Your company must have a mail PEC server and must indicate the mail PEC that will send electronic invoice."
msgstr ""
#. module: l10n_it_edi
#: code:addons/l10n_it_edi/models/ir_mail_server.py:241
#, python-format
msgid "Errors in the E-Invoice :<br/>%s"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,field_description:l10n_it_edi.field_account_tax__l10n_it_kind_exoneration
msgid "Exoneration"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,help:l10n_it_edi.field_account_tax__l10n_it_kind_exoneration
msgid "Exoneration type"
msgstr ""
#. module: l10n_it_edi
#: code:addons/l10n_it_edi/models/ir_mail_server.py:332
#, python-format
msgid "Expiration of the maximum term for communication of acceptance/refusal: %s<br/>%s"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,help:l10n_it_edi.field_res_company__l10n_it_codice_fiscale
msgid "Fiscal code of your company"
msgstr ""
#. module: l10n_it_edi
#: code:addons/l10n_it_edi/models/account_invoice.py:430
#, python-format
msgid "GLOBAL DISCOUNT"
msgstr ""
#. module: l10n_it_edi
#: code:addons/l10n_it_edi/models/account_invoice.py:432
#, python-format
msgid "GLOBAL EXTRA CHARGE"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,field_description:l10n_it_edi.field_res_company__l10n_it_address_recipient_fatturapa
msgid "Government PEC-mail"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,field_description:l10n_it_edi.field_account_tax__l10n_it_has_exoneration
msgid "Has exoneration of tax (Italy)"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,field_description:l10n_it_edi.field_l10n_it_ddt__id
msgid "ID"
msgstr ""
#. module: l10n_it_edi
#: model_terms:ir.ui.view,arch_db:l10n_it_edi.account_invoice_line_it_FatturaPA
msgid "INTERNAL"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,help:l10n_it_edi.field_fetchmail_server__l10n_it_is_pec
msgid "If PEC Server, only mail from '...@pec.fatturapa.it' will be processed."
msgstr ""
#. module: l10n_it_edi
#: model:ir.model,name:l10n_it_edi.model_account_invoice_import_wizard
msgid "Import Your Vendor Bills from Files."
msgstr ""
#. module: l10n_it_edi
#: model:ir.model,name:l10n_it_edi.model_fetchmail_server
msgid "Incoming Mail Server"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model,name:l10n_it_edi.model_account_invoice
msgid "Invoice"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,field_description:l10n_it_edi.field_l10n_it_ddt__invoice_id
msgid "Invoice Reference"
msgstr ""
#. module: l10n_it_edi
#: code:addons/l10n_it_edi/models/account_invoice.py:65
#, python-format
msgid "Invoices for PA are not managed by Odoo, you can download the document and send it on your own."
msgstr ""
#. module: l10n_it_edi
#: code:addons/l10n_it_edi/models/account_invoice.py:154
#, python-format
msgid "Italian invoice: %s"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,field_description:l10n_it_edi.field_account_invoice__l10n_it_einvoice_name
msgid "L10N It Einvoice Name"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,field_description:l10n_it_edi.field_res_company__l10n_it_has_eco_index
msgid "L10N It Has Eco Index"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,field_description:l10n_it_edi.field_res_company__l10n_it_has_tax_representative
msgid "L10N It Has Tax Representative"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,field_description:l10n_it_edi.field_account_invoice__l10n_it_send_state
msgid "L10N It Send State"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,field_description:l10n_it_edi.field_l10n_it_ddt____last_update
msgid "Last Modified on"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,field_description:l10n_it_edi.field_l10n_it_ddt__write_uid
msgid "Last Updated by"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,field_description:l10n_it_edi.field_l10n_it_ddt__write_date
msgid "Last Updated on"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,field_description:l10n_it_edi.field_fetchmail_server__l10n_it_last_uid
msgid "Last message UID"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,field_description:l10n_it_edi.field_account_tax__l10n_it_law_reference
msgid "Law Reference"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,field_description:l10n_it_edi.field_res_company__l10n_it_eco_index_liquidation_state
msgid "Liquidation state"
msgstr ""
#. module: l10n_it_edi
#: code:addons/l10n_it_edi/models/account_invoice.py:281
#, python-format
msgid "Mail sent on %s by %s"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,help:l10n_it_edi.field_res_company__l10n_it_eco_index_share_capital
msgid "Mandatory if the seller/provider is a company with share capital (SpA, SApA, Srl), this field must contain the amount of share capital actually paid up as resulting from the last financial statement"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,help:l10n_it_edi.field_res_partner__l10n_it_pa_index
#: model:ir.model.fields,help:l10n_it_edi.field_res_users__l10n_it_pa_index
msgid "Must contain the 6-character (or 7) code, present in the PA Index in the information relative to the electronic invoicing service, associated with the office which, within the addressee administration, deals with receiving (and processing) the invoice."
msgstr ""
#. module: l10n_it_edi
#: selection:account.invoice,l10n_it_send_state:0
msgid "New"
msgstr ""
#. module: l10n_it_edi
#: selection:res.company,l10n_it_eco_index_sole_shareholder:0
msgid "Not a limited liability company"
msgstr ""
#. module: l10n_it_edi
#: selection:account.invoice,l10n_it_send_state:0
msgid "Not yet send"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,field_description:l10n_it_edi.field_res_company__l10n_it_eco_index_number
msgid "Number in register of companies"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,field_description:l10n_it_edi.field_l10n_it_ddt__name
msgid "Numero DDT"
msgstr ""
#. module: l10n_it_edi
#: selection:account.invoice,l10n_it_send_state:0
msgid "Other"
msgstr ""
#. module: l10n_it_edi
#: code:addons/l10n_it_edi/models/ir_mail_server.py:300
#, python-format
msgid "Outcome notice: %s<br/>%s"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,field_description:l10n_it_edi.field_res_partner__l10n_it_pa_index
#: model:ir.model.fields,field_description:l10n_it_edi.field_res_users__l10n_it_pa_index
msgid "PA index"
msgstr ""
#. module: l10n_it_edi
#: sql_constraint:res.partner:0
msgid "PA index must have between 6 and 7 characters."
msgstr ""
#. module: l10n_it_edi
#: model_terms:ir.ui.view,arch_db:l10n_it_edi.account_invoice_it_FatturaPA_export
msgid "PDF"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,field_description:l10n_it_edi.field_res_partner__l10n_it_pec_email
#: model:ir.model.fields,field_description:l10n_it_edi.field_res_users__l10n_it_pec_email
msgid "PEC e-mail"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,field_description:l10n_it_edi.field_fetchmail_server__l10n_it_is_pec
msgid "PEC server"
msgstr ""
#. module: l10n_it_edi
#: selection:res.company,l10n_it_eco_index_sole_shareholder:0
msgid "Più soci"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,help:l10n_it_edi.field_res_company__l10n_it_tax_system
msgid "Please select the Tax system to which you are subjected."
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,field_description:l10n_it_edi.field_res_company__l10n_it_eco_index_office
msgid "Province of the register-of-companies office"
msgstr ""
#. module: l10n_it_edi
#: model_terms:ir.ui.view,arch_db:l10n_it_edi.account_invoice_form_l10n_it
msgid "Resend"
msgstr ""
#. module: l10n_it_edi
#: model_terms:ir.ui.view,arch_db:l10n_it_edi.account_invoice_it_FatturaPA_export
msgid "SI"
msgstr ""
#. module: l10n_it_edi
#: model_terms:ir.ui.view,arch_db:l10n_it_edi.account_invoice_form_l10n_it
msgid "Send"
msgstr ""
#. module: l10n_it_edi
#: model_terms:ir.ui.view,arch_db:l10n_it_edi.view_account_invoice_filter_l10n_it
msgid "Send status"
msgstr ""
#. module: l10n_it_edi
#: code:addons/l10n_it_edi/models/account_invoice.py:267
#, python-format
msgid "Sending file: %s"
msgstr ""
#. module: l10n_it_edi
#: code:addons/l10n_it_edi/models/account_invoice.py:268
#, python-format
msgid "Sending file: %s to ES: %s"
msgstr ""
#. module: l10n_it_edi
#: model_terms:ir.ui.view,arch_db:l10n_it_edi.invoice_supplier_tree_l10n_it
msgid "Sent"
msgstr ""
#. module: l10n_it_edi
#: selection:account.invoice,l10n_it_send_state:0
msgid "Sent, but invalid"
msgstr ""
#. module: l10n_it_edi
#: selection:account.invoice,l10n_it_send_state:0
msgid "Sent, waiting for response"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,field_description:l10n_it_edi.field_res_company__l10n_it_mail_pec_server_id
msgid "Server PEC"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,field_description:l10n_it_edi.field_res_company__l10n_it_eco_index_share_capital
msgid "Share capital actually paid up"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,field_description:l10n_it_edi.field_res_company__l10n_it_eco_index_sole_shareholder
msgid "Shareholder"
msgstr ""
#. module: l10n_it_edi
#: selection:res.company,l10n_it_eco_index_sole_shareholder:0
msgid "Socio unico"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model,name:l10n_it_edi.model_account_tax
msgid "Tax"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,field_description:l10n_it_edi.field_res_company__l10n_it_tax_system
msgid "Tax System"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,help:l10n_it_edi.field_account_tax__l10n_it_has_exoneration
msgid "Tax has a tax exoneration."
msgstr ""
#. module: l10n_it_edi
#: code:addons/l10n_it_edi/models/account_invoice.py:584
#, python-format
msgid "Tax not found with percentage: %s and exoneration %s for the article: %s"
msgstr ""
#. module: l10n_it_edi
#: code:addons/l10n_it_edi/models/account_invoice.py:589
#, python-format
msgid "Tax not found with percentage: %s for the article: %s"
msgstr ""
#. module: l10n_it_edi
#: model_terms:ir.ui.view,arch_db:l10n_it_edi.res_company_form_l10n_it
msgid "Tax representative"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,field_description:l10n_it_edi.field_res_company__l10n_it_tax_representative_partner_id
msgid "Tax representative partner"
msgstr ""
#. module: l10n_it_edi
#: code:addons/l10n_it_edi/models/ir_mail_server.py:266
#, python-format
msgid "The E-invoice is not delivered to the addressee. The Exchange System is unable to deliver the file to the Public Administration. The Exchange System will contact the PA to report the problem and request that they provide a solution. During the following 15 days, the Exchange System will try to forward the FatturaPA file to the Administration in question again. More informations:<br/>%s"
msgstr ""
#. module: l10n_it_edi
#: code:addons/l10n_it_edi/models/account_invoice.py:108
#, python-format
msgid "The buyer, %s, or his company must have either a VAT number either a tax code (Codice Fiscale)."
msgstr ""
#. module: l10n_it_edi
#: selection:res.company,l10n_it_eco_index_liquidation_state:0
msgid "The company is in a state of liquidation"
msgstr ""
#. module: l10n_it_edi
#: selection:res.company,l10n_it_eco_index_liquidation_state:0
msgid "The company is not in a state of liquidation"
msgstr ""
#. module: l10n_it_edi
#: code:addons/l10n_it_edi/models/account_invoice.py:84
#, python-format
msgid "The maximum length for VAT number is 30. %s have a VAT number too long: %s."
msgstr ""
#. module: l10n_it_edi
#: model_terms:ir.ui.view,arch_db:l10n_it_edi.res_company_form_l10n_it
msgid "The seller/provider is a company listed on the register of companies and as\n"
" such must also indicate the registration data on all documents (art. 2250, Italian\n"
" Civil Code)"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,help:l10n_it_edi.field_res_company__l10n_it_has_eco_index
msgid "The seller/provider is a company listed on the register of companies and as such must also indicate the registration data on all documents (art. 2250, Italian Civil Code)"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,help:l10n_it_edi.field_res_company__l10n_it_has_tax_representative
msgid "The seller/provider is a non-resident subject which carries out transactions in Italy with relevance for VAT purposes and which takes avail of a tax representative in Italy"
msgstr ""
#. module: l10n_it_edi
#: model_terms:ir.ui.view,arch_db:l10n_it_edi.res_company_form_l10n_it
msgid "The seller/provider is a non-resident subject which carries out transactions in Italy\n"
" with relevance for VAT purposes and which takes avail of a tax representative in Italy"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,help:l10n_it_edi.field_res_company__l10n_it_eco_index_number
msgid "This field must contain the number under which the seller/provider is listed on the register of companies."
msgstr ""
#. module: l10n_it_edi
#: selection:account.invoice,l10n_it_send_state:0
msgid "This invoice is delivered"
msgstr ""
#. module: l10n_it_edi
#: selection:account.invoice,l10n_it_send_state:0
msgid "This invoice is delivered and accepted by destinatory"
msgstr ""
#. module: l10n_it_edi
#: selection:account.invoice,l10n_it_send_state:0
msgid "This invoice is delivered and expired (expiry of the maximum term for communication of acceptance/refusal)"
msgstr ""
#. module: l10n_it_edi
#: selection:account.invoice,l10n_it_send_state:0
msgid "This invoice is delivered and refused by destinatory"
msgstr ""
#. module: l10n_it_edi
#: code:addons/l10n_it_edi/models/account_invoice.py:472
#, python-format
msgid "Total amount from the XML File: %s"
msgstr ""
#. module: l10n_it_edi
#: model:ir.actions.act_window,name:l10n_it_edi.action_ddt_account
#: model:ir.model,name:l10n_it_edi.model_l10n_it_ddt
msgid "Transport Document"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,help:l10n_it_edi.field_l10n_it_ddt__date
msgid "Transport document date"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,help:l10n_it_edi.field_l10n_it_ddt__name
msgid "Transport document number"
msgstr ""
#. module: l10n_it_edi
#: code:addons/l10n_it_edi/models/account_invoice.py:456
#, python-format
msgid "Transport informations from XML file:"
msgstr ""
#. module: l10n_it_edi
#: model:ir.model.fields,field_description:l10n_it_edi.field_account_tax__l10n_it_vat_due_date
msgid "VAT due date"
msgstr ""
#. module: l10n_it_edi
#: code:addons/l10n_it_edi/models/account_invoice.py:373
#, python-format
msgid "Vendor not found, useful informations from XML file:"
msgstr ""
#. module: l10n_it_edi
#: code:addons/l10n_it_edi/models/account_invoice.py:336
#, python-format
msgid "You can only import invoice concern your current company: %s"
msgstr ""
#. module: l10n_it_edi
#: code:addons/l10n_it_edi/models/account_invoice.py:135
#, python-format
msgid "You can't regenerate an E-Invoice when the first one is sent and there are no errors"
msgstr ""
#. module: l10n_it_edi
#: code:addons/l10n_it_edi/models/account_invoice.py:125
#, python-format
msgid "You must select one and only one tax by line."
msgstr ""
#. module: l10n_it_edi
#: selection:account.tax,l10n_it_vat_due_date:0
msgid "[D] IVA ad esigibilità differita"
msgstr ""
#. module: l10n_it_edi
#: selection:account.tax,l10n_it_vat_due_date:0
msgid "[I] IVA ad esigibilità immediata"
msgstr ""
#. module: l10n_it_edi
#: selection:account.tax,l10n_it_kind_exoneration:0
msgid "[N1] Escluse ex art. 15"
msgstr ""
#. module: l10n_it_edi
#: selection:account.tax,l10n_it_kind_exoneration:0
msgid "[N2] Non soggette"
msgstr ""
#. module: l10n_it_edi
#: selection:account.tax,l10n_it_kind_exoneration:0
msgid "[N3] Non imponibili"
msgstr ""
#. module: l10n_it_edi
#: selection:account.tax,l10n_it_kind_exoneration:0
msgid "[N4] Esenti"
msgstr ""
#. module: l10n_it_edi
#: selection:account.tax,l10n_it_kind_exoneration:0
msgid "[N5] Regime del margine / IVA non esposta in fattura"
msgstr ""
#. module: l10n_it_edi
#: selection:account.tax,l10n_it_kind_exoneration:0
msgid "[N6] Inversione contabile (per le operazioni in reverse charge ovvero nei casi di autofatturazione per acquisti extra UE di servizi ovvero per importazioni di beni nei soli casi previsti)"
msgstr ""
#. module: l10n_it_edi
#: selection:account.tax,l10n_it_kind_exoneration:0
msgid "[N7] IVA assolta in altro stato UE (vendite a distanza ex art. 40 c. 3 e 4 e art. 41 c. 1 lett. b, DL 331/93; prestazione di servizi di telecomunicazioni, tele-radiodiffusione ed elettronici ex art. 7-sexies lett. f, g, art. 74-sexies DPR 633/72)"
msgstr ""
#. module: l10n_it_edi
#: selection:res.company,l10n_it_tax_system:0
msgid "[RF01] Ordinario"
msgstr ""
#. module: l10n_it_edi
#: selection:res.company,l10n_it_tax_system:0
msgid "[RF02] Contribuenti minimi (art.1, c.96-117, L. 244/07)"
msgstr ""
#. module: l10n_it_edi
#: selection:res.company,l10n_it_tax_system:0
msgid "[RF04] Agricoltura e attività connesse e pesca (artt.34 e 34-bis, DPR 633/72)"
msgstr ""
#. module: l10n_it_edi
#: selection:res.company,l10n_it_tax_system:0
msgid "[RF05] Vendita sali e tabacchi (art.74, c.1, DPR. 633/72)"
msgstr ""
#. module: l10n_it_edi
#: selection:res.company,l10n_it_tax_system:0
msgid "[RF06] Commercio fiammiferi (art.74, c.1, DPR 633/72)"
msgstr ""
#. module: l10n_it_edi
#: selection:res.company,l10n_it_tax_system:0
msgid "[RF07] Editoria (art.74, c.1, DPR 633/72)"
msgstr ""
#. module: l10n_it_edi
#: selection:res.company,l10n_it_tax_system:0
msgid "[RF08] Gestione servizi telefonia pubblica (art.74, c.1, DPR 633/72)"
msgstr ""
#. module: l10n_it_edi
#: selection:res.company,l10n_it_tax_system:0
msgid "[RF09] Rivendita documenti di trasporto pubblico e di sosta (art.74, c.1, DPR 633/72)"
msgstr ""
#. module: l10n_it_edi
#: selection:res.company,l10n_it_tax_system:0
msgid "[RF10] Intrattenimenti, giochi e altre attività di cui alla tariffa allegata al DPR 640/72 (art.74, c.6, DPR 633/72)"
msgstr ""
#. module: l10n_it_edi
#: selection:res.company,l10n_it_tax_system:0
msgid "[RF11] Agenzie viaggi e turismo (art.74-ter, DPR 633/72)"
msgstr ""
#. module: l10n_it_edi
#: selection:res.company,l10n_it_tax_system:0
msgid "[RF12] Agriturismo (art.5, c.2, L. 413/91)"
msgstr ""
#. module: l10n_it_edi
#: selection:res.company,l10n_it_tax_system:0
msgid "[RF13] Vendite a domicilio (art.25-bis, c.6, DPR 600/73)"
msgstr ""
#. module: l10n_it_edi
#: selection:res.company,l10n_it_tax_system:0
msgid "[RF14] Rivendita beni usati, oggetti d’arte, d’antiquariato o da collezione (art.36, DL 41/95)"
msgstr ""
#. module: l10n_it_edi
#: selection:res.company,l10n_it_tax_system:0
msgid "[RF15] Agenzie di vendite all’asta di oggetti d’arte, antiquariato o da collezione (art.40-bis, DL 41/95)"
msgstr ""
#. module: l10n_it_edi
#: selection:res.company,l10n_it_tax_system:0
msgid "[RF16] IVA per cassa P.A. (art.6, c.5, DPR 633/72)"
msgstr ""
#. module: l10n_it_edi
#: selection:res.company,l10n_it_tax_system:0
msgid "[RF17] IVA per cassa (art. 32-bis, DL 83/2012)"
msgstr ""
#. module: l10n_it_edi
#: selection:res.company,l10n_it_tax_system:0
msgid "[RF18] Altro"
msgstr ""
#. module: l10n_it_edi
#: selection:res.company,l10n_it_tax_system:0
msgid "[RF19] Regime forfettario (art.1, c.54-89, L. 190/2014)"
msgstr ""
#. module: l10n_it_edi
#: selection:account.tax,l10n_it_vat_due_date:0
msgid "[S] Scissione dei pagamenti"
msgstr ""
#. module: l10n_it_edi
#: code:addons/l10n_it_edi/models/account_invoice.py:449
#, python-format
msgid "from XML file:"
msgstr ""
+8
View File
@@ -0,0 +1,8 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import res_partner
from . import res_company
from . import account_invoice
from . import ir_mail_server
from . import ddt
@@ -0,0 +1,752 @@
# -*- coding:utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import base64
import zipfile
import io
import logging
import re
from datetime import date, datetime
from lxml import etree
from odoo import api, fields, models, _
from odoo.tools import float_repr
from odoo.exceptions import UserError, ValidationError
from odoo.addons.base.models.ir_mail_server import MailDeliveryException
from odoo.tests.common import Form
_logger = logging.getLogger(__name__)
DEFAULT_FACTUR_ITALIAN_DATE_FORMAT = '%Y-%m-%d'
class AccountInvoice(models.Model):
_inherit = 'account.invoice'
_name = 'account.invoice'
l10n_it_send_state = fields.Selection([
('new', 'New'),
('other', 'Other'),
('to_send', 'Not yet send'),
('sent', 'Sent, waiting for response'),
('invalid', 'Sent, but invalid'),
('delivered', 'This invoice is delivered'),
('delivered_accepted', 'This invoice is delivered and accepted by destinatory'),
('delivered_refused', 'This invoice is delivered and refused by destinatory'),
('delivered_expired', 'This invoice is delivered and expired (expiry of the maximum term for communication of acceptance/refusal)'),
('failed_delivery', 'Delivery impossible, ES certify that it has received the invoice and that the file \
could not be delivered to the addressee') # ok we must do nothing
], default='to_send', copy=False)
l10n_it_stamp_duty = fields.Float(default=0, string="Dati Bollo", size=15, readonly=True, states={'draft': [('readonly', False)]})
l10n_it_ddt_id = fields.Many2one('l10n_it.ddt', string='DDT', readonly=True, states={'draft': [('readonly', False)]}, copy=False)
l10n_it_einvoice_name = fields.Char(readonly=True, copy=False)
l10n_it_einvoice_id = fields.Many2one('ir.attachment', string="Electronic invoice", copy=False)
@api.multi
def invoice_validate(self):
super(AccountInvoice, self).invoice_validate()
for invoice in self:
if invoice.company_id.country_id != self.env.ref('base.it'):
continue
if invoice.type == 'in_invoice' or invoice.type == 'in_refund':
invoice.l10n_it_send_state = "other"
continue
invoice._check_before_xml_exporting()
invoice.invoice_generate_xml()
if len(invoice.commercial_partner_id.l10n_it_pa_index or '') == 6:
invoice.message_post(
body=(_("Invoices for PA are not managed by Odoo, you can download the document and send it on your own."))
)
invoice.l10n_it_send_state = "other"
continue
invoice.l10n_it_send_state = "to_send"
invoice.send_pec_mail()
def _check_before_xml_exporting(self):
seller = self.company_id
buyer = self.commercial_partner_id
# <1.1.1.1>
if not seller.country_id:
raise UserError(_("%s must have a country") % (seller.display_name))
# <1.1.1.2>
if not seller.vat:
raise UserError(_("%s must have a VAT number") % (seller.display_name))
elif len(seller.vat) > 30:
raise UserError(_("The maximum length for VAT number is 30. %s have a VAT number too long: %s.") % (seller.display_name, seller.vat))
# <1.2.1.2>
if not seller.l10n_it_codice_fiscale:
raise UserError(_("%s must have a codice fiscale number") % (seller.display_name))
# <1.2.1.8>
if not seller.l10n_it_tax_system:
raise UserError("The seller's company must have a tax system.")
# <1.2.2>
if not seller.street and not seller.street2:
raise UserError(_("%s must have a street.") % (seller.display_name))
if not seller.zip:
raise UserError(_("%s must have a post code.") % (seller.display_name))
if len(seller.zip) != 5 and seller.country_id.code == 'IT':
raise UserError(_("%s must have a post code of length 5.") % (seller.display_name))
if not seller.city:
raise UserError(_("%s must have a city.") % (seller.display_name))
if not seller.country_id:
raise UserError(_("%s must have a country.") % (seller.display_name))
# <1.4.1>
if not buyer.vat and not buyer.l10n_it_codice_fiscale:
raise UserError(_("The buyer, %s, or his company must have either a VAT number either a tax code (Codice Fiscale).") % (buyer.display_name))
# <1.4.2>
if not buyer.street and not buyer.street2:
raise UserError(_("%s must have a street.") % (buyer.display_name))
if not buyer.zip:
raise UserError(_("%s must have a post code.") % (buyer.display_name))
if len(buyer.zip) != 5:
raise UserError(_("%s must have a post code of length 5.") % (buyer.display_name))
if not buyer.city:
raise UserError(_("%s must have a city.") % (buyer.display_name))
if not buyer.country_id:
raise UserError(_("%s must have a country.") % (buyer.display_name))
# <2.2.1>
for invoice_line in self.invoice_line_ids:
if len(invoice_line.invoice_line_tax_ids) != 1:
raise UserError(_("You must select one and only one tax by line."))
for tax_line in self.tax_line_ids:
if not tax_line.tax_id.l10n_it_has_exoneration and tax_line.tax_id.amount == 0:
raise ValidationError(_("%s has an amount of 0.0, you must indicate the kind of exoneration." % tax_line.name))
@api.multi
def invoice_generate_xml(self):
for invoice in self:
if invoice.l10n_it_einvoice_id and invoice.l10n_it_send_state not in ['invalid', 'to_send']:
raise UserError(_("You can't regenerate an E-Invoice when the first one is sent and there are no errors"))
if invoice.l10n_it_einvoice_id:
invoice.l10n_it_einvoice_id.unlink()
a = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
n = invoice.id
progressive_number = ""
while n:
(n,m) = divmod(n,len(a))
progressive_number = a[m] + progressive_number
report_name = '%(country_code)s%(codice)s_%(progressive_number)s.xml' % {
'country_code': invoice.company_id.country_id.code,
'codice': invoice.company_id.l10n_it_codice_fiscale,
'progressive_number': progressive_number.zfill(5),
}
invoice.l10n_it_einvoice_name = report_name
data = b"<?xml version='1.0' encoding='UTF-8'?>" + invoice._export_as_xml()
description = _('Italian invoice: %s') % invoice.type
invoice.l10n_it_einvoice_id = self.env['ir.attachment'].create({
'name': report_name,
'res_id': invoice.id,
'res_model': invoice._name,
'datas': base64.encodestring(data),
'datas_fname': report_name,
'description': description,
'type': 'binary',
})
invoice.message_post(
body=(_("E-Invoice is generated on %s by %s") % (fields.Datetime.now(), self.env.user.display_name))
)
def _export_as_xml(self):
''' Create the xml file content.
:return: The XML content as str.
'''
self.ensure_one()
def format_date(dt):
# Format the date in the italian standard.
dt = dt or datetime.now()
return dt.strftime(DEFAULT_FACTUR_ITALIAN_DATE_FORMAT)
def format_monetary(number, currency):
# Format the monetary values to avoid trailing decimals (e.g. 90.85000000000001).
return float_repr(number, min(2, currency.decimal_places))
def format_numbers(number):
#format number to str with between 2 and 8 decimals (event if it's .00)
number_splited = str(number).split('.')
if len(number_splited) == 1:
return "%.02f" % number
cents = number_splited[1]
if len(cents) > 8:
return "%.08f" % number
return float_repr(number, max(2, len(cents)))
def format_numbers_two(number):
#format number to str with 2 (event if it's .00)
return "%.02f" % number
def discount_type(discount):
return 'SC' if discount > 0 else 'MG'
def format_phone(number):
if not number:
return False
number = number.replace(' ', '').replace('/', '').replace('.', '')
if len(number) > 4 and len(number) < 13:
return number
return False
def get_vat_number(vat):
return vat[2:].replace(' ', '')
def get_vat_country(vat):
return vat[:2].upper()
formato_trasmissione = "FPR12"
if len(self.commercial_partner_id.l10n_it_pa_index or '1') == 6:
formato_trasmissione = "FPA12"
if self.type == 'out_invoice':
document_type = 'TD01'
elif self.type == 'out_refund':
document_type = 'TD04'
else:
document_type = 'TD0X'
pdf = self.env.ref('account.account_invoices').render_qweb_pdf(self.id)[0]
pdf = base64.b64encode(pdf)
pdf_name = re.sub(r'\W+', '', self.number) + '.pdf'
# Create file content.
template_values = {
'record': self,
'format_date': format_date,
'format_monetary': format_monetary,
'format_numbers': format_numbers,
'format_numbers_two': format_numbers_two,
'format_phone': format_phone,
'discount_type': discount_type,
'get_vat_number': get_vat_number,
'get_vat_country': get_vat_country,
'abs': abs,
'formato_trasmissione': formato_trasmissione,
'document_type': document_type,
'pdf': pdf,
'pdf_name': pdf_name,
}
content = self.env.ref('l10n_it_edi.account_invoice_it_FatturaPA_export').render(template_values)
return content
@api.multi
def send_pec_mail(self):
self.ensure_one()
allowed_state = ['to_send', 'invalid']
if not self.company_id.l10n_it_mail_pec_server_id or not self.company_id.l10n_it_address_send_fatturapa:
self.message_post(
body=(_("Error when sending mail with E-Invoice: Your company must have a mail PEC server and must indicate the mail PEC that will send electronic invoice."))
)
self.l10n_it_send_state = 'invalid'
return
if self.l10n_it_send_state not in allowed_state:
raise UserError(_("%s isn't in a right state. It must be in a 'Not yet send' or 'Invalid' state.") % (self.display_name))
message = self.env['mail.message'].create({
'subject': _('Sending file: %s') % (self.l10n_it_einvoice_id.name),
'body': _('Sending file: %s to ES: %s') % (self.l10n_it_einvoice_id.name, self.env.user.company_id.l10n_it_address_recipient_fatturapa),
'email_from': self.env.user.company_id.l10n_it_address_send_fatturapa,
'mail_server_id': self.env.user.company_id.l10n_it_mail_pec_server_id.id,
'attachment_ids': [(6, 0, self.l10n_it_einvoice_id.ids)],
})
mail_fattura = self.env['mail.mail'].create({
'mail_message_id': message.id,
'email_to': self.env.user.company_id.l10n_it_address_recipient_fatturapa,
})
try:
mail_fattura.send(raise_exception=True)
self.message_post(
body=(_("Mail sent on %s by %s") % (fields.Datetime.now(), self.env.user.display_name))
)
self.l10n_it_send_state = 'sent'
except MailDeliveryException as error:
self.message_post(
body=(_("Error when sending mail with E-Invoice: %s") % (error.args[0]))
)
self.l10n_it_send_state = 'invalid'
def _import_xml_invoice(self, content, attachment):
''' Extract invoice values from the E-Invoice xml tree passed as parameter.
:param content: The tree of the xml file.
:return: A dictionary containing account.invoice values to create/update it.
'''
try:
tree = etree.fromstring(content)
except:
_logger.info('Error during decoding XML file')
return self.env['account.invoice']
invoices = self.env['account.invoice']
# possible to have multiple invoices in the case of an invoice batch, the batch itself is repeated for every invoice of the batch
for body_tree in tree.xpath('//FatturaElettronicaBody', namespaces=tree.nsmap):
elements = tree.xpath('//DatiGeneraliDocumento/TipoDocumento', namespaces=tree.nsmap)
if elements and elements[0].text and elements[0].text == 'TD01':
self_ctx = self.with_context(type='in_invoice')
elif elements and elements[0].text and elements[0].text == 'TD04':
self_ctx = self.with_context(type='in_refund')
else:
_logger.info(_('Document type not managed: %s.') % (elements[0].text))
# type must be present in the context to get the right behavior of the _default_journal method (account.invoice).
# journal_id must be present in the context to get the right behavior of the _default_account method (account.invoice.line).
elements = tree.xpath('//CessionarioCommittente//IdCodice', namespaces=tree.nsmap)
company = elements and self.env['res.company'].search([('vat', 'ilike', elements[0].text)], limit=1)
if not company:
elements = tree.xpath('//CessionarioCommittente//CodiceFiscale', namespaces=tree.nsmap)
company = elements and self.env['res.company'].search([('l10n_it_codice_fiscale', 'ilike', elements[0].text)], limit=1)
if company:
self_ctx = self_ctx.with_context(company_id=company.id)
else:
company = self.env.user.company_id
if elements:
_logger.info(_('Company not found with codice fiscale: %s. The company\'s user is set by default.') % elements[0].text)
else:
_logger.info(_('Company not found. The company\'s user is set by default.'))
if not self.env.user._is_superuser():
if self.env.user.company_id != company:
raise UserError(_("You can only import invoice concern your current company: %s") % self.env.user.company_id.display_name)
journal_id = self_ctx._default_journal().id
self_ctx = self_ctx.with_context(journal_id=journal_id)
# self could be a single record (editing) or be empty (new).
with Form(self_ctx, view='account.invoice_supplier_form') as invoice_form:
message_to_log = []
invoice_form.company_id = company
# Refund type.
# TD01 == invoice
# TD02 == advance/down payment on invoice
# TD03 == advance/down payment on fee
# TD04 == credit note
# TD05 == debit note
# TD06 == fee
elements = tree.xpath('//DatiGeneraliDocumento/TipoDocumento', namespaces=tree.nsmap)
if elements and elements[0].text and elements[0].text == 'TD01':
invoice_form.type = 'in_invoice'
elif elements and elements[0].text and elements[0].text == 'TD04':
invoice_form.type = 'in_refund'
# Partner (first step to avoid warning 'Warning! You must first select a partner.'). <1.2>
elements = tree.xpath('//CedentePrestatore//IdCodice', namespaces=tree.nsmap)
partner = elements and self.env['res.partner'].search(['&', ('vat', 'ilike', elements[0].text), '|', ('company_id', '=', company.id), ('company_id', '=', False)], limit=1)
if not partner:
elements = tree.xpath('//CedentePrestatore//CodiceFiscale', namespaces=tree.nsmap)
partner = elements and self.env['res.partner'].search(['&', ('l10n_it_codice_fiscale', '=', elements[0].text), '|', ('company_id', '=', company.id), ('company_id', '=', False)], limit=1)
if not partner:
elements = tree.xpath('//DatiTrasmissione//Email', namespaces=tree.nsmap)
partner = elements and self.env['res.partner'].search(['&', '|', ('email', '=', elements[0].text), ('l10n_it_pec_email', '=', elements[0].text), '|', ('company_id', '=', company.id), ('company_id', '=', False)], limit=1)
if partner:
invoice_form.partner_id = partner
else:
message_to_log.append("%s<br/>%s" % (
_("Vendor not found, useful informations from XML file:"),
self._compose_info_message(
tree, './/CedentePrestatore')))
# Numbering attributed by the transmitter. <1.1.2>
elements = tree.xpath('//ProgressivoInvio', namespaces=tree.nsmap)
if elements:
invoice_form.name = elements[0].text
elements = body_tree.xpath('.//DatiGeneraliDocumento//Numero', namespaces=body_tree.nsmap)
if elements:
invoice_form.reference = elements[0].text
# Currency. <2.1.1.2>
elements = body_tree.xpath('.//DatiGeneraliDocumento/Divisa', namespaces=body_tree.nsmap)
if elements:
currency_str = elements[0].text
currency = self.env.ref('base.%s' % currency_str.upper(), raise_if_not_found=False)
if currency != self.env.user.company_id.currency_id and currency.active:
invoice_form.currency_id = currency
# Date. <2.1.1.3>
elements = body_tree.xpath('.//DatiGeneraliDocumento/Data', namespaces=body_tree.nsmap)
if elements:
date_str = elements[0].text
date_obj = datetime.strptime(date_str, DEFAULT_FACTUR_ITALIAN_DATE_FORMAT)
invoice_form.date_invoice = date_obj.strftime(DEFAULT_FACTUR_ITALIAN_DATE_FORMAT)
# Dati Bollo. <2.1.1.6>
elements = body_tree.xpath('.//DatiGeneraliDocumento/DatiBollo/ImportoBollo', namespaces=body_tree.nsmap)
if elements:
invoice_form.l10n_it_stamp_duty = float(elements[0].text)
# List of all amount discount (will be add after all article to avoid to have a negative sum)
discount_list = []
percentage_global_discount = 1.0
# Global discount. <2.1.1.8>
discount_elements = body_tree.xpath('.//DatiGeneraliDocumento/ScontoMaggiorazione', namespaces=body_tree.nsmap)
total_discount_amount = 0.0
if discount_elements:
for discount_element in discount_elements:
discount_line = discount_element.xpath('.//Tipo', namespaces=body_tree.nsmap)
discount_sign = -1
if discount_line and discount_line[0].text == 'SC':
discount_sign = 1
discount_percentage = discount_element.xpath('.//Percentuale', namespaces=body_tree.nsmap)
if discount_percentage and discount_percentage[0].text:
percentage_global_discount *= 1 - float(discount_percentage[0].text)/100 * discount_sign
discount_amount_text = discount_element.xpath('.//Importo', namespaces=body_tree.nsmap)
if discount_amount_text and discount_amount_text[0].text:
discount_amount = float(discount_amount_text[0].text) * discount_sign * -1
discount = {}
discount["seq"] = 0
if discount_amount < 0:
discount["name"] = _('GLOBAL DISCOUNT')
else:
discount["name"] = _('GLOBAL EXTRA CHARGE')
discount["amount"] = discount_amount
discount["tax"] = []
discount_list.append(discount)
# Comment. <2.1.1.11>
elements = body_tree.xpath('.//DatiGeneraliDocumento//Causale', namespaces=body_tree.nsmap)
for element in elements:
invoice_form.comment = '%s%s\n' % (invoice_form.comment or '', element.text)
# Informations relative to the purchase order, the contract, the agreement,
# the reception phase or invoices previously transmitted
# <2.1.2> - <2.1.6>
for document_type in ['DatiOrdineAcquisto', 'DatiContratto', 'DatiConvenzione', 'DatiRicezione', 'DatiFattureCollegate']:
elements = body_tree.xpath('.//DatiGenerali/' + document_type, namespaces=body_tree.nsmap)
if elements:
for element in elements:
message_to_log.append("%s %s<br/>%s" % (document_type, _("from XML file:"),
self._compose_info_message(element, '.')))
# Dati DDT. <2.1.8>
elements = body_tree.xpath('.//DatiGenerali/DatiDDT', namespaces=body_tree.nsmap)
if elements:
message_to_log.append("%s<br/>%s" % (
_("Transport informations from XML file:"),
self._compose_info_message(body_tree, './/DatiGenerali/DatiDDT')))
# Due date. <2.4.2.5>
elements = body_tree.xpath('.//DatiPagamento/DettaglioPagamento/DataScadenzaPagamento', namespaces=body_tree.nsmap)
if elements:
date_str = elements[0].text
date_obj = datetime.strptime(date_str, DEFAULT_FACTUR_ITALIAN_DATE_FORMAT)
invoice_form.date_due = fields.Date.to_string(date_obj)
# Total amount. <2.4.2.6>
elements = body_tree.xpath('.//ImportoPagamento', namespaces=body_tree.nsmap)
amount_total_import = 0
for element in elements:
amount_total_import += float(element.text)
if amount_total_import:
message_to_log.append(_("Total amount from the XML File: %s") % (
amount_total_import))
# Bank account. <2.4.2.13>
elements = body_tree.xpath('.//DatiPagamento/DettaglioPagamento/IBAN', namespaces=body_tree.nsmap)
if elements:
if invoice_form.partner_id and invoice_form.partner_id.commercial_partner_id:
bank = self.env['res.partner.bank'].search([
('acc_number', '=', elements[0].text),
('partner_id.id', '=', invoice_form.partner_id.commercial_partner_id.id)
])
else:
bank = self.env['res.partner.bank'].search([('acc_number', '=', elements[0].text)])
if bank:
invoice_form.partner_bank_id = bank
else:
message_to_log.append("%s<br/>%s" % (
_("Bank account not found, useful informations from XML file:"),
self._compose_multi_info_message(
body_tree, ['.//DatiPagamento//Beneficiario',
'.//DatiPagamento//IstitutoFinanziario',
'.//DatiPagamento//IBAN',
'.//DatiPagamento//ABI',
'.//DatiPagamento//CAB',
'.//DatiPagamento//BIC',
'.//DatiPagamento//ModalitaPagamento'])))
else:
elements = body_tree.xpath('.//DatiPagamento/DettaglioPagamento', namespaces=body_tree.nsmap)
if elements:
message_to_log.append("%s<br/>%s" % (
_("Bank account not found, useful informations from XML file:"),
self._compose_info_message(body_tree, './/DatiPagamento')))
# Invoice lines. <2.2.1>
elements = body_tree.xpath('.//DettaglioLinee', namespaces=body_tree.nsmap)
if elements:
for element in elements:
with invoice_form.invoice_line_ids.new() as invoice_line_form:
# Sequence.
line_elements = element.xpath('.//NumeroLinea', namespaces=body_tree.nsmap)
if line_elements:
invoice_line_form.sequence = int(line_elements[0].text) * 2
# Product.
line_elements = element.xpath('.//Descrizione', namespaces=body_tree.nsmap)
if line_elements:
invoice_line_form.name = " ".join(line_elements[0].text.split())
elements_code = element.xpath('.//CodiceArticolo', namespaces=body_tree.nsmap)
if elements_code:
for element_code in elements_code:
type_code = element_code.xpath('.//CodiceTipo', namespaces=body_tree.nsmap)[0]
code = element_code.xpath('.//CodiceValore', namespaces=body_tree.nsmap)[0]
if type_code.text == 'EAN':
product = self.env['product.product'].search([('barcode', '=', code.text)])
if product:
invoice_line_form.product_id = product
break
if partner:
product_supplier = self.env['product.supplierinfo'].search([('name', '=', partner.id), ('product_code', '=', code.text)])
if product_supplier and product_supplier.product_id:
invoice_line_form.product_id = product_supplier.product_id
break
if not invoice_line_form.product_id:
for element_code in elements_code:
code = element_code.xpath('.//CodiceValore', namespaces=body_tree.nsmap)[0]
product = self.env['product.product'].search([('default_code', '=', code.text)])
if product:
invoice_line_form.product_id = product
break
# Price Unit.
line_elements = element.xpath('.//PrezzoUnitario', namespaces=body_tree.nsmap)
if line_elements:
invoice_line_form.price_unit = float(line_elements[0].text)
# Quantity.
line_elements = element.xpath('.//Quantita', namespaces=body_tree.nsmap)
if line_elements:
invoice_line_form.quantity = float(line_elements[0].text)
else:
invoice_line_form.quantity = 1
# Taxes
tax_element = element.xpath('.//AliquotaIVA', namespaces=body_tree.nsmap)
natura_element = element.xpath('.//Natura', namespaces=body_tree.nsmap)
invoice_line_form.invoice_line_tax_ids.clear()
if tax_element and tax_element[0].text:
percentage = float(tax_element[0].text)
if natura_element and natura_element[0].text:
l10n_it_kind_exoneration = natura_element[0].text
tax = self.env['account.tax'].search([
('company_id', '=', invoice_form.company_id.id),
('amount_type', '=', 'percent'),
('type_tax_use', '=', 'purchase'),
('amount', '=', percentage),
('l10n_it_kind_exoneration', '=', l10n_it_kind_exoneration),
], limit=1)
else:
tax = self.env['account.tax'].search([
('company_id', '=', invoice_form.company_id.id),
('amount_type', '=', 'percent'),
('type_tax_use', '=', 'purchase'),
('amount', '=', percentage),
], limit=1)
l10n_it_kind_exoneration = ''
if tax:
invoice_line_form.invoice_line_tax_ids.add(tax)
else:
if l10n_it_kind_exoneration:
message_to_log.append(_("Tax not found with percentage: %s and exoneration %s for the article: %s") % (
percentage,
l10n_it_kind_exoneration,
invoice_line_form.name))
else:
message_to_log.append(_("Tax not found with percentage: %s for the article: %s") % (
percentage,
invoice_line_form.name))
# Discount in cascade mode.
# if 3 discounts : -10% -50€ -20%
# the result must be : (((price -10%)-50€) -20%)
# Generic form : (((price -P1%)-A1€) -P2%)
# It will be split in two parts: fix amount and pourcent amount
# example: (((((price - A1€) -P2%) -A3€) -A4€) -P5€)
# pourcent: 1-(1-P2)*(1-P5)
# fix amount: A1*(1-P2)*(1-P5)+A3*(1-P5)+A4*(1-P5) (we must take account of all
# percentage present after the fix amount)
line_elements = element.xpath('.//ScontoMaggiorazione', namespaces=body_tree.nsmap)
total_discount_amount = 0.0
total_discount_percentage = percentage_global_discount
if line_elements:
for line_element in line_elements:
discount_line = line_element.xpath('.//Tipo', namespaces=body_tree.nsmap)
discount_sign = -1
if discount_line and discount_line[0].text == 'SC':
discount_sign = 1
discount_percentage = line_element.xpath('.//Percentuale', namespaces=body_tree.nsmap)
if discount_percentage and discount_percentage[0].text:
pourcentage_actual = 1 - float(discount_percentage[0].text)/100 * discount_sign
total_discount_percentage *= pourcentage_actual
total_discount_amount *= pourcentage_actual
discount_amount = line_element.xpath('.//Importo', namespaces=body_tree.nsmap)
if discount_amount and discount_amount[0].text:
total_discount_amount += float(discount_amount[0].text) * discount_sign * -1
# Save amount discount.
if total_discount_amount != 0:
discount = {}
discount["seq"] = invoice_line_form.sequence + 1
if total_discount_amount < 0:
discount["name"] = _('DISCOUNT: ') + invoice_line_form.name
else:
discount["name"] = _('EXTRA CHARGE: ') + invoice_line_form.name
discount["amount"] = total_discount_amount
discount["tax"] = []
for tax in invoice_line_form.invoice_line_tax_ids:
discount["tax"].append(tax)
discount_list.append(discount)
invoice_line_form.discount = (1 - total_discount_percentage) * 100
# Apply amount discount.
for discount in discount_list:
with invoice_form.invoice_line_ids.new() as invoice_line_form_discount:
invoice_line_form_discount.invoice_line_tax_ids.clear()
invoice_line_form_discount.sequence = discount["seq"]
invoice_line_form_discount.name = discount["name"]
invoice_line_form_discount.price_unit = discount["amount"]
new_invoice = invoice_form.save()
new_invoice.l10n_it_send_state = "other"
elements = body_tree.xpath('.//Allegati', namespaces=body_tree.nsmap)
if elements:
for element in elements:
name_attachment = element.xpath('.//NomeAttachment', namespaces=body_tree.nsmap)[0].text
attachment_64 = str.encode(element.xpath('.//Attachment', namespaces=body_tree.nsmap)[0].text)
attachment_64 = self.env['ir.attachment'].create({
'name': name_attachment,
'datas': attachment_64,
'datas_fname': name_attachment,
'type': 'binary',
})
# default_res_id is had to context to avoid facturx to import his content
new_invoice.with_context(default_res_id=new_invoice.id).message_post(
body=(_("Attachment from XML")),
attachment_ids=[attachment_64.id]
)
for message in message_to_log:
new_invoice.message_post(body=message)
if attachment:
new_invoice.l10n_it_einvoice_name = attachment.name
attachment.write({'res_model': 'account.invoice', 'res_id': new_invoice.id})
new_invoice.message_post(attachment_ids=[attachment.id])
invoices += new_invoice
return invoices
def _compose_info_message(self, tree, element_tags):
output_str = ""
elements = tree.xpath(element_tags, namespaces=tree.nsmap)
for element in elements:
output_str += "<ul>"
for line in element.iter():
if line.text:
text = " ".join(line.text.split())
if text:
output_str += "<li>%s: %s</li>" % (line.tag, text)
output_str += "</ul>"
return output_str
def _compose_multi_info_message(self, tree, element_tags):
output_str = "<ul>"
for element_tag in element_tags:
elements = tree.xpath(element_tag, namespaces=tree.nsmap)
if not elements:
continue
for element in elements:
text = " ".join(element.text.split())
if text:
output_str += "<li>%s: %s</li>" % (element.tag, text)
return output_str + "</ul>"
class AccountTax(models.Model):
_name = "account.tax"
_inherit = "account.tax"
l10n_it_vat_due_date = fields.Selection([
("I", "[I] IVA ad esigibilità immediata"),
("D", "[D] IVA ad esigibilità differita"),
("S", "[S] Scissione dei pagamenti")], default="I", string="VAT due date")
l10n_it_has_exoneration = fields.Boolean(string="Has exoneration of tax (Italy)", help="Tax has a tax exoneration.")
l10n_it_kind_exoneration = fields.Selection(selection=[
("N1", "[N1] Escluse ex art. 15"),
("N2", "[N2] Non soggette"),
("N3", "[N3] Non imponibili"),
("N4", "[N4] Esenti"),
("N5", "[N5] Regime del margine / IVA non esposta in fattura"),
("N6", "[N6] Inversione contabile (per le operazioni in reverse charge ovvero nei casi di autofatturazione per acquisti extra UE di servizi ovvero per importazioni di beni nei soli casi previsti)"),
("N7", "[N7] IVA assolta in altro stato UE (vendite a distanza ex art. 40 c. 3 e 4 e art. 41 c. 1 lett. b, DL 331/93; prestazione di servizi di telecomunicazioni, tele-radiodiffusione ed elettronici ex art. 7-sexies lett. f, g, art. 74-sexies DPR 633/72)")],
string="Exoneration",
help="Exoneration type",
default="N1")
l10n_it_law_reference = fields.Char(string="Law Reference", size=100)
@api.constrains('l10n_it_has_exoneration',
'l10n_it_kind_exoneration',
'l10n_it_law_reference',
'amount',
'l10n_it_vat_due_date')
def _check_exoneration_with_no_tax(self):
for tax in self:
if tax.l10n_it_has_exoneration:
if not tax.l10n_it_kind_exoneration or not tax.l10n_it_law_reference or tax.amount != 0:
raise ValidationError("If the tax has exoneration, you must enter a kind of exoneration, a law reference and the amount of the tax must be 0.0.")
if tax.l10n_it_kind_exoneration == 'N6' and tax.l10n_it_vat_due_date == 'S':
raise UserError(_("'Scissione dei pagamenti' is not compatible with exoneration of kind 'N6'"))
class ImportInvoiceImportWizard(models.TransientModel):
_name = 'account.invoice.import.wizard'
_inherit = 'account.invoice.import.wizard'
@api.multi
def _create_invoice_from_file(self, attachment):
if attachment.mimetype == 'application/xml' and re.search("([A-Z]{2}[A-Za-z0-9]{2,28}_[A-Za-z0-9]{0,5}.(xml.p7m|xml))", attachment.name):
if self.env['account.invoice'].search([('l10n_it_einvoice_name', '=', attachment.name)], limit=1):
# invoice already exist
raise UserError(_('E-invoice already exist: %s') % attachment.name)
self = self.with_context(default_journal_id= self.journal_id.id)
invoice = self.env['account.invoice']._import_xml_invoice(base64.decodestring(attachment.datas), attachment)
else:
invoice = super(ImportInvoiceImportWizard, self)._create_invoice_from_file(attachment)
return invoice
+21
View File
@@ -0,0 +1,21 @@
# -*- coding:utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models, api
class L10nItDdt(models.Model):
_name = 'l10n_it.ddt'
_description = 'Transport Document'
invoice_id = fields.One2many('account.invoice', 'l10n_it_ddt_id', string='Invoice Reference',
ondelete='cascade')
name = fields.Char(string="Numero DDT", size=20, help="Transport document number", required=True)
date = fields.Date(string="Data DDT", help="Transport document date", required=True)
@api.multi
def name_get(self):
res = []
for ddt in self:
res.append((ddt.id, ("%s (%s)") % (ddt.name, ddt.date)))
return res
+359
View File
@@ -0,0 +1,359 @@
# -*- coding:utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import zipfile
import io
import re
import logging
import email
import dateutil
import pytz
import base64
try:
from xmlrpc import client as xmlrpclib
except ImportError:
import xmlrpclib
from lxml import etree
from datetime import datetime
from odoo import api, fields, models, tools, _
from odoo.exceptions import ValidationError
from odoo.tools import pycompat
_logger = logging.getLogger(__name__)
class FetchmailServer(models.Model):
_name = 'fetchmail.server'
_inherit = 'fetchmail.server'
l10n_it_is_pec = fields.Boolean('PEC server', help="If PEC Server, only mail from '...@pec.fatturapa.it' will be processed.")
l10n_it_last_uid = fields.Integer(string='Last message UID', default=1)
@api.constrains('l10n_it_is_pec', 'type')
def _check_pec(self):
for record in self:
if record.l10n_it_is_pec and record.type != 'imap':
raise ValidationError("PEC mail server must be of type IMAP.")
@api.multi
def fetch_mail(self):
""" WARNING: meant for cron usage only - will commit() after each email! """
MailThread = self.env['mail.thread']
for server in self.filtered(lambda s: s.l10n_it_is_pec):
_logger.info('start checking for new emails on %s PEC server %s', server.type, server.name)
count, failed = 0, 0
imap_server = None
try:
imap_server = server.connect()
imap_server.select()
result, data = imap_server.uid('search', None, '(FROM "@pec.fatturapa.it")', '(UID %s:*)' % (server.l10n_it_last_uid))
new_max_uid = server.l10n_it_last_uid
for uid in data[0].split():
if int(uid) <= server.l10n_it_last_uid:
# We get always minimum 1 message. If no new message, we receive the newest already managed.
continue
result, data = imap_server.uid('fetch', uid, '(RFC822)')
if not data[0]:
continue
message = data[0][1]
# To leave the mail in the state in which they were.
if "Seen" not in data[1].decode("utf-8"):
imap_server.uid('STORE', uid, '+FLAGS', '\\Seen')
else:
imap_server.uid('STORE', uid, '-FLAGS', '\\Seen')
# See details in message_process() in mail_thread.py
if isinstance(message, xmlrpclib.Binary):
message = bytes(message.data)
if isinstance(message, pycompat.text_type):
message = message.encode('utf-8')
msg_txt = email.message_from_bytes(message)
try:
self._attachment_invoice(msg_txt)
new_max_uid = max(new_max_uid, int(uid))
except Exception:
_logger.info('Failed to process mail from %s server %s.', server.type, server.name, exc_info=True)
failed += 1
self._cr.commit()
count += 1
server.write({'l10n_it_last_uid': new_max_uid})
_logger.info("Fetched %d email(s) on %s server %s; %d succeeded, %d failed.", count, server.type, server.name, (count - failed), failed)
except Exception:
_logger.info("General failure when trying to fetch mail from %s server %s.", server.type, server.name, exc_info=True)
finally:
if imap_server:
imap_server.close()
imap_server.logout()
server.write({'date': fields.Datetime.now()})
return super(FetchmailServer, self.filtered(lambda s: not s.l10n_it_is_pec)).fetch_mail()
def _attachment_invoice(self, msg_txt):
body, attachments = self.env['mail.thread']._message_extract_payload(msg_txt)
from_address = tools.decode_smtp_header(msg_txt.get('from'))
for attachment in attachments:
split_attachment = attachment.fname.rpartition('.')
if len(split_attachment) < 3:
_logger.info('E-invoice filename not compliant: %s', attachment.fname)
continue
attachment_name = split_attachment[0]
attachment_ext = split_attachment[2]
split_underscore = attachment_name.rsplit('_', 2)
if len(split_underscore) < 2:
_logger.info('E-invoice filename not compliant: %s', attachment.fname)
continue
if attachment_ext != 'zip':
if split_underscore[1] in ['RC', 'NS', 'MC', 'MT', 'EC', 'SE', 'NE', 'DT']:
# we have a receipt
self._message_receipt_invoice(split_underscore[1], attachment)
elif re.search("([A-Z]{2}[A-Za-z0-9]{2,28}_[A-Za-z0-9]{0,5}.(xml.p7m|xml))", attachment.fname):
# we have a new E-invoice
self._create_invoice_from_mail(attachment.content, attachment.fname, from_address)
else:
if split_underscore[1] == 'AT':
# Attestazione di avvenuta trasmissione della fattura con impossibilità di recapito
self._message_AT_invoice(attachment)
else:
_logger.info('New E-invoice in zip file: %s', attachment.fname)
self._create_invoice_from_mail_with_zip(attachment, from_address)
def _create_invoice_from_mail(self, att_content, att_name, from_address):
if self.env['account.invoice'].search([('l10n_it_einvoice_name', '=', att_name)], limit=1):
# invoice already exist
_logger.info('E-invoice already exist: %s', att_name)
return
invoice_attachment = self.env['ir.attachment'].create({
'name': att_name,
'datas': base64.encodestring(att_content),
'datas_fname': att_name,
'type': 'binary',
})
invoice = self.env['account.invoice']._import_xml_invoice(att_content, invoice_attachment)
invoice.l10n_it_send_state = "new"
invoice.source_email = from_address
self._cr.commit()
_logger.info('New E-invoice: %s', att_name)
def _create_invoice_from_mail_with_zip(self, attachment_zip, from_address):
with zipfile.ZipFile(io.BytesIO(attachment_zip.content)) as z:
for att_name in z.namelist():
if self.env['account.invoice'].search([('l10n_it_einvoice_name', '=', att_name)], limit=1):
# invoice already exist
_logger.info('E-invoice in zip file (%s) already exist: %s', attachment_zip.fname, att_name)
continue
att_content = z.open(att_name).read()
self._create_invoice_from_mail(att_content, att_name, from_address)
def _message_AT_invoice(self, attachment_zip):
with zipfile.ZipFile(io.BytesIO(attachment_zip.content)) as z:
for attachment_name in z.namelist():
split_name_attachment = attachment_name.rpartition('.')
if len(split_name_attachment) < 3:
continue
split_underscore = split_name_attachment[0].rsplit('_', 2)
if len(split_underscore) < 2:
continue
if split_underscore[1] == 'AT':
attachment = z.open(attachment_name).read()
_logger.info('New AT receipt for: %s', split_underscore[0])
try:
tree = etree.fromstring(attachment)
except:
_logger.info('Error in decoding new receipt file: %s', attachment_name)
return
elements = tree.xpath('//NomeFile', namespaces=tree.nsmap)
if elements and elements[0].text:
filename = elements[0].text
else:
return
related_invoice = self.env['account.invoice'].search([
('l10n_it_einvoice_name', '=', filename)])
if not related_invoice:
_logger.info('Error: invoice not found for receipt file: %s', filename)
return
related_invoice.l10n_it_send_state = 'failed_delivery'
info = self._return_multi_line_xml(tree, ['//IdentificativoSdI', '//DataOraRicezione', '//MessageId', '//PecMessageId', '//Note'])
related_invoice.message_post(
body=(_("ES certify that it has received the invoice and that the file \
could not be delivered to the addressee. <br/>%s") % (info))
)
def _message_receipt_invoice(self, receipt_type, attachment):
try:
tree = etree.fromstring(attachment.content)
except:
_logger.info('Error in decoding new receipt file: %s', attachment.fname)
return {}
elements = tree.xpath('//NomeFile', namespaces=tree.nsmap)
if elements and elements[0].text:
filename = elements[0].text
else:
return {}
if receipt_type == 'RC':
# Delivery receipt
# This is the receipt sent by the ES to the transmitting subject to communicate
# delivery of the file to the addressee
related_invoice = self.env['account.invoice'].search([
('l10n_it_einvoice_name', '=', filename),
('l10n_it_send_state', '=', 'sent')])
if not related_invoice:
_logger.info('Error: invoice not found for receipt file: %s', attachment.fname)
return
related_invoice.l10n_it_send_state = 'delivered'
info = self._return_multi_line_xml(tree, ['//IdentificativoSdI', '//DataOraRicezione', '//DataOraConsegna', '//Note'])
related_invoice.message_post(
body=(_("E-Invoice is delivery to the destinatory:<br/>%s") % (info))
)
elif receipt_type == 'NS':
# Rejection notice
# This is the receipt sent by the ES to the transmitting subject if one or more of
# the checks carried out by the ES on the file received do not have a successful result.
related_invoice = self.env['account.invoice'].search([
('l10n_it_einvoice_name', '=', filename),
('l10n_it_send_state', '=', 'sent')])
if not related_invoice:
_logger.info('Error: invoice not found for receipt file: %s', attachment.fname)
return
related_invoice.l10n_it_send_state = 'invalid'
error = self._return_error_xml(tree)
related_invoice.message_post(
body=(_("Errors in the E-Invoice :<br/>%s") % (error))
)
activity_vals = {
'activity_type_id': self.env.ref('mail.mail_activity_data_todo').id,
'user_id': related_invoice.user_id.id if related_invoice.user_id else self.env.user.id
}
related_invoice.activity_schedule(summary='Rejection notice', **activity_vals)
elif receipt_type == 'MC':
# Failed delivery notice
# This is the receipt sent by the ES to the transmitting subject if the file is not
# delivered to the addressee.
related_invoice = self.env['account.invoice'].search([
('l10n_it_einvoice_name', '=', filename),
('l10n_it_send_state', '=', 'sent')])
if not related_invoice:
_logger.info('Error: invoice not found for receipt file: %s', attachment.fname)
return
info = self._return_multi_line_xml(tree, [
'//IdentificativoSdI',
'//DataOraRicezione',
'//Descrizione',
'//MessageId',
'//Note'])
related_invoice.message_post(
body=(_("The E-invoice is not delivered to the addressee. The Exchange System is\
unable to deliver the file to the Public Administration. The Exchange System will\
contact the PA to report the problem and request that they provide a solution. \
During the following 15 days, the Exchange System will try to forward the FatturaPA\
file to the Administration in question again. More informations:<br/>%s") % (info))
)
elif receipt_type == 'NE':
# Outcome notice
# This is the receipt sent by the ES to the invoice sender to communicate the result
# (acceptance or refusal of the invoice) of the checks carried out on the document by
# the addressee.
related_invoice = self.env['account.invoice'].search([
('l10n_it_einvoice_name', '=', filename),
('l10n_it_send_state', '=', 'delivered')])
if not related_invoice:
_logger.info('Error: invoice not found for receipt file: %s', attachment.fname)
return
elements = tree.xpath('//Esito', namespaces=tree.nsmap)
if elements and elements[0].text:
if elements[0].text == 'EC01':
related_invoice.l10n_it_send_state = 'delivered_accepted'
elif elements[0].text == 'EC02':
related_invoice.l10n_it_send_state = 'delivered_refused'
info = self._return_multi_line_xml(tree,
['//Esito',
'//Descrizione',
'//IdentificativoSdI',
'//DataOraRicezione',
'//DataOraConsegna',
'//Note'
])
related_invoice.message_post(
body=(_("Outcome notice: %s<br/>%s") % (related_invoice.l10n_it_send_state, info))
)
if related_invoice.l10n_it_send_state == 'delivered_refused':
activity_vals = {
'activity_type_id': self.env.ref('mail.mail_activity_data_todo').id,
'user_id': related_invoice.user_id.id if related_invoice.user_id else self.env.user.id
}
related_invoice.activity_schedule(summary='Outcome notice: Refused', **activity_vals)
# elif receipt_type == 'MT':
# Metadata file
# This is the file sent by the ES to the addressee together with the invoice file,
# containing the main reference data of the file useful for processing, including
# the IdentificativoSDI.
# Useless for Odoo
elif receipt_type == 'DT':
# Deadline passed notice
# This is the receipt sent by the ES to both the invoice sender and the invoice
# addressee to communicate the expiry of the maximum term for communication of
# acceptance/refusal.
related_invoice = self.env['account.invoice'].search([
('l10n_it_einvoice_name', '=', filename), ('l10n_it_send_state', '=', 'delivered')])
if not related_invoice:
_logger.info('Error: invoice not found for receipt file: %s', attachment.fname)
return
related_invoice.l10n_it_send_state = 'delivered_expired'
info = self._return_multi_line_xml(tree, [
'//Descrizione',
'//IdentificativoSdI',
'//Note'])
related_invoice.message_post(
body=(_("Expiration of the maximum term for communication of acceptance/refusal:\
%s<br/>%s") % (filename, info))
)
def _return_multi_line_xml(self, tree, element_tags):
output_str = "<ul>"
for element_tag in element_tags:
elements = tree.xpath(element_tag, namespaces=tree.nsmap)
if not elements:
continue
for element in elements:
text = " ".join(element.text.split())
if text:
output_str += "<li>%s: %s</li>" % (element.tag, text)
return output_str + "</ul>"
def _return_error_xml(self, tree):
output_str = "<ul>"
elements = tree.xpath('//Errore', namespaces=tree.nsmap)
if not elements:
return
for element in elements:
descrizione = " ".join(element[1].text.split())
if descrizione:
output_str += "<li>Errore %s: %s</li>" % (element[0].text, descrizione)
return output_str + "</ul>"
+110
View File
@@ -0,0 +1,110 @@
# -*- coding:utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
from odoo.exceptions import ValidationError
TAX_SYSTEM = [
("RF01", "[RF01] Ordinario"),
("RF02", "[RF02] Contribuenti minimi (art.1, c.96-117, L. 244/07)"),
("RF04", "[RF04] Agricoltura e attività connesse e pesca (artt.34 e 34-bis, DPR 633/72)"),
("RF05", "[RF05] Vendita sali e tabacchi (art.74, c.1, DPR. 633/72)"),
("RF06", "[RF06] Commercio fiammiferi (art.74, c.1, DPR 633/72)"),
("RF07", "[RF07] Editoria (art.74, c.1, DPR 633/72)"),
("RF08", "[RF08] Gestione servizi telefonia pubblica (art.74, c.1, DPR 633/72)"),
("RF09", "[RF09] Rivendita documenti di trasporto pubblico e di sosta (art.74, c.1, DPR 633/72)"),
("RF10", "[RF10] Intrattenimenti, giochi e altre attività di cui alla tariffa allegata al DPR 640/72 (art.74, c.6, DPR 633/72)"),
("RF11", "[RF11] Agenzie viaggi e turismo (art.74-ter, DPR 633/72)"),
("RF12", "[RF12] Agriturismo (art.5, c.2, L. 413/91)"),
("RF13", "[RF13] Vendite a domicilio (art.25-bis, c.6, DPR 600/73)"),
("RF14", "[RF14] Rivendita beni usati, oggetti d’arte, d’antiquariato o da collezione (art.36, DL 41/95)"),
("RF15", "[RF15] Agenzie di vendite all’asta di oggetti d’arte, antiquariato o da collezione (art.40-bis, DL 41/95)"),
("RF16", "[RF16] IVA per cassa P.A. (art.6, c.5, DPR 633/72)"),
("RF17", "[RF17] IVA per cassa (art. 32-bis, DL 83/2012)"),
("RF18", "[RF18] Altro"),
("RF19", "[RF19] Regime forfettario (art.1, c.54-89, L. 190/2014)"),
]
class ResCompany(models.Model):
_name = 'res.company'
_inherit = 'res.company'
l10n_it_codice_fiscale = fields.Char(string="Codice Fiscale", size=16, related='partner_id.l10n_it_codice_fiscale',
store=True, readonly=False, help="Fiscal code of your company")
l10n_it_tax_system = fields.Selection(selection=TAX_SYSTEM, string="Tax System",
help="Please select the Tax system to which you are subjected.")
# PEC server
l10n_it_mail_pec_server_id = fields.Many2one('ir.mail_server', string="Server PEC",
help="Configure your PEC-mail server to send electronic invoices.")
l10n_it_address_recipient_fatturapa = fields.Char(string="Government PEC-mail",
help="Enter Government PEC-mail address. Ex: sdi01@pec.fatturapa.it")
l10n_it_address_send_fatturapa = fields.Char(string="Company PEC-mail",
help="Enter your company PEC-mail address. Ex: yourcompany@pec.mail.it")
# Economic and Administrative Index
l10n_it_has_eco_index = fields.Boolean(default=False,
help="The seller/provider is a company listed on the register of companies and as\
such must also indicate the registration data on all documents (art. 2250, Italian\
Civil Code)")
l10n_it_eco_index_office = fields.Many2one('res.country.state', domain="[('country_id','=','IT')]",
string="Province of the register-of-companies office")
l10n_it_eco_index_number = fields.Char(string="Number in register of companies", size=20,
help="This field must contain the number under which the\
seller/provider is listed on the register of companies.")
l10n_it_eco_index_share_capital = fields.Float(default="0.0", size=15, string="Share capital actually paid up",
help="Mandatory if the seller/provider is a company with share\
capital (SpA, SApA, Srl), this field must contain the amount\
of share capital actually paid up as resulting from the last\
financial statement")
l10n_it_eco_index_sole_shareholder = fields.Selection(
[
("NO", "Not a limited liability company"),
("SU", "Socio unico"),
("SM", "Più soci")],
string="Shareholder")
l10n_it_eco_index_liquidation_state = fields.Selection(
[
("LS", "The company is in a state of liquidation"),
("LN", "The company is not in a state of liquidation")],
string="Liquidation state")
# Tax representative
l10n_it_has_tax_representative = fields.Boolean(default=False,
help="The seller/provider is a non-resident subject which\
carries out transactions in Italy with relevance for VAT\
purposes and which takes avail of a tax representative in\
Italy")
l10n_it_tax_representative_partner_id = fields.Many2one('res.partner', string='Tax representative partner')
@api.constrains('l10n_it_has_eco_index',
'l10n_it_eco_index_office',
'l10n_it_eco_index_number',
'l10n_it_eco_index_share_capital',
'l10n_it_eco_index_sole_shareholder',
'l10n_it_eco_index_liquidation_state')
def _check_eco_admin_index(self):
for record in self:
if not record.l10n_it_has_eco_index:
continue
if not record.l10n_it_eco_index_office\
or not record.l10n_it_eco_index_number\
or not record.l10n_it_eco_index_share_capital\
or not record.l10n_it_eco_index_sole_shareholder\
or not record.l10n_it_eco_index_liquidation_state:
raise ValidationError("All fields about the Economic and Administrative Index must be completed.")
@api.constrains('l10n_it_has_tax_representative',
'l10n_it_tax_representative_partner_id')
def _check_tax_representative(self):
for record in self:
if not record.l10n_it_has_tax_representative:
continue
if not record.l10n_it_tax_representative_partner_id:
raise ValidationError("You must select a tax representative.")
if not record.l10n_it_tax_representative_partner_id.vat:
raise ValidationError("Your tax representative partner must have a tax number.")
if not record.l10n_it_tax_representative_partner_id.country_id:
raise ValidationError("Your tax representative partner must have a country.")
+28
View File
@@ -0,0 +1,28 @@
# -*- coding:utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
from odoo.exceptions import ValidationError
class ResPartner(models.Model):
_name = 'res.partner'
_inherit = 'res.partner'
l10n_it_pec_email = fields.Char(string="PEC e-mail")
l10n_it_codice_fiscale = fields.Char(string="Codice Fiscale", size=16)
l10n_it_pa_index = fields.Char(string="PA index",
size=7,
help="Must contain the 6-character (or 7) code, present in the PA\
Index in the information relative to the electronic invoicing service,\
associated with the office which, within the addressee administration, deals\
with receiving (and processing) the invoice.")
_sql_constraints = [
('l10n_it_codice_fiscale',
"CHECK(l10n_it_codice_fiscale IS NULL OR LENGTH(l10n_it_codice_fiscale) >= 11)",
"Codice fiscale must have between 11 and 16 characters."),
('l10n_it_pa_index',
"CHECK(l10n_it_pa_index IS NULL OR LENGTH(l10n_it_pa_index) >= 6)",
"PA index must have between 6 and 7 characters."),
]
@@ -0,0 +1,2 @@
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
"access_it_ddt_manager","it_ddt manager","model_l10n_it_ddt","account.group_account_invoice",1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_it_ddt_manager it_ddt manager model_l10n_it_ddt account.group_account_invoice 1 1 1 1
+2
View File
@@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
from . import test_generate
+32
View File
@@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
import time
from odoo import fields
from odoo.tests.common import TransactionCase, Form
class TestItalianElectronicInvoice(TransactionCase):
def test_state(self):
f = Form(self.env['account.invoice'])
f.partner_id = self.env.ref('base.res_partner_12')
with f.invoice_line_ids.new() as l:
l.product_id = self.env.ref('product.product_product_3')
invoice = f.save()
# I check that Initially customer invoice state is "Draft"
self.assertEqual(invoice.state, 'draft')
# I called the "Confirm Draft Invoices" wizard
w = Form(self.env['account.invoice.confirm']).save()
# I clicked on Confirm Invoices Button
w.with_context(
active_model='account.invoice',
active_id=invoice.id,
active_ids=invoice.ids,
type='out_invoice',
).invoice_confirm()
# I check that customer invoice state is "Open"
self.assertEqual(invoice.state, 'open')
# Electronic invoice must be present and have the same name as l10n_it_einvoice_name
self.assertEqual(invoice.l10n_it_einvoice_id.name, invoice.l10n_it_einvoice_name)
+265
View File
@@ -0,0 +1,265 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="fetchmail_server_form_l10n_it" model="ir.ui.view">
<field name="name">fetchmail.server.form.l10n.it</field>
<field name="model">fetchmail.server</field>
<field name="priority">20</field>
<field name="inherit_id" ref="fetchmail.view_email_server_form"/>
<field name="arch" type="xml">
<data>
<xpath expr="//field[@name='date']" position="after">
<field name="l10n_it_is_pec"/>
</xpath>
</data>
</field>
</record>
<record id="account_tax_form_l10n_it" model="ir.ui.view">
<field name="name">account.tax.form.l10n.it</field>
<field name="model">account.tax</field>
<field name="priority">20</field>
<field name="inherit_id" ref="account.view_tax_form"/>
<field name="arch" type="xml">
<data>
<xpath expr="//page" position="inside">
<group>
<field name="l10n_it_vat_due_date"/>
<field name="l10n_it_has_exoneration" readonly="False"/>
<field name="l10n_it_kind_exoneration" attrs="{'invisible': [('l10n_it_has_exoneration', '=', False)]}"/>
<field name="l10n_it_law_reference" attrs="{'invisible': [('l10n_it_has_exoneration', '=', False)]}"/>
</group>
</xpath>
</data>
</field>
</record>
<record id="res_partner_form_l10n_it" model="ir.ui.view">
<field name="name">res.partner.form.l10n.it</field>
<field name="model">res.partner</field>
<field name="priority">20</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="arch" type="xml">
<data>
<xpath expr="//field[@name='category_id']" position="after">
<field name="l10n_it_pec_email" attrs="{'invisible': [('parent_id', '!=', False)]}"/>
<field name="l10n_it_codice_fiscale" attrs="{'invisible': [('parent_id', '!=', False)]}"/>
<field name="l10n_it_pa_index" attrs="{'invisible': [('parent_id', '!=', False)]}"/>
</xpath>
</data>
</field>
</record>
<record id="res_company_form_l10n_it" model="ir.ui.view">
<field name="name">res.company.form.l10n.it</field>
<field name="model">res.company</field>
<field name="priority">20</field>
<field name="inherit_id" ref="base.view_company_form"/>
<field name="arch" type="xml">
<data>
<xpath expr="//div[hasclass('o_address_format')]" position="after">
<field name="l10n_it_mail_pec_server_id"/>
<field name="l10n_it_address_send_fatturapa"/>
<field name="l10n_it_address_recipient_fatturapa"/>
</xpath>
<xpath expr="//field[@name='vat']" position="after">
<field name="l10n_it_codice_fiscale"/>
<field name="l10n_it_tax_system"/>
</xpath>
<xpath expr="//page" position="after">
<page string="Electronic Invoicing">
<group>
<separator string="Economic and Administrative Index" colspan="4"/>
<div colspan="4">
The seller/provider is a company listed on the register of companies and as
such must also indicate the registration data on all documents (art. 2250, Italian
Civil Code)
</div>
<group>
<field name="l10n_it_has_eco_index" string="Company listed on the register of companies"/>
</group>
<group attrs="{'invisible': [('l10n_it_has_eco_index', '=', False)]}">
<field name="l10n_it_eco_index_office"/>
<field name="l10n_it_eco_index_number"/>
<field name="l10n_it_eco_index_share_capital"/>
<field name="l10n_it_eco_index_sole_shareholder"/>
<field name="l10n_it_eco_index_liquidation_state"/>
</group>
</group>
<group>
<separator string="Tax representative" colspan="4"/>
<div colspan="4">
The seller/provider is a non-resident subject which carries out transactions in Italy
with relevance for VAT purposes and which takes avail of a tax representative in Italy
</div>
<group>
<field name="l10n_it_has_tax_representative" string="Company have a tax representative"/>
</group>
<group attrs="{'invisible': [('l10n_it_has_tax_representative', '=', False)]}">
<field name="l10n_it_tax_representative_partner_id"/>
</group>
</group>
</page>
</xpath>
</data>
</field>
</record>
<record id="invoice_supplier_tree_l10n_it" model="ir.ui.view">
<field name="name">account.invoice.supplier.tree.l10n.it</field>
<field name="model">account.invoice</field>
<field name="priority">20</field>
<field name="inherit_id" ref="account.invoice_tree"/>
<field name="arch" type="xml">
<data>
<xpath expr="//field[@name='type']" position="after">
<field name="l10n_it_send_state" invisible="1" widget="label_selection" options="{'classes': {'to_send': 'default', 'invalid': 'danger', 'sent': 'warning',
'delivered': 'success', 'delivered_accepted': 'success', 'delivered_refused': 'success', 'delivered_expired': 'success', 'failed_delivery': 'success'}}"/>
<button icon="fa-paper-plane-o" class="btn-outline-warning disabled" aria-label="Sent" title="Sent" attrs="{'invisible': [('l10n_it_send_state', '!=', 'sent')]}"/>
<button icon="fa-exclamation-triangle" class="btn-outline-danger disabled" aria-label="Error" title="Error" attrs="{'invisible': [('l10n_it_send_state', '!=', 'invalid')]}"/>
<button icon="fa-check" class="btn-outline-success disabled" aria-label="Delivered" title="Delivered" attrs="{'invisible': [('l10n_it_send_state', 'not in', ['delivered', 'delivered_accepted', 'delivered_refused', 'delivered_expired', 'failed_delivery'])]}"/>
</xpath>
</data>
</field>
</record>
<record id="invoice_kanban_l10n_it" model="ir.ui.view">
<field name="name">account.invoice.kanban.l10n.it</field>
<field name="model">account.invoice</field>
<field name="priority">20</field>
<field name="inherit_id" ref="account.invoice_kanban"/>
<field name="arch" type="xml">
<data>
<xpath expr="//field[@name='currency_id']" position="after">
<field name="l10n_it_send_state"/>
</xpath>
<xpath expr="//div[hasclass('o_kanban_record_headings')]" position="inside">
<i class="text-success fa fa-plus-circle" t-if="record.l10n_it_send_state.raw_value == 'new'"/>
<i class="text-warning fa fa-paper-plane-o" t-if="record.l10n_it_send_state.raw_value == 'sent'"/>
<i class="text-danger fa fa-exclamation-triangle" t-if="record.l10n_it_send_state.raw_value == 'invalid'"/>
<i class="text-success fa fa-check" t-if="['delivered', 'delivered_accepted', 'delivered_refused', 'delivered_expired', 'failed_delivery'].indexOf(record.l10n_it_send_state.raw_value) >= 0" />
</xpath>
</data>
</field>
</record>
<record id="account_invoice_form_l10n_it" model="ir.ui.view">
<field name="name">account.invoice.form.l10n.it</field>
<field name="model">account.invoice</field>
<field name="priority">20</field>
<field name="inherit_id" ref="account.invoice_form"/>
<field name="arch" type="xml">
<data>
<xpath expr="//button[@name='preview_invoice']" position="after">
<button name="invoice_validate" type="object" icon="fa-envelope-o" attrs="{'invisible': [('l10n_it_send_state', '!=', 'invalid')]}" class="btn btn-primary" string="Resend"/>
<button name="invoice_validate" type="object" icon="fa-envelope-o" attrs="{'invisible': ['|',('state', 'in', ['draft', 'cancel']),('l10n_it_send_state', 'not in', ['to_send', False])]}" class="btn btn-primary" string="Send"/>
</xpath>
<xpath expr="//field[@name='type']" position="before">
<div class="alert alert-success" role="alert" attrs="{'invisible': [('l10n_it_send_state', 'not in', ['delivered', 'delivered_accepted', 'delivered_refused', 'delivered_expired', 'failed_delivery'])]}">
<i class="fa fa-check" aria-label="Delivered" title="Delivered"></i> <field name="l10n_it_send_state" readonly="1"/>
</div>
<div class="alert alert-warning" role="alert" attrs="{'invisible': [('l10n_it_send_state', '!=', 'sent')]}">
<i class="fa fa-paper-plane-o"/> E-Invoice sent, waiting for a response
</div>
<div class="alert alert-danger" role="alert" attrs="{'invisible': [('l10n_it_send_state', '!=', 'invalid')]}">
<i class="fa fa-exclamation-triangle"/> E-Invoice check failed. You can modify the invoice, and resend it.
</div>
</xpath>
<xpath expr="//page[@name='other_info']" position="after">
<page string="Electronic Invoicing">
<group>
<group>
<field name="l10n_it_stamp_duty"/>
</group>
<group>
<field name="l10n_it_ddt_id"/>
</group>
</group>
</page>
</xpath>
</data>
</field>
</record>
<record id="account_invoice_supplier_form_l10n_it" model="ir.ui.view">
<field name="name">account.invoice.supplier.form.l10n.it</field>
<field name="model">account.invoice</field>
<field name="priority">20</field>
<field name="inherit_id" ref="account.invoice_supplier_form"/>
<field name="arch" type="xml">
<data>
<xpath expr="//field[@name='date_due']" position="replace">
<field name="date_due" attrs="{'readonly': [('state', 'in', ['open', 'in_payment', 'paid'])]}" force_save="1"/>
</xpath>
<xpath expr="//page[@name='other_info']" position="after">
<page string="Electronic Invoicing">
<group>
<group>
<field name="l10n_it_stamp_duty"/>
</group>
</group>
</page>
</xpath>
</data>
</field>
</record>
<record id="view_account_invoice_filter_l10n_it" model="ir.ui.view">
<field name="name">account.invoice.select.l10n.it</field>
<field name="model">account.invoice</field>
<field name="inherit_id" ref="account.view_account_invoice_filter"/>
<field name="arch" type="xml">
<xpath expr="//filter[@name='late']" position="after">
<separator/>
<filter name="error" string="E-invoice error" domain="[('l10n_it_send_state', '=', 'invalid')]"/>
<filter name="sent" string="E-invoice sent" domain="[('l10n_it_send_state', '=', 'sent')]"/>
<filter name="no_sent" string="E-invoice to send" domain="[('l10n_it_send_state', 'in', ['to_send',False])]"/>
<filter name="delivered" string="E-invoice delivered" domain="[('l10n_it_send_state', 'in', ['delivered', 'delivered_accepted', 'delivered_refused', 'delivered_expired', 'failed_delivery'])]"/>
</xpath>
<xpath expr="//filter[@name='status']" position="after">
<filter name="send_status" string="Send status" context="{'group_by':'l10n_it_send_state'}"/>
</xpath>
</field>
</record>
<record id="l10n_it_ddt" model="ir.ui.view">
<field name="name">ddt.form.l10n.it</field>
<field name="model">l10n_it.ddt</field>
<field name="arch" type="xml">
<form>
<group>
<field name="name"/>
<field name="date"/>
</group>
</form>
</field>
</record>
<record id="l10n_it_ddt_list_view" model="ir.ui.view">
<field name="name">l10n_it.ddt.list.view</field>
<field name="model">l10n_it.ddt</field>
<field name="arch" type="xml">
<tree>
<field name="name"/>
<field name="date"/>
</tree>
</field>
</record>
<record id="action_ddt_account" model="ir.actions.act_window">
<field name="name">Transport Document</field>
<field name="res_model">l10n_it.ddt</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="l10n_it_ddt_list_view"/>
</record>
<menuitem
name="DDT"
parent="account.account_account_menu"
action="action_ddt_account"
id="menu_action_ddt_account"
sequence="15"
groups="base.group_no_one"/>
</odoo>