diff --git a/addons/l10n_it_edi/__manifest__.py b/addons/l10n_it_edi/__manifest__.py index f0a88555d0a..c0ba05dd435 100644 --- a/addons/l10n_it_edi/__manifest__.py +++ b/addons/l10n_it_edi/__manifest__.py @@ -7,7 +7,10 @@ 'version': '0.3', 'depends': [ 'l10n_it', - 'account_edi' + # Although account_edi is a dependency of account_edi_proxy_client, + # it is here because it's in the auto-install + 'account_edi', + 'account_edi_proxy_client', ], 'auto_install': ['l10n_it', 'account_edi'], 'author': 'Odoo', @@ -21,6 +24,8 @@ E-invoice implementation 'data/account_edi_data.xml', 'data/invoice_it_template.xml', 'data/invoice_it_simplified_template.xml', + 'data/ir_cron.xml', + 'views/res_config_settings_views.xml', 'views/l10n_it_view.xml', ], 'demo': [ diff --git a/addons/l10n_it_edi_sdicoop/data/cron.xml b/addons/l10n_it_edi/data/ir_cron.xml similarity index 100% rename from addons/l10n_it_edi_sdicoop/data/cron.xml rename to addons/l10n_it_edi/data/ir_cron.xml diff --git a/addons/l10n_it_edi/i18n/it.po b/addons/l10n_it_edi/i18n/it.po index b4f5df975df..dbeea228c60 100644 --- a/addons/l10n_it_edi/i18n/it.po +++ b/addons/l10n_it_edi/i18n/it.po @@ -10,6 +10,7 @@ msgstr "" "PO-Revision-Date: 2022-03-29 14:31+0000\n" "Last-Translator: \n" "Language-Team: \n" +"Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" @@ -89,6 +90,24 @@ msgid "" "'Scissione dei pagamenti' is not compatible with exoneration of kind 'N6'" msgstr "'Scissione dei pagamenti' non è compatibile con l'esenzione di tipo 'N6'" +#. module: l10n_it_edi +#: model_terms:ir.ui.view,arch_db:l10n_it_edi.res_config_settings_view_form +msgid "" +"\n" +" Fattura Elettronica mode\n" +" " +msgstr "Modalitá Fattura Elettronica" + +#. module: l10n_it_edi +#: model_terms:ir.ui.view,arch_db:l10n_it_edi.res_config_settings_view_form +msgid "Allow Odoo to process invoices" +msgstr "Permetti ad Odoo di inviare le fatture" + +#. module: l10n_it_edi +#: model_terms:ir.ui.view,arch_db:l10n_it_edi.res_config_settings_view_form +msgid "A Demo service is in use." +msgstr "Un servizio Demo è in uso" + #. module: l10n_it_edi #: code:addons/l10n_it_edi/models/res_company.py:0 #, python-format @@ -102,6 +121,17 @@ msgstr "Tutti i campi che riguardano l'Indice Economico e Amministrativo devono msgid "Attachment from XML" msgstr "Allegato dall'XML" +#. module: l10n_it_edi +#: model_terms:ir.ui.view,arch_db:l10n_it_edi.res_config_settings_view_form +msgid "An Official or Test service has been registered." +msgstr "É giá stato registrato un servizio Ufficiale o di Test." + +#. module: l10n_it_edi +#: code:addons/l10n_it_edi/models/account_edi_format.py:0 +#, python-format +msgid "Attached file is empty" +msgstr "Il file allegato è vuoto" + #. module: l10n_it_edi #: code:addons/l10n_it_edi/models/account_edi_format.py:0 #: code:addons/l10n_it_edi/models/account_edi_format.py:0 @@ -109,6 +139,11 @@ msgstr "Allegato dall'XML" msgid "Bank account not found, useful informations from XML file:" msgstr "Conto bancario non trovato, informazioni utili dal file XML:" +#. module: l10n_it_edi +#: model_terms:ir.ui.view,arch_db:l10n_it_edi.res_config_settings_view_form +msgid "By checking this box, I accept that Odoo may process my invoices." +msgstr "Selezionando questa casella, accetto che Odoo invii le mie fatture." + #. 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 @@ -141,6 +176,11 @@ msgstr "L'azienda ha un rappresentante fiscale" msgid "Company listed on the register of companies" msgstr "L'azienda compare nel Registro delle Imprese" +#. module: l10n_it_edi +#: model:ir.model,name:l10n_it_edi.model_res_config_settings +msgid "Config Settings" +msgstr "Impostazione configurazioni" + #. 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." @@ -189,12 +229,15 @@ msgid "" msgstr "Consegna non riuscita, il SdI certifica che ha ricevuto la fattura " "e che non è stato possibile consegnare il file al destinatario" +#. module: l10n_it_edi +#: model:ir.model.fields.selection,name:l10n_it_edi.selection__res_config_settings__l10n_it_edi_sdicoop_demo_mode__demo +msgid "Demo" +msgstr "Demo" + #. module: l10n_it_edi #: model:ir.model.fields,field_description:l10n_it_edi.field_account_edi_format__display_name #: model:ir.model.fields,field_description:l10n_it_edi.field_account_move__display_name #: model:ir.model.fields,field_description:l10n_it_edi.field_account_tax__display_name -#: model:ir.model.fields,field_description:l10n_it_edi.field_fetchmail_server__display_name -#: model:ir.model.fields,field_description:l10n_it_edi.field_ir_mail_server__display_name #: model:ir.model.fields,field_description:l10n_it_edi.field_l10n_it_ddt__display_name #: model:ir.model.fields,field_description:l10n_it_edi.field_res_company__display_name #: model:ir.model.fields,field_description:l10n_it_edi.field_res_partner__display_name @@ -237,6 +280,11 @@ msgstr "Il SdI certifica che ha ricevuto la Fattura e che non è stato possibile msgid "Economic and Administrative Index" msgstr "Indice Economico e Amministrativo" +#. module: l10n_it_edi +#: model_terms:ir.ui.view,arch_db:l10n_it_edi.res_config_settings_view_form +msgid "Electronic Document Invoicing" +msgstr "Interscambio di dati in formato elettronico" + #. 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.res_company_form_l10n_it @@ -308,6 +356,20 @@ msgstr "Termine massimo di decorrenza per l'accettazione o il rifiuto della comu msgid "FatturaPA Send State" msgstr "Stato di invio della FatturaPA" +#. module: l10n_it_edi +#: model:ir.model.fields,field_description:l10n_it_edi.field_account_bank_statement_line__l10n_it_edi_transaction +#: model:ir.model.fields,field_description:l10n_it_edi.field_account_move__l10n_it_edi_transaction +#: model:ir.model.fields,field_description:l10n_it_edi.field_account_payment__l10n_it_edi_transaction +msgid "FatturaPA Transaction" +msgstr "Transazione FatturaPA" + +#. module: l10n_it_edi +#: model:ir.actions.server,name:l10n_it_edi.ir_cron_receive_fattura_pa_invoice_ir_actions_server +#: model:ir.cron,cron_name:l10n_it_edi.ir_cron_receive_fattura_pa_invoice +#: model:ir.cron,name:l10n_it_edi.ir_cron_receive_fattura_pa_invoice +msgid "FatturaPA: Receive invoices from the exchange system" +msgstr "FatturaPA: Ricezione fatture dal Sistema d'Interscambio" + #. 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" @@ -327,10 +389,9 @@ msgstr "Esente dalle tasse (Italia)" #: model:ir.model.fields,field_description:l10n_it_edi.field_account_edi_format__id #: model:ir.model.fields,field_description:l10n_it_edi.field_account_move__id #: model:ir.model.fields,field_description:l10n_it_edi.field_account_tax__id -#: model:ir.model.fields,field_description:l10n_it_edi.field_fetchmail_server__id -#: model:ir.model.fields,field_description:l10n_it_edi.field_ir_mail_server__id #: model:ir.model.fields,field_description:l10n_it_edi.field_l10n_it_ddt__id #: model:ir.model.fields,field_description:l10n_it_edi.field_res_company__id +#: model:ir.model.fields,field_description:l10n_it_edi.field_res_config_settings__id #: model:ir.model.fields,field_description:l10n_it_edi.field_res_partner__id msgid "ID" msgstr "ID" @@ -355,6 +416,20 @@ msgid "" msgstr "Se l'Imposta risulta essere un'Esenzione, bisogna compilare il Tipo " "di Esenzione e il suo valore dev'essere 0.0." +#. module: l10n_it_edi +#: model_terms:ir.ui.view,arch_db:l10n_it_edi.res_config_settings_view_form +msgid "" +"In demo mode Odoo will just simulate the sending of invoices to the government.
\n" +" In test mode (experimental) Odoo will send the invoices to a non-production service.\n" +" Saving this change will direct all companies on this database to this use this configuration.\n" +" Once registered for testing or official, the mode cannot be changed." +msgstr "Nella modalitá Demo, Odoo simulerá l'invio delle fatture " +"all'Agenzia.
Nella modalitá di Test (sperimentale) Odoo manderá le fatture " +"a un servizio non di produzione. Salvando quest'impostazione fará sì che " +"tutte le Aziende di questo Database usino questa configurazione. " +"Una volta registrato per la modalitá Test o Ufficiale, la modalitá non può " +"più essere cambiata." + #. module: l10n_it_edi #: model:ir.model,name:l10n_it_edi.model_fetchmail_server msgid "Incoming Mail Server" @@ -386,11 +461,17 @@ msgstr "Riferimento Fattura" msgid "" "Invoices for PA are not managed by Odoo, you can download the document and " "send it on your own." -msgstr "Le Fatture verso la PA non sono gestite da Odoo, puoi scaricare il " -"documento ed inviarlo manualmente." +msgstr "Le Fatture per la Pubblica Amministrazione non sono gestite da Odoo. " +"É possibile scaricare il documento e inviarlo tramite il sito dell'Agenzia delle Entrate." + +#. module: l10n_it_edi +#: model:ir.model.fields,field_description:l10n_it_edi.field_res_config_settings__is_edi_proxy_active +msgid "Is Edi Proxy Active" +msgstr "É attivo il Proxy EDI" #. module: l10n_it_edi #: code:addons/l10n_it_edi/models/account_invoice.py:0 +#: code:addons/l10n_it_edi/models/account_edi_format.py:0 #, python-format msgid "Italian invoice: %s" msgstr "Fattura italiana: %s" @@ -400,6 +481,21 @@ msgstr "Fattura italiana: %s" msgid "Journal Entry" msgstr "Movimento Contabile" +#. module: l10n_it_edi +#: model:ir.model.fields,field_description:l10n_it_edi.field_res_config_settings__l10n_it_edi_proxy_current_state +msgid "L10N It Edi Proxy Current State" +msgstr "Stato corrente del Proxy L10n It EDI" + +#. module: l10n_it_edi +#: model:ir.model.fields,field_description:l10n_it_edi.field_res_config_settings__l10n_it_edi_sdicoop_demo_mode +msgid "L10N It Edi Sdicoop Demo Mode" +msgstr "Modalitá demo L10n It EDI Sdicoop" + +#. module: l10n_it_edi +#: model:ir.model.fields,field_description:l10n_it_edi.field_res_config_settings__l10n_it_edi_sdicoop_register +msgid "L10N It Edi Sdicoop Register" +msgstr "Registra L10n It EDI Sdicoop" + #. module: l10n_it_edi #: model:ir.model.fields,field_description:l10n_it_edi.field_account_bank_statement_line__l10n_it_einvoice_name #: model:ir.model.fields,field_description:l10n_it_edi.field_account_move__l10n_it_einvoice_name @@ -421,13 +517,12 @@ msgstr "Ha rappresentante fiscale" #: model:ir.model.fields,field_description:l10n_it_edi.field_account_edi_format____last_update #: model:ir.model.fields,field_description:l10n_it_edi.field_account_move____last_update #: model:ir.model.fields,field_description:l10n_it_edi.field_account_tax____last_update -#: model:ir.model.fields,field_description:l10n_it_edi.field_fetchmail_server____last_update -#: model:ir.model.fields,field_description:l10n_it_edi.field_ir_mail_server____last_update #: model:ir.model.fields,field_description:l10n_it_edi.field_l10n_it_ddt____last_update #: model:ir.model.fields,field_description:l10n_it_edi.field_res_company____last_update +#: model:ir.model.fields,field_description:l10n_it_edi.field_res_config_settings____last_update #: model:ir.model.fields,field_description:l10n_it_edi.field_res_partner____last_update msgid "Last Modified on" -msgstr "Ultima modifica il" +msgstr "Data Modifica" #. module: l10n_it_edi #: model:ir.model.fields,field_description:l10n_it_edi.field_l10n_it_ddt__write_uid @@ -518,6 +613,11 @@ msgstr "Numero nel Registro delle Imprese" msgid "Numero DDT" msgstr "Numero DDT" +#. module: l10n_it_edi +#: model:ir.model.fields.selection,name:l10n_it_edi.selection__res_config_settings__l10n_it_edi_sdicoop_demo_mode__prod +msgid "Official" +msgstr "Ufficiale" + #. module: l10n_it_edi #: code:addons/l10n_it_edi/models/ir_mail_server.py:0 #, python-format @@ -586,6 +686,14 @@ msgstr "Per favore, configurare l'email PEC statale \n" msgid "Please configure Username for this Server PEC" msgstr "Per favore, configurare il Nome Utente per questo server email PEC" +#. module: l10n_it_edi +#: code:addons/l10n_it_edi/models/account_edi_format.py:0 +#, python-format +msgid "" +"Please fill your codice fiscale to be able to receive invoices from " +"FatturaPA" +msgstr "Per favore, inserire il Codice Fiscale per poter ricevere le fatture da FatturaPA" + #. 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." @@ -823,6 +931,109 @@ msgstr "Questa fattura è consegnata e scaduta (è passato il termine " msgid "This invoice is delivered and refused by destinatory" msgstr "Questa fattura è consegnata e rifiutata dal destinatario" +#. module: l10n_it_edi +#: code:addons/l10n_it_edi/models/account_edi_format.py:0 +#, python-format +msgid "Service momentarily unavailable" +msgstr "Servizio momentaneamente non disponibile" + +#. module: l10n_it_edi +#: model:ir.model.fields.selection,name:l10n_it_edi.selection__res_config_settings__l10n_it_edi_sdicoop_demo_mode__test +msgid "Test (experimental)" +msgstr "Test (sperimentale)" + +#. module: l10n_it_edi +#: code:addons/l10n_it_edi/models/res_config_settings.py:0 +#, python-format +msgid "" +"The company has already registered with the service as 'Test' or 'Official'," +" it cannot change." +msgstr "L'Azienda è giá registrata al servizio con la modalitá Test o Officiale, non può essere cambiata." + +#. module: l10n_it_edi +#: code:addons/l10n_it_edi/models/account_edi_format.py:0 +#, python-format +msgid "" +"The invoice has been issued, but the delivery to the Addressee has failed. " +"You will be required to send a courtesy copy of the invoice to your customer" +" through another channel, outside of the Exchange System, and promptly " +"notify him that the original is deposited in his personal area on the portal" +" \"Invoices and Fees\" of the Revenue Agency." +msgstr "La fattura è stata inviata, ma la consegna al Destinatario è fallita. " +"É necessario mandare una copia di cortesia della fattura al cliente " +"tramite un altro canale al di fuori dell'SdI, e notificarlo che l'originale " +"è depositato nella sua Area Personale all' interno del portale 'Fatture e Corrispettivi' " +"sul sito dell'Agenzia delle Entrate" + +#. module: l10n_it_edi +#: code:addons/l10n_it_edi/models/account_edi_format.py:0 +#, python-format +msgid "" +"The invoice has been issued, but the delivery to the Public Administration " +"has failed. The Exchange System will contact them to report the problem and " +"request that they provide a solution. During the following 10 days, the " +"Exchange System will try to forward the FatturaPA file to the Public " +"Administration in question again. Should this also fail, the System will " +"notify Odoo of the failed delivery, and you will be required to send the " +"invoice to the Administration through another channel, outside of the " +"Exchange System." +msgstr "La fattura è stata inviata, ma la consegna alla Pubblica Amministrazione " +"è fallita. Il Sistema d'Interscambio contatterá il Destinatario per far rapporto " +"sul problema, e richiederá che venga approntata una soluzione. Durante i " +"10 giorni successivi, il Sistema d'Interscambio proverá a inoltrare il file " +"FatturaPA in questione ancora. Dovesse fallire anche questo tentativo, il " +"sistema notificherá Odoo della mancata consegna, e sará necessario inviare " +"la fattura tramite un altro canale, al di fuori dell'SdI" + +#. module: l10n_it_edi +#: code:addons/l10n_it_edi/models/account_edi_format.py:0 +#, python-format +msgid "The invoice has been refused by the Exchange System" +msgstr "La fattura è stata rifiutata dal Sistema d'Interscambio" + +#. module: l10n_it_edi +#: code:addons/l10n_it_edi/models/account_edi_format.py:0 +#, python-format +msgid "" +"The invoice has been succesfully transmitted. The addressee has 15 days to " +"accept or reject it." +msgstr "La fattura è stata trasmessa con successo. Il Destinatario ha 15 giorni per accettarla o rifiutarla." + +#. module: l10n_it_edi +#: code:addons/l10n_it_edi/models/account_edi_format.py:0 +#, python-format +msgid "The invoice was refused by the addressee." +msgstr "La fattura è stata rifiutata dal Destinatario." + +#. module: l10n_it_edi +#: code:addons/l10n_it_edi/models/account_edi_format.py:0 +#, python-format +msgid "" +"The invoice was successfully transmitted to the Public Administration and we" +" are waiting for confirmation" +msgstr "La fattura è stata trasmessa con successo alla Pubblica Amministrazione " +"e siamo in attesa di conferma." + +#. module: l10n_it_edi +#: code:addons/l10n_it_edi/models/account_edi_format.py:0 +#, python-format +msgid "" +"The invoice was successfully transmitted to the Public Administration and we" +" are waiting for confirmation." +msgstr "La fattura è stata inviata con successo alla Pubblica Amministrazione " +"e siamo in attesa di conferma." + +#. module: l10n_it_edi +#: code:addons/l10n_it_edi/models/account_edi_format.py:0 +#, python-format +msgid "" +"This invoice number had already been submitted to the SdI, so it is set as Sent. Please verify that the system is correctly configured, because the correct flow does not need to send the same invoice twice for any reason.\n" +" Original message from the SDI: %s" +msgstr "Una fattura con questo numero è giá stata inviata all'SdI, quindi viene impostata come inviata. " +"Per favore, verificare che il sistema sia configurato correttamente, perchè in nessun caso il sistema " +"ha motivo di inviare la stessa fattura più volte.\n" +"Messaggio originale dall'SdI: %s" + #. module: l10n_it_edi #: code:addons/l10n_it_edi/models/account_edi_format.py:0 #, python-format @@ -851,6 +1062,12 @@ msgstr "Numero Documento Di Trasporto" msgid "Transport informations from XML file:" msgstr "Informazioni di Trasporto dal file XML:" +#. module: l10n_it_edi +#: code:addons/l10n_it_edi/models/account_edi_format.py:0 +#, python-format +msgid "Unauthorized user" +msgstr "Utente non autorizzato" + #. 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" @@ -862,6 +1079,12 @@ msgstr "Data scadenza IVA" msgid "Vendor not found, useful informations from XML file:" msgstr "Fornitore non trovato, informazioni dal file XML:" +#. module: l10n_it_edi +#: code:addons/l10n_it_edi/models/account_edi_format.py:0 +#, python-format +msgid "You are not allowed to check the status of this invoice." +msgstr "Non sei autorizzato a controllare lo stato di questa fattura." + #. module: l10n_it_edi #: code:addons/l10n_it_edi/models/account_edi_format.py:0 #, python-format @@ -870,6 +1093,16 @@ msgid "" "no errors" msgstr "Non puoi rigenerare una Fattura Elettronica se è giá stata inviata senza errori." +#. module: l10n_it_edi +#: code:addons/l10n_it_edi/models/account_edi_format.py:0 +#: code:addons/l10n_it_edi/models/account_edi_format.py:0 +#: code:addons/l10n_it_edi/models/account_edi_format.py:0 +#, python-format +msgid "" +"You must accept the terms and conditions in the settings to use FatturaPA." +msgstr "Devi accettare i Termini e le Condizioni d'uso nelle impostazioni " +"per poter utilizzare FatturaPA." + #. module: l10n_it_edi #: code:addons/l10n_it_edi/models/res_company.py:0 #, python-format diff --git a/addons/l10n_it_edi/i18n/l10n_it_edi.pot b/addons/l10n_it_edi/i18n/l10n_it_edi.pot index 418a8d6df17..6f668e4515f 100644 --- a/addons/l10n_it_edi/i18n/l10n_it_edi.pot +++ b/addons/l10n_it_edi/i18n/l10n_it_edi.pot @@ -17,18 +17,11 @@ msgstr "" #. module: l10n_it_edi #: code:addons/l10n_it_edi/models/account_edi_format.py:0 +#: code:addons/l10n_it_edi/models/account_edi_format.py:0 #, 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:0 -#, 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_edi_format.py:0 #, python-format @@ -89,6 +82,34 @@ 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.res_config_settings_view_form +msgid "" +"\n" +" Fattura Elettronica mode\n" +" " +msgstr "" + +#. module: l10n_it_edi +#: model_terms:ir.ui.view,arch_db:l10n_it_edi.res_config_settings_view_form +msgid "Allow Odoo to process invoices" +msgstr "" + +#. module: l10n_it_edi +#: model_terms:ir.ui.view,arch_db:l10n_it_edi.res_config_settings_view_form +msgid "A Demo service is in use." +msgstr "" + +#. module: l10n_it_edi +#: code:addons/l10n_it_edi/models/account_invoice.py:0 +#, python-format +msgid "" +"A simplified invoice was created instead of an ordinary one. This is because" +" the invoice is a domestic invoice with " +"a total amount of less than or equal to 400€ and the customer's address is " +"incomplete." +msgstr "" + #. module: l10n_it_edi #: code:addons/l10n_it_edi/models/res_company.py:0 #, python-format @@ -96,6 +117,23 @@ msgid "" "All fields about the Economic and Administrative Index must be completed." msgstr "" +#. module: l10n_it_edi +#: code:addons/l10n_it_edi/models/account_edi_format.py:0 +#, python-format +msgid "All quantities should be positive." +msgstr "" + +#. module: l10n_it_edi +#: model_terms:ir.ui.view,arch_db:l10n_it_edi.res_config_settings_view_form +msgid "An Official or Test service has been registered." +msgstr "" + +#. module: l10n_it_edi +#: code:addons/l10n_it_edi/models/account_edi_format.py:0 +#, python-format +msgid "Attached file is empty" +msgstr "" + #. module: l10n_it_edi #: code:addons/l10n_it_edi/models/account_edi_format.py:0 #, python-format @@ -109,6 +147,19 @@ msgstr "" msgid "Bank account not found, useful informations from XML file:" msgstr "" +#. module: l10n_it_edi +#: model_terms:ir.ui.view,arch_db:l10n_it_edi.res_config_settings_view_form +msgid "By checking this box, I accept that Odoo may process my invoices." +msgstr "" + +#. module: l10n_it_edi +#: code:addons/l10n_it_edi/models/account_edi_format.py:0 +#, python-format +msgid "" +"Cannot apply Reverse Charge to a bill which contains both services and " +"goods." +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 @@ -126,11 +177,6 @@ msgstr "" 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" @@ -142,8 +188,8 @@ msgid "Company listed on the register of companies" 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." +#: model:ir.model,name:l10n_it_edi.model_res_config_settings +msgid "Config Settings" msgstr "" #. module: l10n_it_edi @@ -182,30 +228,20 @@ msgid "Dati Bollo" msgstr "" #. module: l10n_it_edi -#: model:ir.model.fields.selection,name:l10n_it_edi.selection__account_move__l10n_it_send_state__failed_delivery -msgid "" -"Delivery impossible, ES certify that it has received the invoice and that " -"the file could not be delivered to the addressee" +#: model:ir.model.fields.selection,name:l10n_it_edi.selection__res_config_settings__l10n_it_edi_sdicoop_demo_mode__demo +msgid "Demo" msgstr "" #. module: l10n_it_edi #: model:ir.model.fields,field_description:l10n_it_edi.field_account_edi_format__display_name #: model:ir.model.fields,field_description:l10n_it_edi.field_account_move__display_name #: model:ir.model.fields,field_description:l10n_it_edi.field_account_tax__display_name -#: model:ir.model.fields,field_description:l10n_it_edi.field_fetchmail_server__display_name -#: model:ir.model.fields,field_description:l10n_it_edi.field_ir_mail_server__display_name #: model:ir.model.fields,field_description:l10n_it_edi.field_l10n_it_ddt__display_name #: model:ir.model.fields,field_description:l10n_it_edi.field_res_company__display_name #: model:ir.model.fields,field_description:l10n_it_edi.field_res_partner__display_name msgid "Display Name" msgstr "" -#. module: l10n_it_edi -#: code:addons/l10n_it_edi/models/ir_mail_server.py:0 -#, python-format -msgid "E-Invoice is delivery to the destinatory:
%s" -msgstr "" - #. module: l10n_it_edi #: code:addons/l10n_it_edi/models/account_invoice.py:0 #, python-format @@ -223,16 +259,13 @@ msgid "EDI format" msgstr "" #. module: l10n_it_edi -#: code:addons/l10n_it_edi/models/ir_mail_server.py:0 -#, python-format -msgid "" -"ES certify that it has received the invoice and that the file" -" could not be delivered to the addressee.
%s" +#: 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.res_company_form_l10n_it -msgid "Economic and Administrative Index" +#: model_terms:ir.ui.view,arch_db:l10n_it_edi.res_config_settings_view_form +msgid "Electronic Document Invoicing" msgstr "" #. module: l10n_it_edi @@ -248,36 +281,6 @@ msgstr "" 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 -#: code:addons/l10n_it_edi/models/account_invoice.py:0 -#, 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:0 -#, 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:0 -#, python-format -msgid "Errors in the E-Invoice :
%s" -msgstr "" - #. module: l10n_it_edi #: model:ir.model.fields,field_description:l10n_it_edi.field_account_tax__l10n_it_kind_exoneration msgid "Exoneration" @@ -289,18 +292,24 @@ msgid "Exoneration type" msgstr "" #. module: l10n_it_edi -#: code:addons/l10n_it_edi/models/ir_mail_server.py:0 -#, python-format -msgid "" -"Expiration of the maximum term for communication of acceptance/refusal:" -" %s
%s" +#: model:ir.model.fields,field_description:l10n_it_edi.field_account_bank_statement_line__l10n_it_edi_attachment_id +#: model:ir.model.fields,field_description:l10n_it_edi.field_account_move__l10n_it_edi_attachment_id +#: model:ir.model.fields,field_description:l10n_it_edi.field_account_payment__l10n_it_edi_attachment_id +msgid "FatturaPA Attachment" msgstr "" #. module: l10n_it_edi -#: model:ir.model.fields,field_description:l10n_it_edi.field_account_bank_statement_line__l10n_it_send_state -#: model:ir.model.fields,field_description:l10n_it_edi.field_account_move__l10n_it_send_state -#: model:ir.model.fields,field_description:l10n_it_edi.field_account_payment__l10n_it_send_state -msgid "FatturaPA Send State" +#: model:ir.model.fields,field_description:l10n_it_edi.field_account_bank_statement_line__l10n_it_edi_transaction +#: model:ir.model.fields,field_description:l10n_it_edi.field_account_move__l10n_it_edi_transaction +#: model:ir.model.fields,field_description:l10n_it_edi.field_account_payment__l10n_it_edi_transaction +msgid "FatturaPA Transaction" +msgstr "" + +#. module: l10n_it_edi +#: model:ir.actions.server,name:l10n_it_edi.ir_cron_receive_fattura_pa_invoice_ir_actions_server +#: model:ir.cron,cron_name:l10n_it_edi.ir_cron_receive_fattura_pa_invoice +#: model:ir.cron,name:l10n_it_edi.ir_cron_receive_fattura_pa_invoice +msgid "FatturaPA: Receive invoices from the exchange system" msgstr "" #. module: l10n_it_edi @@ -308,11 +317,6 @@ msgstr "" msgid "Fiscal code of your company" 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)" @@ -335,12 +339,6 @@ msgstr "" 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 #: code:addons/l10n_it_edi/models/account_invoice.py:0 #, python-format @@ -350,8 +348,18 @@ msgid "" msgstr "" #. module: l10n_it_edi -#: model:ir.model,name:l10n_it_edi.model_fetchmail_server -msgid "Incoming Mail Server" +#: model_terms:ir.ui.view,arch_db:l10n_it_edi.res_config_settings_view_form +msgid "" +"In demo mode Odoo will just simulate the sending of invoices to the government.
\n" +" In test mode (experimental) Odoo will send the invoices to a non-production service.\n" +" Saving this change will direct all companies on this database to this use this configuration.\n" +" Once registered for testing or official, the mode cannot be changed." +msgstr "" + +#. module: l10n_it_edi +#: code:addons/l10n_it_edi/models/account_edi_format.py:0 +#, python-format +msgid "In line %s, you must select one and only one tax by line." msgstr "" #. module: l10n_it_edi @@ -362,12 +370,6 @@ msgid "" "person and '12345678901' or 'IT12345678901' for businesses." msgstr "" -#. module: l10n_it_edi -#: code:addons/l10n_it_edi/models/account_invoice.py:0 -#, python-format -msgid "Invalid configuration:" -msgstr "" - #. module: l10n_it_edi #: model:ir.model.fields,field_description:l10n_it_edi.field_l10n_it_ddt__invoice_id msgid "Invoice Reference" @@ -382,6 +384,12 @@ msgid "" msgstr "" #. module: l10n_it_edi +#: model:ir.model.fields,field_description:l10n_it_edi.field_res_config_settings__is_edi_proxy_active +msgid "Is Edi Proxy Active" +msgstr "" + +#. module: l10n_it_edi +#: code:addons/l10n_it_edi/models/account_edi_format.py:0 #: code:addons/l10n_it_edi/models/account_invoice.py:0 #, python-format msgid "Italian invoice: %s" @@ -392,6 +400,21 @@ msgstr "" msgid "Journal Entry" msgstr "" +#. module: l10n_it_edi +#: model:ir.model.fields,field_description:l10n_it_edi.field_res_config_settings__l10n_it_edi_proxy_current_state +msgid "L10N It Edi Proxy Current State" +msgstr "" + +#. module: l10n_it_edi +#: model:ir.model.fields,field_description:l10n_it_edi.field_res_config_settings__l10n_it_edi_sdicoop_demo_mode +msgid "L10N It Edi Sdicoop Demo Mode" +msgstr "" + +#. module: l10n_it_edi +#: model:ir.model.fields,field_description:l10n_it_edi.field_res_config_settings__l10n_it_edi_sdicoop_register +msgid "L10N It Edi Sdicoop Register" +msgstr "" + #. module: l10n_it_edi #: model:ir.model.fields,field_description:l10n_it_edi.field_account_bank_statement_line__l10n_it_einvoice_name #: model:ir.model.fields,field_description:l10n_it_edi.field_account_move__l10n_it_einvoice_name @@ -413,8 +436,6 @@ msgstr "" #: model:ir.model.fields,field_description:l10n_it_edi.field_account_edi_format____last_update #: model:ir.model.fields,field_description:l10n_it_edi.field_account_move____last_update #: model:ir.model.fields,field_description:l10n_it_edi.field_account_tax____last_update -#: model:ir.model.fields,field_description:l10n_it_edi.field_fetchmail_server____last_update -#: model:ir.model.fields,field_description:l10n_it_edi.field_ir_mail_server____last_update #: model:ir.model.fields,field_description:l10n_it_edi.field_l10n_it_ddt____last_update #: model:ir.model.fields,field_description:l10n_it_edi.field_res_company____last_update #: model:ir.model.fields,field_description:l10n_it_edi.field_res_partner____last_update @@ -431,11 +452,6 @@ msgstr "" 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" @@ -451,17 +467,6 @@ msgstr "" msgid "MP05" msgstr "" -#. module: l10n_it_edi -#: model:ir.model,name:l10n_it_edi.model_ir_mail_server -msgid "Mail Server" -msgstr "" - -#. module: l10n_it_edi -#: code:addons/l10n_it_edi/models/account_invoice.py:0 -#, 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 "" @@ -481,21 +486,11 @@ msgid "" "invoice." msgstr "" -#. module: l10n_it_edi -#: model:ir.model.fields.selection,name:l10n_it_edi.selection__account_move__l10n_it_send_state__new -msgid "New" -msgstr "" - #. module: l10n_it_edi #: model:ir.model.fields.selection,name:l10n_it_edi.selection__res_company__l10n_it_eco_index_sole_shareholder__no msgid "Not a limited liability company" msgstr "" -#. module: l10n_it_edi -#: model:ir.model.fields.selection,name:l10n_it_edi.selection__account_move__l10n_it_send_state__to_send -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" @@ -507,20 +502,8 @@ msgid "Numero DDT" msgstr "" #. module: l10n_it_edi -#: code:addons/l10n_it_edi/models/ir_mail_server.py:0 -#, python-format -msgid "Original E-invoice XML file" -msgstr "" - -#. module: l10n_it_edi -#: model:ir.model.fields.selection,name:l10n_it_edi.selection__account_move__l10n_it_send_state__other -msgid "Other" -msgstr "" - -#. module: l10n_it_edi -#: code:addons/l10n_it_edi/models/ir_mail_server.py:0 -#, python-format -msgid "Outcome notice: %s
%s" +#: model:ir.model.fields.selection,name:l10n_it_edi.selection__res_config_settings__l10n_it_edi_sdicoop_demo_mode__prod +msgid "Official" msgstr "" #. module: l10n_it_edi @@ -536,6 +519,7 @@ msgstr "" #. module: l10n_it_edi #: model_terms:ir.ui.view,arch_db:l10n_it_edi.account_invoice_it_FatturaPA_export +#: model_terms:ir.ui.view,arch_db:l10n_it_edi.account_invoice_it_simplified_FatturaPA_export msgid "PDF" msgstr "" @@ -545,32 +529,17 @@ msgstr "" msgid "PEC e-mail" msgstr "" -#. module: l10n_it_edi -#: code:addons/l10n_it_edi/models/ir_mail_server.py:0 -#, python-format -msgid "PEC mail server must be of type IMAP." -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 #: model:ir.model.fields.selection,name:l10n_it_edi.selection__res_company__l10n_it_eco_index_sole_shareholder__sm msgid "Più soci" msgstr "" #. module: l10n_it_edi -#: code:addons/l10n_it_edi/models/ir_mail_server.py:0 +#: code:addons/l10n_it_edi/models/account_edi_format.py:0 #, python-format -msgid "Please configure Government PEC-mail\tin company settings" -msgstr "" - -#. module: l10n_it_edi -#: code:addons/l10n_it_edi/models/ir_mail_server.py:0 -#, python-format -msgid "Please configure Username for this Server PEC" +msgid "" +"Please fill your codice fiscale to be able to receive invoices from " +"FatturaPA" msgstr "" #. module: l10n_it_edi @@ -589,30 +558,9 @@ msgid "SI" msgstr "" #. module: l10n_it_edi -#: code:addons/l10n_it_edi/models/account_invoice.py:0 +#: code:addons/l10n_it_edi/models/account_edi_format.py:0 #, python-format -msgid "Sending file: %s" -msgstr "" - -#. module: l10n_it_edi -#: code:addons/l10n_it_edi/models/account_invoice.py:0 -#, python-format -msgid "Sending file: %s to ES: %s" -msgstr "" - -#. module: l10n_it_edi -#: model:ir.model.fields.selection,name:l10n_it_edi.selection__account_move__l10n_it_send_state__invalid -msgid "Sent, but invalid" -msgstr "" - -#. module: l10n_it_edi -#: model:ir.model.fields.selection,name:l10n_it_edi.selection__account_move__l10n_it_send_state__sent -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" +msgid "Service momentarily unavailable" msgstr "" #. module: l10n_it_edi @@ -685,24 +633,24 @@ msgid "Tax representative partner %s of %s must have a tax number." msgstr "" #. module: l10n_it_edi -#: code:addons/l10n_it_edi/models/ir_mail_server.py:0 -#, 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 " -"information:
%s" +#: model:ir.model.fields.selection,name:l10n_it_edi.selection__res_config_settings__l10n_it_edi_sdicoop_demo_mode__test +msgid "Test (experimental)" msgstr "" #. module: l10n_it_edi #: code:addons/l10n_it_edi/models/account_edi_format.py:0 #, python-format msgid "" -"The buyer, %s, or his company must have either a VAT number either a tax " -"code (Codice Fiscale)." +"The buyer, %s, or his company must have a VAT number and/or a tax code " +"(Codice Fiscale)." +msgstr "" + +#. module: l10n_it_edi +#: code:addons/l10n_it_edi/models/res_config_settings.py:0 +#, python-format +msgid "" +"The company has already registered with the service as 'Test' or 'Official'," +" it cannot change." msgstr "" #. module: l10n_it_edi @@ -719,13 +667,69 @@ msgstr "" #: code:addons/l10n_it_edi/models/account_edi_format.py:0 #, python-format msgid "" -"The maximum length for VAT number is 30. %s have a VAT number too long: %s." +"The filename is duplicated. Try again (or adjust the FatturaPA Filename " +"sequence). Original message from the SDI: %s" msgstr "" #. module: l10n_it_edi #: code:addons/l10n_it_edi/models/account_edi_format.py:0 #, python-format -msgid "The seller must have a bank account." +msgid "" +"The invoice has been issued, but the delivery to the Addressee has failed. " +"You will be required to send a courtesy copy of the invoice to your customer" +" through another channel, outside of the Exchange System, and promptly " +"notify him that the original is deposited in his personal area on the portal" +" \"Invoices and Fees\" of the Revenue Agency." +msgstr "" + +#. module: l10n_it_edi +#: code:addons/l10n_it_edi/models/account_edi_format.py:0 +#, python-format +msgid "" +"The invoice has been issued, but the delivery to the Public Administration " +"has failed. The Exchange System will contact them to report the problem and " +"request that they provide a solution. During the following 10 days, the " +"Exchange System will try to forward the FatturaPA file to the Public " +"Administration in question again. Should this also fail, the System will " +"notify Odoo of the failed delivery, and you will be required to send the " +"invoice to the Administration through another channel, outside of the " +"Exchange System." +msgstr "" + +#. module: l10n_it_edi +#: code:addons/l10n_it_edi/models/account_edi_format.py:0 +#, python-format +msgid "The invoice has been refused by the Exchange System" +msgstr "" + +#. module: l10n_it_edi +#: code:addons/l10n_it_edi/models/account_edi_format.py:0 +#, python-format +msgid "" +"The invoice has been succesfully transmitted. The addressee has 15 days to " +"accept or reject it." +msgstr "" + +#. module: l10n_it_edi +#: code:addons/l10n_it_edi/models/account_edi_format.py:0 +#, python-format +msgid "The invoice was refused by the addressee." +msgstr "" + +#. module: l10n_it_edi +#: code:addons/l10n_it_edi/models/account_edi_format.py:0 +#: code:addons/l10n_it_edi/models/account_edi_format.py:0 +#, python-format +msgid "" +"The invoice was sent to FatturaPA, but we are still awaiting a response. " +"Click the link above to check for an update." +msgstr "" + +#. module: l10n_it_edi +#: code:addons/l10n_it_edi/models/account_edi_format.py:0 +#, python-format +msgid "" +"The maximum length for VAT number is 30. %s have a VAT number too long: %s." msgstr "" #. module: l10n_it_edi @@ -773,25 +777,11 @@ msgid "" msgstr "" #. module: l10n_it_edi -#: model:ir.model.fields.selection,name:l10n_it_edi.selection__account_move__l10n_it_send_state__delivered -msgid "This invoice is delivered" -msgstr "" - -#. module: l10n_it_edi -#: model:ir.model.fields.selection,name:l10n_it_edi.selection__account_move__l10n_it_send_state__delivered_accepted -msgid "This invoice is delivered and accepted by destinatory" -msgstr "" - -#. module: l10n_it_edi -#: model:ir.model.fields.selection,name:l10n_it_edi.selection__account_move__l10n_it_send_state__delivered_expired +#: code:addons/l10n_it_edi/models/account_edi_format.py:0 +#, python-format msgid "" -"This invoice is delivered and expired (expiry of the maximum term for " -"communication of acceptance/refusal)" -msgstr "" - -#. module: l10n_it_edi -#: model:ir.model.fields.selection,name:l10n_it_edi.selection__account_move__l10n_it_send_state__delivered_refused -msgid "This invoice is delivered and refused by destinatory" +"This invoice number had already been submitted to the SdI, so it is set as Sent. Please verify that the system is correctly configured, because the correct flow does not need to send the same invoice twice for any reason.\n" +" Original message from the SDI: %s" msgstr "" #. module: l10n_it_edi @@ -822,11 +812,25 @@ msgstr "" msgid "Transport informations from XML file:" msgstr "" +#. module: l10n_it_edi +#: code:addons/l10n_it_edi/models/account_edi_format.py:0 +#, python-format +msgid "Unauthorized user" +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_edi_format.py:0 +#, python-format +msgid "" +"Vendor bills sent as self-invoices to the SdI require a valid PA Index " +"(Codice Destinatario) on the company's contact." +msgstr "" + #. module: l10n_it_edi #: code:addons/l10n_it_edi/models/account_edi_format.py:0 #, python-format @@ -836,9 +840,16 @@ msgstr "" #. module: l10n_it_edi #: code:addons/l10n_it_edi/models/account_edi_format.py:0 #, python-format +msgid "You are not allowed to check the status of this invoice." +msgstr "" + +#. module: l10n_it_edi +#: code:addons/l10n_it_edi/models/account_edi_format.py:0 +#: code:addons/l10n_it_edi/models/account_edi_format.py:0 +#: code:addons/l10n_it_edi/models/account_edi_format.py:0 +#, python-format msgid "" -"You can't regenerate an E-Invoice when the first one is sent and there are " -"no errors" +"You must accept the terms and conditions in the settings to use FatturaPA." msgstr "" #. module: l10n_it_edi @@ -847,12 +858,6 @@ msgstr "" msgid "You must select a tax representative." msgstr "" -#. module: l10n_it_edi -#: code:addons/l10n_it_edi/models/account_edi_format.py:0 -#, python-format -msgid "You must select one and only one tax by line." -msgstr "" - #. module: l10n_it_edi #: code:addons/l10n_it_edi/models/res_company.py:0 #, python-format diff --git a/addons/l10n_it_edi/models/__init__.py b/addons/l10n_it_edi/models/__init__.py index 703ed068a9a..0465baacd14 100644 --- a/addons/l10n_it_edi/models/__init__.py +++ b/addons/l10n_it_edi/models/__init__.py @@ -3,8 +3,8 @@ from . import res_partner from . import res_company +from . import res_config_settings from . import account_invoice from . import account_edi_format -from . import ir_mail_server from . import ddt from . import mail_template diff --git a/addons/l10n_it_edi/models/account_edi_format.py b/addons/l10n_it_edi/models/account_edi_format.py index 9838cffe7e7..bcfee240055 100644 --- a/addons/l10n_it_edi/models/account_edi_format.py +++ b/addons/l10n_it_edi/models/account_edi_format.py @@ -1,7 +1,9 @@ # -*- coding:utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from odoo import api, models, fields, _ +from odoo import api, models, fields, _, _lt +from odoo.exceptions import UserError +from odoo.addons.account_edi_proxy_client.models.account_edi_proxy_user import AccountEdiProxyError from odoo.addons.l10n_it_edi.tools.remove_signature import remove_signature from odoo.osv.expression import OR, AND @@ -9,6 +11,7 @@ from lxml import etree from datetime import datetime import re import logging +import base64 _logger = logging.getLogger(__name__) @@ -264,74 +267,71 @@ class AccountEdiFormat(models.Model): def _l10n_it_is_simplified_document_type(self, document_type): return self._l10n_it_document_type_mapping().get(document_type, {}).get('simplified', False) - # ------------------------------------------------------------------------- - # Export - # ------------------------------------------------------------------------- - - def _prepare_invoice_report(self, pdf_writer, edi_document): - self.ensure_one() - if self.code != 'fattura_pa': - return super()._prepare_invoice_report(pdf_writer, edi_document) - if edi_document.attachment_id: - pdf_writer.embed_odoo_attachment(edi_document.attachment_id) - - def _is_compatible_with_journal(self, journal): - # OVERRIDE - self.ensure_one() - if self.code != 'fattura_pa': - return super()._is_compatible_with_journal(journal) - return journal.type in ('sale', 'purchase') and journal.country_code == 'IT' - - def _l10n_it_edi_is_required_for_invoice(self, invoice): - """ Is the edi required for this invoice based on the method (here: PEC mail) - Deprecated: in future release PEC mail will be removed. - TO OVERRIDE - """ - return ((invoice.is_sale_document() or self._l10n_it_get_document_type(invoice)) - and invoice.country_code == 'IT' - and invoice.l10n_it_send_state not in ('sent', 'delivered', 'delivered_accepted')) - - def _is_required_for_invoice(self, invoice): - # OVERRIDE - self.ensure_one() - if self.code != 'fattura_pa': - return super()._is_required_for_invoice(invoice) - - return self._l10n_it_edi_is_required_for_invoice(invoice) - - def _post_fattura_pa(self, invoices): - # TO OVERRIDE - invoice = invoices # no batching ensure that we only have one invoice - invoice.l10n_it_send_state = 'other' - invoice._check_before_xml_exporting() - if invoice.l10n_it_einvoice_id and invoice.l10n_it_send_state not in ['invalid', 'to_send']: - return {'error': _("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() - res = invoice.invoice_generate_xml() - if invoice._is_commercial_partner_pa(): - invoice.message_post( - body=(_("Invoices for PA are not managed by Odoo, you can download the document and send it on your own.")) - ) - else: - invoice.l10n_it_send_state = 'to_send' - if 'attachment' in res: - res['success'] = True - return {invoice: res} - - def _post_invoice_edi(self, invoices, test_mode=False): - # OVERRIDE - self.ensure_one() - edi_result = super()._post_invoice_edi(invoices) - if self.code != 'fattura_pa': - return edi_result - - return self._post_fattura_pa(invoices) - # ------------------------------------------------------------------------- # Import # ------------------------------------------------------------------------- + def _cron_receive_fattura_pa(self): + ''' Check the proxy for incoming invoices. + ''' + proxy_users = self.env['account_edi_proxy_client.user'].search([('edi_format_id', '=', self.env.ref('l10n_it_edi.edi_fatturaPA').id)]) + + if proxy_users._get_demo_state() == 'demo': + return + + for proxy_user in proxy_users: + company = proxy_user.company_id + try: + res = proxy_user._make_request(proxy_user._get_server_url() + '/api/l10n_it_edi/1/in/RicezioneInvoice', + params={'recipient_codice_fiscale': company.l10n_it_codice_fiscale}) + except AccountEdiProxyError as e: + _logger.error('Error while receiving file from SdiCoop: %s', e) + + proxy_acks = [] + for id_transaction, fattura in res.items(): + if self.env['ir.attachment'].search([('name', '=', fattura['filename']), ('res_model', '=', 'account.move')], limit=1): + # name should be unique, the invoice already exists + _logger.info('E-invoice already exists: %s', fattura['filename']) + proxy_acks.append(id_transaction) + continue + + file = proxy_user._decrypt_data(fattura['file'], fattura['key']) + + try: + tree = etree.fromstring(file) + except Exception: + # should not happen as the file has been checked by SdiCoop + _logger.info('Received file badly formatted, skipping: \n %s', file) + continue + invoice = self.env['account.move'].with_company(company).create({'move_type': 'in_invoice'}) + attachment = self.env['ir.attachment'].create({ + 'name': fattura['filename'], + 'raw': file, + 'type': 'binary', + 'res_model': 'account.move', + 'res_id': invoice.id + }) + if not self.env.context.get('test_skip_commit'): + self.env.cr.commit() # In case something fails after, we still have the attachment + # So that we don't delete the attachment when deleting the invoice + attachment.res_id = False + attachment.res_model = False + invoice.unlink() + invoice = self.env.ref('l10n_it_edi.edi_fatturaPA')._create_invoice_from_xml_tree(fattura['filename'], tree) + attachment.write({'res_model': 'account.move', + 'res_id': invoice.id}) + proxy_acks.append(id_transaction) + if not self.env.context.get('test_skip_commit'): + self.env.cr.commit() + + + if proxy_acks: + try: + proxy_user._make_request(proxy_user._get_server_url() + '/api/l10n_it_edi/1/ack', + params={'transaction_ids': proxy_acks}) + except AccountEdiProxyError as e: + _logger.error('Error while receiving file from SdiCoop: %s', e) + def _check_filename_is_fattura_pa(self, filename): return re.search("([A-Z]{2}[A-Za-z0-9]{2,28}_[A-Za-z0-9]{0,5}.(xml.p7m|xml))", filename) @@ -747,7 +747,6 @@ class AccountEdiFormat(models.Model): invoice_line_global_discount.price_unit = general_discount new_invoice = invoice_form - new_invoice.l10n_it_send_state = "other" elements = body_tree.xpath('.//Allegati') if elements: @@ -775,3 +774,315 @@ class AccountEdiFormat(models.Model): invoices += new_invoice return invoices + + # ------------------------------------------------------------------------- + # Export + # ------------------------------------------------------------------------- + + def _prepare_invoice_report(self, pdf_writer, edi_document): + self.ensure_one() + if self.code != 'fattura_pa': + return super()._prepare_invoice_report(pdf_writer, edi_document) + if edi_document.attachment_id: + pdf_writer.embed_odoo_attachment(edi_document.attachment_id) + + def _is_compatible_with_journal(self, journal): + # OVERRIDE + self.ensure_one() + if self.code != 'fattura_pa': + return super()._is_compatible_with_journal(journal) + return journal.type in ('sale', 'purchase') and journal.country_code == 'IT' + + def _is_required_for_invoice(self, invoice): + # OVERRIDE + self.ensure_one() + if self.code != 'fattura_pa': + return super()._is_required_for_invoice(invoice) + + is_self_invoice = self._l10n_it_edi_is_self_invoice(invoice) + return ( + (invoice.is_sale_document() or (is_self_invoice and invoice.is_purchase_document())) + and invoice.country_code == 'IT' + ) + + def _export_as_xml(self, invoice): + ''' Create the xml file content. + :return: The XML content as str. + ''' + template_values = invoice._prepare_fatturapa_export_values() + if not self._l10n_it_is_simplified_document_type(template_values['document_type']): + content = self.env['ir.qweb']._render('l10n_it_edi.account_invoice_it_FatturaPA_export', template_values) + else: + content = self.env['ir.qweb']._render('l10n_it_edi.account_invoice_it_simplified_FatturaPA_export', template_values) + invoice.message_post(body=_( + "A simplified invoice was created instead of an ordinary one. This is because the invoice \ + is a domestic invoice with a total amount of less than or equal to 400€ and the customer's address is incomplete." + )) + return content + + def _get_invoice_edi_content(self, move): + #OVERRIDE + if self.code != 'fattura_pa': + return super()._get_invoice_edi_content(move) + return self._export_as_xml(move) + + def _check_move_configuration(self, move): + # OVERRIDE + res = super()._check_move_configuration(move) + if self.code != 'fattura_pa': + return res + + res.extend(self._l10n_it_edi_check_invoice_configuration(move)) + + if not self._get_proxy_user(move.company_id): + res.append(_("You must accept the terms and conditions in the settings to use FatturaPA.")) + + return res + + def _needs_web_services(self): + self.ensure_one() + return self.code == 'fattura_pa' or super()._needs_web_services() + + def _support_batching(self, move=None, state=None, company=None): + # OVERRIDE + if self.code == 'fattura_pa': + return state == 'to_send' and move.is_invoice() + + return super()._support_batching(move=move, state=state, company=company) + + def _get_batch_key(self, move, state): + # OVERRIDE + if self.code != 'fattura_pa': + return super()._get_batch_key(move, state) + + return move.move_type, bool(move.l10n_it_edi_transaction) + + def _l10n_it_post_invoices_step_1(self, invoices): + ''' Send the invoices to the proxy. + ''' + to_return = {} + + to_send = {} + for invoice in invoices: + xml = "" + str(self._export_as_xml(invoice)) + filename = self._l10n_it_edi_generate_electronic_invoice_filename(invoice) + attachment = self.env['ir.attachment'].create({ + 'name': filename, + 'res_id': invoice.id, + 'res_model': invoice._name, + 'raw': xml.encode(), + 'description': _('Italian invoice: %s', invoice.move_type), + 'type': 'binary', + }) + invoice.l10n_it_edi_attachment_id = attachment + + if invoice._is_commercial_partner_pa(): + invoice.message_post( + body=(_("Invoices for PA are not managed by Odoo, you can download the document and send it on your own.")) + ) + to_return[invoice] = {'attachment': attachment, 'success': True} + else: + to_send[filename] = { + 'invoice': invoice, + 'data': {'filename': filename, 'xml': base64.b64encode(xml.encode()).decode()}} + + company = invoices.company_id + proxy_user = self._get_proxy_user(company) + if not proxy_user: # proxy user should exist, because there is a check in _check_move_configuration + return {invoice: { + 'error': _("You must accept the terms and conditions in the settings to use FatturaPA."), + 'blocking_level': 'error'} for invoice in invoices} + + responses = {} + if proxy_user._get_demo_state() == 'demo': + responses = {i['data']['filename']: {'id_transaction': 'demo'} for i in to_send.values()} + else: + try: + responses = self._l10n_it_edi_upload([i['data'] for i in to_send.values()], proxy_user) + except AccountEdiProxyError as e: + return {invoice: {'error': e.message, 'blocking_level': 'error'} for invoice in invoices} + + for filename, response in responses.items(): + invoice = to_send[filename]['invoice'] + to_return[invoice] = response + if 'id_transaction' in response: + invoice.l10n_it_edi_transaction = response['id_transaction'] + to_return[invoice].update({ + 'error': _('The invoice was sent to FatturaPA, but we are still awaiting a response. Click the link above to check for an update.'), + 'blocking_level': 'info', + }) + return to_return + + def _l10n_it_post_invoices_step_2(self, invoices): + ''' Check if the sent invoices have been processed by FatturaPA. + ''' + to_check = {i.l10n_it_edi_transaction: i for i in invoices} + to_return = {} + company = invoices.company_id + proxy_user = self._get_proxy_user(company) + if not proxy_user: # proxy user should exist, because there is a check in _check_move_configuration + return {invoice: { + 'error': _("You must accept the terms and conditions in the settings to use FatturaPA."), + 'blocking_level': 'error'} for invoice in invoices} + + if proxy_user._get_demo_state() == 'demo': + # simulate success and bypass ack + return {invoice: {'attachment': invoice.l10n_it_edi_attachment_id} for invoice in invoices} + else: + try: + responses = proxy_user._make_request(proxy_user._get_server_url() + '/api/l10n_it_edi/1/in/TrasmissioneFatture', + params={'ids_transaction': list(to_check.keys())}) + except AccountEdiProxyError as e: + return {invoice: {'error': e.message, 'blocking_level': 'error'} for invoice in invoices} + + proxy_acks = [] + for id_transaction, response in responses.items(): + invoice = to_check[id_transaction] + if 'error' in response: + to_return[invoice] = response + continue + + state = response['state'] + if state == 'awaiting_outcome': + to_return[invoice] = { + 'error': _('The invoice was sent to FatturaPA, but we are still awaiting a response. Click the link above to check for an update.'), + 'blocking_level': 'info', + } + continue + elif state == 'not_found': + # Invoice does not exist on proxy. Either it does not belong to this proxy_user or it was not created correctly when + # it was sent to the proxy. + to_return[invoice] = {'error': _('You are not allowed to check the status of this invoice.'), 'blocking_level': 'error'} + continue + + if not response.get('file'): # It means there is no status update, so we can skip it + document = invoice.edi_document_ids.filtered(lambda d: d.edi_format_id.code == 'fattura_pa') + to_return[invoice] = {'error': document.error, 'blocking_level': document.blocking_level} + continue + xml = proxy_user._decrypt_data(response['file'], response['key']) + response_tree = etree.fromstring(xml) + if state == 'ricevutaConsegna': + if invoice._is_commercial_partner_pa(): + to_return[invoice] = {'error': _('The invoice has been succesfully transmitted. The addressee has 15 days to accept or reject it.')} + else: + to_return[invoice] = {'attachment': invoice.l10n_it_edi_attachment_id, 'success': True} + elif state == 'notificaScarto': + elements = response_tree.xpath('//Errore') + error_codes = [element.find('Codice').text for element in elements] + errors = [element.find('Descrizione').text for element in elements] + # Duplicated invoice + if '00404' in error_codes: + idx = error_codes.index('00404') + invoice.message_post(body=_( + 'This invoice number had already been submitted to the SdI, so it is' + ' set as Sent. Please verify that the system is correctly configured,' + ' because the correct flow does not need to send the same invoice' + ' twice for any reason.\n' + ' Original message from the SDI: %s', errors[idx])) + to_return[invoice] = {'attachment': invoice.l10n_it_edi_attachment_id, 'success': True} + else: + # Add helpful text if duplicated filename error + if '00002' in error_codes: + idx = error_codes.index('00002') + errors[idx] = _( + 'The filename is duplicated. Try again (or adjust the FatturaPA Filename sequence).' + ' Original message from the SDI: %s', [errors[idx]] + ) + to_return[invoice] = {'error': self._format_error_message(_('The invoice has been refused by the Exchange System'), errors), 'blocking_level': 'error'} + invoice.l10n_it_edi_transaction = False + elif state == 'notificaMancataConsegna': + if invoice._is_commercial_partner_pa(): + to_return[invoice] = {'error': _( + 'The invoice has been issued, but the delivery to the Public Administration' + ' has failed. The Exchange System will contact them to report the problem' + ' and request that they provide a solution.' + ' During the following 10 days, the Exchange System will try to forward the' + ' FatturaPA file to the Public Administration in question again.' + ' Should this also fail, the System will notify Odoo of the failed delivery,' + ' and you will be required to send the invoice to the Administration' + ' through another channel, outside of the Exchange System.')} + else: + to_return[invoice] = {'success': True, 'attachment': invoice.l10n_it_edi_attachment_id} + invoice._message_log(body=_( + 'The invoice has been issued, but the delivery to the Addressee has' + ' failed. You will be required to send a courtesy copy of the invoice' + ' to your customer through another channel, outside of the Exchange' + ' System, and promptly notify him that the original is deposited' + ' in his personal area on the portal "Invoices and Fees" of the' + ' Revenue Agency.')) + elif state == 'notificaEsito': + outcome = response_tree.find('Esito').text + if outcome == 'EC01': + to_return[invoice] = {'attachment': invoice.l10n_it_edi_attachment_id, 'success': True} + else: # ECO2 + to_return[invoice] = {'error': _('The invoice was refused by the addressee.'), 'blocking_level': 'error'} + elif state == 'NotificaDecorrenzaTermini': + to_return[invoice] = {'attachment': invoice.l10n_it_edi_attachment_id, 'success': True} + proxy_acks.append(id_transaction) + + if proxy_acks: + try: + proxy_user._make_request(proxy_user._get_server_url() + '/api/l10n_it_edi/1/ack', + params={'transaction_ids': proxy_acks}) + except AccountEdiProxyError as e: + # Will be ignored and acked again next time. + _logger.error('Error while acking file to SdiCoop: %s', e) + + return to_return + + def _post_fattura_pa(self, invoices): + # OVERRIDE + if not invoices[0].l10n_it_edi_transaction: + return self._l10n_it_post_invoices_step_1(invoices) + else: + return self._l10n_it_post_invoices_step_2(invoices) + + def _post_invoice_edi(self, invoices, test_mode=False): + # OVERRIDE + self.ensure_one() + edi_result = super()._post_invoice_edi(invoices) + if self.code != 'fattura_pa': + return edi_result + + return self._post_fattura_pa(invoices) + + # ------------------------------------------------------------------------- + # Proxy methods + # ------------------------------------------------------------------------- + + def _get_proxy_identification(self, company): + if self.code != 'fattura_pa': + return super()._get_proxy_identification() + + if not company.l10n_it_codice_fiscale: + raise UserError(_('Please fill your codice fiscale to be able to receive invoices from FatturaPA')) + + return self.env['res.partner']._l10n_it_normalize_codice_fiscale(company.l10n_it_codice_fiscale) + + def _l10n_it_edi_upload(self, files, proxy_user): + '''Upload files to fatturapa. + + :param files: A list of dictionary {filename, base64_xml}. + :returns: A dictionary. + * message: Message from fatturapa. + * transactionId: The fatturapa ID of this request. + * error: An eventual error. + * error_level: Info, warning, error. + ''' + ERRORS = { + 'EI01': {'error': _lt('Attached file is empty'), 'blocking_level': 'error'}, + 'EI02': {'error': _lt('Service momentarily unavailable'), 'blocking_level': 'warning'}, + 'EI03': {'error': _lt('Unauthorized user'), 'blocking_level': 'error'}, + } + + if not files: + return {} + + result = proxy_user._make_request(proxy_user._get_server_url() + '/api/l10n_it_edi/1/out/SdiRiceviFile', params={'files': files}) + + # Translate the errors. + for filename in result.keys(): + if 'error' in result[filename]: + result[filename] = ERRORS.get(result[filename]['error'], {'error': result[filename]['error'], 'blocking_level': 'error'}) + + return result diff --git a/addons/l10n_it_edi/models/account_invoice.py b/addons/l10n_it_edi/models/account_invoice.py index b8e37467fb8..90509276bcd 100644 --- a/addons/l10n_it_edi/models/account_invoice.py +++ b/addons/l10n_it_edi/models/account_invoice.py @@ -10,7 +10,9 @@ from datetime import datetime from odoo import api, fields, models, _ from odoo.tools import float_repr, float_compare from odoo.exceptions import UserError, ValidationError -from odoo.addons.base.models.ir_mail_server import MailDeliveryException + + +_logger = logging.getLogger(__name__) DEFAULT_FACTUR_ITALIAN_DATE_FORMAT = '%Y-%m-%d' @@ -18,19 +20,8 @@ DEFAULT_FACTUR_ITALIAN_DATE_FORMAT = '%Y-%m-%d' class AccountMove(models.Model): _inherit = 'account.move' - 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, string="FatturaPA Send State") + l10n_it_edi_transaction = fields.Char(copy=False, string="FatturaPA Transaction") + l10n_it_edi_attachment_id = fields.Many2one('ir.attachment', copy=False, string="FatturaPA Attachment") l10n_it_stamp_duty = fields.Float(default=0, string="Dati Bollo", readonly=True, states={'draft': [('readonly', False)]}) @@ -48,12 +39,6 @@ class AccountMove(models.Model): invoice.l10n_it_einvoice_id = einvoice.attachment_id invoice.l10n_it_einvoice_name = einvoice.attachment_id.name - def _check_before_xml_exporting(self): - # DEPRECATED use AccountEdiFormat._l10n_it_edi_check_invoice_configuration instead - errors = self.env['account.edi.format']._l10n_it_edi_check_invoice_configuration(self) - if errors: - raise UserError(self.env['account.edi.format']._format_error_message(_("Invalid configuration:"), errors)) - def invoice_generate_xml(self): self.ensure_one() report_name = self.env['account.edi.format']._l10n_it_edi_generate_electronic_invoice_filename(self) @@ -298,73 +283,11 @@ class AccountMove(models.Model): } return template_values - def _export_as_xml(self): - '''DEPRECATED : this will be moved to AccountEdiFormat in a future version. - Create the xml file content. - :return: The XML content as str. - ''' - template_values = self._prepare_fatturapa_export_values() - if not self.env['account.edi.format']._l10n_it_is_simplified_document_type(template_values['document_type']): - content = self.env['ir.qweb']._render('l10n_it_edi.account_invoice_it_FatturaPA_export', template_values) - else: - content = self.env['ir.qweb']._render('l10n_it_edi.account_invoice_it_simplified_FatturaPA_export', template_values) - self.message_post(body=_("A simplified invoice was created instead of an ordinary one. This is because the invoice \ - is a domestic invoice with a total amount of less than or equal to 400€ and the customer's address is incomplete.")) - return content - def _post(self, soft=True): # OVERRIDE posted = super()._post(soft=soft) - - for move in posted.filtered(lambda m: m.l10n_it_send_state == 'to_send' and m.move_type in ['out_invoice', 'out_refund'] and m.company_id.country_id.code == 'IT'): - move.send_pec_mail() - return posted - 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_mail_pec_server_id.active - 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_name), - 'body': _('Sending file: %s to ES: %s') % (self.l10n_it_einvoice_name, self.env.company.l10n_it_address_recipient_fatturapa), - 'author_id': self.env.user.partner_id.id, - 'email_from': self.env.company.l10n_it_address_send_fatturapa, - 'reply_to': self.env.company.l10n_it_address_send_fatturapa, - 'mail_server_id': self.env.company.l10n_it_mail_pec_server_id.id, - 'attachment_ids': [(6, 0, self.l10n_it_einvoice_id.ids)], - }) - - mail_fattura = self.env['mail.mail'].sudo().with_context(wo_bounce_return_path=True).create({ - 'mail_message_id': message.id, - 'email_to': self.env.company.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 _compose_info_message(self, tree, element_tags): output_str = "" elements = tree.xpath(element_tags) diff --git a/addons/l10n_it_edi/models/ir_mail_server.py b/addons/l10n_it_edi/models/ir_mail_server.py deleted file mode 100644 index aa2841fdf7a..00000000000 --- a/addons/l10n_it_edi/models/ir_mail_server.py +++ /dev/null @@ -1,449 +0,0 @@ -# -*- 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 email.policy -import dateutil -import pytz - -from lxml import etree -from datetime import datetime -from xmlrpc import client as xmlrpclib - -from odoo import api, fields, models, tools, _ -from odoo.exceptions import ValidationError, UserError -from odoo.addons.l10n_it_edi.tools.remove_signature import remove_signature - - -_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 IT', default=1) - - def _search_edi_invoice(self, att_name, send_state=False): - """ Search sent l10n_it_edi fatturaPA invoices """ - - conditions = [ - ('move_id', "!=", False), - ('edi_format_id.code', '=', 'fattura_pa'), - ('attachment_id.name', '=', att_name), - ] - if send_state: - conditions.append(('move_id.l10n_it_send_state', '=', send_state)) - - return self.env['account.edi.document'].search(conditions, limit=1).move_id - - @api.constrains('l10n_it_is_pec', 'server_type') - def _check_pec(self): - for record in self: - if record.l10n_it_is_pec and record.server_type != 'imap': - raise ValidationError(_("PEC mail server must be of type IMAP.")) - - 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.server_type, server.name) - - count, failed = 0, 0 - imap_server = None - try: - imap_server = server.connect() - imap_server.select() - - # Only download new emails - email_filter = ['(UID %s:*)' % (server.l10n_it_last_uid)] - - # The l10n_it_edi.fatturapa_bypass_incoming_address_filter prevents the sender address check on incoming email. - bypass_incoming_address_filter = self.env['ir.config_parameter'].get_param('l10n_it_edi.bypass_incoming_address_filter', False) - if not bypass_incoming_address_filter: - email_filter.append('(FROM "@pec.fatturapa.it")') - - data = imap_server.uid('search', None, *email_filter)[1] - - 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, str): - message = message.encode('utf-8') - msg_txt = email.message_from_bytes(message, policy=email.policy.SMTP) - - 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.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.server_type, server.name, (count - failed), failed) - except Exception: - _logger.info("General failure when trying to fetch mail from %s server %s.", server.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): - parsed_values = self.env['mail.thread']._message_parse_extract_payload(msg_txt) - body, attachments = parsed_values['body'], parsed_values['attachments'] - from_address = 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) - else: - att_filename = attachment.fname - match = re.search("([A-Z]{2}[A-Za-z0-9]{2,28}_[A-Za-z0-9]{0,5}.(xml.p7m|xml))", att_filename) - # If match, we have an invoice. - if match: - # If it's signed, the content has a bytes type and we just remove the signature's envelope - if match.groups()[1] == 'xml.p7m': - att_content_data = remove_signature(attachment.content) - # If the envelope cannot be removed, the remove_signature returns None, so we skip - if not att_content_data: - _logger.warning("E-invoice couldn't be read: %s", att_filename) - continue - att_filename = att_filename.replace('.xml.p7m', '.xml') - else: - # Otherwise, it should be an utf-8 encoded XML string - att_content_data = attachment.content.encode() - self._create_invoice_from_mail(att_content_data, att_filename, 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_data, att_name, from_address): - """ Creates an invoice from the content of an email present in ir.attachments - - :param att_content_data: The 'utf-8' encoded bytes string representing the content of the attachment. - :param att_name: The attachment's file name. - :param from_address: The sender address of the email. - """ - - invoices = self.env['account.move'] - - # Check if we already imported the email as an attachment - existing = self.env['ir.attachment'].search([('name', '=', att_name), ('res_model', '=', 'account.move')]) - if existing: - _logger.info('E-invoice already exist: %s', att_name) - return invoices - - # Create the new attachment for the file - attachment = self.env['ir.attachment'].create({ - 'name': att_name, - 'raw': att_content_data, - 'res_model': 'account.move', - 'type': 'binary'}) - - # Decode the file. - try: - tree = etree.fromstring(att_content_data) - except Exception: - _logger.info('The xml file is badly formatted: %s', att_name) - return invoices - - invoices = self.env.ref('l10n_it_edi.edi_fatturaPA')._create_invoice_from_xml_tree(att_name, tree) - if not invoices: - _logger.info('E-invoice not found in file: %s', att_name) - return invoices - invoices.l10n_it_send_state = "new" - invoices.invoice_source_email = from_address - for invoice in invoices: - invoice.with_context(no_new_invoice=True, default_res_id=invoice.id) \ - .message_post(body=(_("Original E-invoice XML file")), attachment_ids=[attachment.id]) - - self._cr.commit() - - _logger.info('New E-invoices (%s), ids: %s', att_name, [x.id for x in invoices]) - return invoices - - 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(): - existing = self.env['ir.attachment'].search([('name', '=', att_name), ('res_model', '=', 'account.move')]) - if existing: - # 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') - if elements and elements[0].text: - filename = elements[0].text - else: - return - - related_invoice = self._search_edi_invoice(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.
%s") % (info)) - ) - - def _message_receipt_invoice(self, receipt_type, attachment): - - try: - tree = etree.fromstring(attachment.content.encode()) - except: - _logger.info('Error in decoding new receipt file: %s', attachment.fname) - return {} - - elements = tree.xpath('//NomeFile') - 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._search_edi_invoice(filename, '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:
%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._search_edi_invoice(filename, '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 :
%s") % (error)) - ) - related_invoice.activity_schedule( - 'mail.mail_activity_data_todo', - summary='Rejection notice', - user_id=related_invoice.invoice_user_id.id if related_invoice.invoice_user_id else self.env.user.id) - - 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._search_edi_invoice(filename, '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 information:
%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._search_edi_invoice(filename, 'delivered') - if not related_invoice: - _logger.info('Error: invoice not found for receipt file: %s', attachment.fname) - return - elements = tree.xpath('//Esito') - 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
%s") % (related_invoice.l10n_it_send_state, info)) - ) - if related_invoice.l10n_it_send_state == 'delivered_refused': - related_invoice.activity_schedule( - 'mail.mail_activity_todo', - user_id=related_invoice.invoice_user_id.id if related_invoice.invoice_user_id else self.env.user.id, - summary='Outcome notice: Refused') - - # 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._search_edi_invoice(filename, '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
%s") % (filename, info)) - ) - - def _return_multi_line_xml(self, tree, element_tags): - output_str = "" - - def _return_error_xml(self, tree): - output_str = "" - -class IrMailServer(models.Model): - _name = "ir.mail_server" - _inherit = "ir.mail_server" - - res_company_l10n_it_mail_pec_server_ids = fields.One2many( - comodel_name='res.company', - inverse_name='l10n_it_mail_pec_server_id', - string='PEC-mail server active usage', - readonly=True) - - def _get_test_email_addresses(self): - self.ensure_one() - - company = self.env["res.company"].search([("l10n_it_mail_pec_server_id", "=", self.id)], limit=1) - if not company: - # it's not a PEC server - return super()._get_test_email_addresses() - email_from = self.smtp_user - if not email_from: - raise UserError(_('Please configure Username for this Server PEC')) - email_to = company.l10n_it_address_recipient_fatturapa - if not email_to: - raise UserError(_('Please configure Government PEC-mail in company settings')) - return email_from, email_to - - def _active_usages_compute(self): - usages_super = super()._active_usages_compute() - for record in self.filtered('res_company_l10n_it_mail_pec_server_ids'): - usages_super.setdefault(record.id, []).extend( - map(lambda c: _('%s (Company E-Invoicing PEC-mail Server)', c.display_name), - record.res_company_l10n_it_mail_pec_server_ids) - ) - return usages_super - - def build_email(self, email_from, email_to, subject, body, email_cc=None, email_bcc=None, reply_to=False, - attachments=None, message_id=None, references=None, object_id=False, subtype='plain', headers=None, - body_alternative=None, subtype_alternative='plain'): - - if self.env.context.get('wo_bounce_return_path') and headers: - headers['Return-Path'] = email_from - return super(IrMailServer, self).build_email(email_from, email_to, subject, body, email_cc=email_cc, email_bcc=email_bcc, reply_to=reply_to, - attachments=attachments, message_id=message_id, references=references, object_id=object_id, subtype=subtype, headers=headers, - body_alternative=body_alternative, subtype_alternative=subtype_alternative) diff --git a/addons/l10n_it_edi/models/res_company.py b/addons/l10n_it_edi/models/res_company.py index 71c651f84e9..6eb47ecc07c 100644 --- a/addons/l10n_it_edi/models/res_company.py +++ b/addons/l10n_it_edi/models/res_company.py @@ -34,15 +34,6 @@ class ResCompany(models.Model): 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\ diff --git a/addons/l10n_it_edi_sdicoop/models/res_config_settings.py b/addons/l10n_it_edi/models/res_config_settings.py similarity index 98% rename from addons/l10n_it_edi_sdicoop/models/res_config_settings.py rename to addons/l10n_it_edi/models/res_config_settings.py index 299ead5b441..adc5c66aefb 100644 --- a/addons/l10n_it_edi_sdicoop/models/res_config_settings.py +++ b/addons/l10n_it_edi/models/res_config_settings.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo import api, models, fields, _ from odoo.exceptions import UserError diff --git a/addons/l10n_it_edi/tests/__init__.py b/addons/l10n_it_edi/tests/__init__.py index c75d90ba899..3b92ef2c10f 100644 --- a/addons/l10n_it_edi/tests/__init__.py +++ b/addons/l10n_it_edi/tests/__init__.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. - -from . import test_ir_mail_server -from . import test_res_partner +from . import common +from . import test_edi_export +from . import test_edi_import +from . import test_edi_reverse_charge diff --git a/addons/l10n_it_edi/tests/common.py b/addons/l10n_it_edi/tests/common.py new file mode 100644 index 00000000000..2ab185b271d --- /dev/null +++ b/addons/l10n_it_edi/tests/common.py @@ -0,0 +1,64 @@ +from unittest.mock import patch, MagicMock + +from odoo import tools +from odoo.tests import tagged +from odoo.addons.account_edi.tests.common import AccountEdiTestCommon +from odoo.addons.account_edi_proxy_client.models.account_edi_proxy_user import AccountEdiProxyClientUser + +def patch_proxy_user(func): + @patch.object(AccountEdiProxyClientUser, '_make_request', MagicMock(spec=AccountEdiProxyClientUser._make_request)) + @patch.object(AccountEdiProxyClientUser, '_decrypt_data', MagicMock(spec=AccountEdiProxyClientUser._decrypt_data)) + @patch.object(AccountEdiProxyClientUser, '_get_demo_state', MagicMock(spec=AccountEdiProxyClientUser._get_demo_state)) + def patched(self, *args, **kwargs): + return func(self, *args, **kwargs) + return patched + +@tagged('post_install_l10n', 'post_install', '-at_install') +class TestItEdi(AccountEdiTestCommon): + + @classmethod + def setUpClass(cls, chart_template_ref='l10n_it.l10n_it_chart_template_generic', edi_format_ref="l10n_it_edi.edi_fatturaPA"): + super().setUpClass(chart_template_ref=chart_template_ref, edi_format_ref=edi_format_ref) + + # Company data ------ + cls.company = cls.company_data_2['company'] + cls.company.l10n_it_codice_fiscale = '01234560157' + cls.company.partner_id.l10n_it_pa_index = "0803HR0" + cls.company.vat = 'IT01234560157' + + cls.test_bank = cls.env['res.partner.bank'].with_company(cls.company).create({ + 'partner_id': cls.company.partner_id.id, + 'acc_number': 'IT1212341234123412341234123', + 'bank_name': 'BIG BANK', + 'bank_bic': 'BIGGBANQ', + }) + + cls.company.l10n_it_tax_system = "RF01" + cls.company.street = "1234 Test Street" + cls.company.zip = "12345" + cls.company.city = "Prova" + cls.company.country_id = cls.env.ref('base.it') + + # We create this because we are unable to post without a proxy user existing + cls.proxy_user = cls.env['account_edi_proxy_client.user'].create({ + 'id_client': 'l10n_it_edi_test', + 'company_id': cls.company.id, + 'edi_format_id': cls.edi_format.id, + 'edi_identification': 'l10n_it_edi_test', + 'private_key': 'l10n_it_edi_test', + }) + + cls.standard_line = { + 'name': 'standard_line', + 'quantity': 1, + 'price_unit': 800.40, + 'tax_ids': [(6, 0, [cls.company.account_sale_tax_id.id])] + } + + + @classmethod + def _get_test_file_content(cls, filename): + """ Get the content of a test file inside this module """ + path = 'l10n_it_edi/tests/expected_xmls/' + filename + with tools.file_open(path, mode='rb') as test_file: + return test_file.read() diff --git a/addons/l10n_it_edi_sdicoop/tests/expected_xmls/IT00470550013_basis.xml b/addons/l10n_it_edi/tests/expected_xmls/IT00470550013_basis.xml similarity index 100% rename from addons/l10n_it_edi_sdicoop/tests/expected_xmls/IT00470550013_basis.xml rename to addons/l10n_it_edi/tests/expected_xmls/IT00470550013_basis.xml diff --git a/addons/l10n_it_edi_sdicoop/tests/expected_xmls/IT00470550013_simpl.xml b/addons/l10n_it_edi/tests/expected_xmls/IT00470550013_simpl.xml similarity index 100% rename from addons/l10n_it_edi_sdicoop/tests/expected_xmls/IT00470550013_simpl.xml rename to addons/l10n_it_edi/tests/expected_xmls/IT00470550013_simpl.xml diff --git a/addons/l10n_it_edi_sdicoop/tests/expected_xmls/reverse_charge_bill.xml b/addons/l10n_it_edi/tests/expected_xmls/reverse_charge_bill.xml similarity index 100% rename from addons/l10n_it_edi_sdicoop/tests/expected_xmls/reverse_charge_bill.xml rename to addons/l10n_it_edi/tests/expected_xmls/reverse_charge_bill.xml diff --git a/addons/l10n_it_edi_sdicoop/tests/expected_xmls/reverse_charge_invoice.xml b/addons/l10n_it_edi/tests/expected_xmls/reverse_charge_invoice.xml similarity index 100% rename from addons/l10n_it_edi_sdicoop/tests/expected_xmls/reverse_charge_invoice.xml rename to addons/l10n_it_edi/tests/expected_xmls/reverse_charge_invoice.xml diff --git a/addons/l10n_it_edi_sdicoop/tests/test_edi_xml.py b/addons/l10n_it_edi/tests/test_edi_export.py similarity index 92% rename from addons/l10n_it_edi_sdicoop/tests/test_edi_xml.py rename to addons/l10n_it_edi/tests/test_edi_export.py index 60fa39355f1..31049d03044 100644 --- a/addons/l10n_it_edi_sdicoop/tests/test_edi_xml.py +++ b/addons/l10n_it_edi/tests/test_edi_export.py @@ -8,35 +8,19 @@ from freezegun import freeze_time from odoo import tools from odoo.tests import tagged -from odoo.addons.account_edi.tests.common import AccountEdiTestCommon +from odoo.addons.l10n_it_edi.tests.common import TestItEdi from odoo.exceptions import UserError _logger = logging.getLogger(__name__) -class TestItEdiCommon(AccountEdiTestCommon): +@tagged('post_install_l10n', 'post_install', '-at_install') +class TestItEdiExport(TestItEdi): @classmethod def setUpClass(cls): super().setUpClass(chart_template_ref='l10n_it.l10n_it_chart_template_generic', edi_format_ref='l10n_it_edi.edi_fatturaPA') - # Use the company_data_2 to test that the e-invoice is imported for the right company - cls.company = cls.company_data_2['company'] - - cls.company.l10n_it_codice_fiscale = '01234560157' - cls.company.vat = 'IT01234560157' - cls.test_bank = cls.env['res.partner.bank'].with_company(cls.company).create({ - 'partner_id': cls.company.partner_id.id, - 'acc_number': 'IT1212341234123412341234123', - 'bank_name': 'BIG BANK', - 'bank_bic': 'BIGGBANQ', - }) - cls.company.l10n_it_tax_system = "RF01" - cls.company.street = "1234 Test Street" - cls.company.zip = "12345" - cls.company.city = "Prova" - cls.company.country_id = cls.env.ref('base.it') - cls.price_included_tax = cls.env['account.tax'].create({ 'name': '22% price included tax', 'amount': 22.0, @@ -119,13 +103,6 @@ class TestItEdiCommon(AccountEdiTestCommon): 'is_company': True, }) - cls.standard_line = { - 'name': 'standard_line', - 'quantity': 1, - 'price_unit': 800.40, - 'tax_ids': [(6, 0, [cls.company.account_sale_tax_id.id])] - } - cls.standard_line_below_400 = { 'name': 'cheap_line', 'quantity': 1, @@ -290,15 +267,6 @@ class TestItEdiCommon(AccountEdiTestCommon): ], }) - # We create this because we are unable to post without a proxy user existing - cls.proxy_user = cls.env['account_edi_proxy_client.user'].create({ - 'id_client': 'l10n_it_edi_sdicoop_test', - 'company_id': cls.company.id, - 'edi_format_id': cls.edi_format.id, - 'edi_identification': 'l10n_it_edi_sdicoop_test', - 'private_key': 'l10n_it_edi_sdicoop_test', - }) - cls.zero_tax_invoice = cls.env['account.move'].with_company(cls.company).create({ 'move_type': 'out_invoice', 'invoice_date': datetime.date(2022, 3, 24), @@ -335,13 +303,10 @@ class TestItEdiCommon(AccountEdiTestCommon): @classmethod def _get_test_file_content(cls, filename): """ Get the content of a test file inside this module """ - path = 'l10n_it_edi_sdicoop/tests/expected_xmls/' + filename + path = 'l10n_it_edi/tests/expected_xmls/' + filename with tools.file_open(path, mode='rb') as test_file: return test_file.read() - -@tagged('post_install_l10n', 'post_install', '-at_install') -class TestItEdi(TestItEdiCommon): def test_price_included_taxes(self): """ When the tax is price included, there should be a rounding value added to the xml, if the sum(subtotals) * tax_rate is not equal to taxable base * tax rate (there is a constraint in the edi where taxable base * tax rate = tax amount, but also @@ -415,7 +380,7 @@ class TestItEdi(TestItEdiCommon): 2577.29 ''') - invoice_etree = etree.fromstring(self.price_included_invoice._export_as_xml()) + invoice_etree = etree.fromstring(self.edi_format._export_as_xml(self.price_included_invoice)) # Remove the attachment and its details invoice_etree = self.with_applied_xpath(invoice_etree, "") self.assertXmlTreeEqual(invoice_etree, expected_etree) @@ -424,7 +389,7 @@ class TestItEdi(TestItEdiCommon): # The EDI can account for discounts, but a line with, for example, a 100% discount should still have # a corresponding tax with a base amount of 0 - invoice_etree = etree.fromstring(self.partial_discount_invoice._export_as_xml()) + invoice_etree = etree.fromstring(self.edi_format._export_as_xml(self.partial_discount_invoice)) expected_etree = self.with_applied_xpath( etree.fromstring(self.edi_basis_xml), ''' @@ -481,7 +446,7 @@ class TestItEdi(TestItEdiCommon): self.assertXmlTreeEqual(invoice_etree, expected_etree) def test_fully_discounted_inovice(self): - invoice_etree = etree.fromstring(self.full_discount_invoice._export_as_xml()) + invoice_etree = etree.fromstring(self.edi_format._export_as_xml(self.full_discount_invoice)) expected_etree = self.with_applied_xpath( etree.fromstring(self.edi_basis_xml), ''' @@ -518,7 +483,7 @@ class TestItEdi(TestItEdiCommon): self.assertXmlTreeEqual(invoice_etree, expected_etree) def test_non_latin_and_latin_invoice(self): - invoice_etree = etree.fromstring(self.non_latin_and_latin_invoice._export_as_xml()) + invoice_etree = etree.fromstring(self.edi_format._export_as_xml(self.non_latin_and_latin_invoice)) expected_etree = self.with_applied_xpath( etree.fromstring(self.edi_basis_xml), ''' @@ -567,7 +532,7 @@ class TestItEdi(TestItEdiCommon): self.assertXmlTreeEqual(invoice_etree, expected_etree) def test_below_400_codice_simplified_invoice(self): - invoice_etree = etree.fromstring(self.below_400_codice_simplified_invoice._export_as_xml()) + invoice_etree = etree.fromstring(self.edi_format._export_as_xml(self.below_400_codice_simplified_invoice)) expected_etree = self.with_applied_xpath( etree.fromstring(self.edi_simplified_basis_xml), ''' @@ -597,7 +562,7 @@ class TestItEdi(TestItEdiCommon): self.assertXmlTreeEqual(invoice_etree, expected_etree) def test_total_400_VAT_simplified_invoice(self): - invoice_etree = etree.fromstring(self.total_400_VAT_simplified_invoice._export_as_xml()) + invoice_etree = etree.fromstring(self.edi_format._export_as_xml(self.total_400_VAT_simplified_invoice)) expected_etree = self.with_applied_xpath( etree.fromstring(self.edi_simplified_basis_xml), ''' @@ -635,7 +600,7 @@ class TestItEdi(TestItEdiCommon): self.assertEqual(res[self.pa_partner_invoice], {'attachment': self.pa_partner_invoice.l10n_it_edi_attachment_id, 'success': True}) def test_zero_percent_taxes(self): - invoice_etree = etree.fromstring(self.zero_tax_invoice._export_as_xml()) + invoice_etree = etree.fromstring(self.edi_format._export_as_xml(self.zero_tax_invoice)) expected_etree = self.with_applied_xpath( etree.fromstring(self.edi_basis_xml), ''' diff --git a/addons/l10n_it_edi/tests/test_edi_import.py b/addons/l10n_it_edi/tests/test_edi_import.py new file mode 100644 index 00000000000..09ac8ec206f --- /dev/null +++ b/addons/l10n_it_edi/tests/test_edi_import.py @@ -0,0 +1,136 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import datetime +import logging +from freezegun import freeze_time +from lxml import etree + +from odoo import tools +from odoo.tests import tagged +from odoo.addons.l10n_it_edi.tests.common import TestItEdi, patch_proxy_user +from odoo.addons.l10n_it_edi.tools.remove_signature import remove_signature + +_logger = logging.getLogger(__name__) + +@tagged('post_install_l10n', 'post_install', '-at_install') +class TestItEdiImport(TestItEdi): + """ Main test class for the l10n_it_edi vendor bills XML import""" + + fake_test_content = """ + + + + TWICE_TEST + + + + 01234560157 + + + + + + + TD02 + + + + """ + + @classmethod + def setUpClass(cls): + super().setUpClass(chart_template_ref='l10n_it.l10n_it_chart_template_generic', + edi_format_ref='l10n_it_edi.edi_fatturaPA') + + # Build test data. + # invoice_filename1 is used for vendor bill receipts tests + # invoice_filename2 is used for vendor bill tests + cls.invoice_filename1 = 'IT01234567890_FPR01.xml' + cls.invoice_filename2 = 'IT01234567890_FPR02.xml' + cls.signed_invoice_filename = 'IT01234567890_FPR01.xml.p7m' + cls.invoice_content = cls._get_test_file_content(cls.invoice_filename1) + cls.signed_invoice_content = cls._get_test_file_content(cls.signed_invoice_filename) + cls.invoice = cls.env['account.move'].create({ + 'move_type': 'in_invoice', + 'ref': '01234567890' + }) + cls.attachment = cls.env['ir.attachment'].create({ + 'name': cls.invoice_filename1, + 'raw': cls.invoice_content, + 'res_id': cls.invoice.id, + 'res_model': 'account.move', + }) + cls.edi_document = cls.env['account.edi.document'].create({ + 'edi_format_id': cls.edi_format.id, + 'move_id': cls.invoice.id, + 'attachment_id': cls.attachment.id, + 'state': 'sent' + }) + + cls.test_invoice_xmls = {k: cls._get_test_file_content(v) for k, v in [ + ('normal_1', 'IT01234567890_FPR01.xml'), + ('signed', 'IT01234567890_FPR01.xml.p7m'), + ]} + + @classmethod + def _get_test_file_content(cls, filename): + """ Get the content of a test file inside this module """ + path = 'l10n_it_edi/tests/expected_xmls/' + filename + with tools.file_open(path, mode='rb') as test_file: + return test_file.read() + + # ----------------------------- + # + # Vendor bills + # + # ----------------------------- + + def test_receive_vendor_bill(self): + """ Test a sample e-invoice file from https://www.fatturapa.gov.it/export/documenti/fatturapa/v1.2/IT01234567890_FPR01.xml """ + content = etree.fromstring(self.invoice_content) + invoices = self.edi_format._create_invoice_from_xml_tree(self.invoice_filename2, content) + self.assertTrue(bool(invoices)) + + def test_receive_signed_vendor_bill(self): + """ Test a signed (P7M) sample e-invoice file from https://www.fatturapa.gov.it/export/documenti/fatturapa/v1.2/IT01234567890_FPR01.xml """ + with freeze_time('2020-04-06'): + content = etree.fromstring(remove_signature(self.signed_invoice_content)) + invoices = self.edi_format._create_invoice_from_xml_tree(self.signed_invoice_filename, content) + + self.assertRecordValues(invoices, [{ + 'company_id': self.company.id, + 'name': 'BILL/2014/12/0001', + 'invoice_date': datetime.date(2014, 12, 18), + 'ref': '01234567890', + }]) + + @patch_proxy_user + def test_receive_same_vendor_bill_twice(self): + """ Test that the second time we are receiving an SdiCoop invoice, the second is discarded """ + + # The make_request function is called twice when running the _cron_receive_fattura_pa + # first to the /in/RicezioneInvoice endpoint (to find new incoming invoices) + # second to the /api/l10n_it_edi/1/ack to acknowledge the invoices have been recieved + content = self.fake_test_content.encode() + fake_responses = [ + # Response of the format id_transaction: fattura dict + {'9999999999': {'filename': self.invoice_filename2, 'key': '123', 'file': content}}, + # The response from the _make_request for the ack can be None + None, + ] * 2 # Since the cron is run twice, and we want the fake results both times + self.proxy_user._make_request.side_effect = fake_responses + # When calling the decrypt function, the file we're accessing is already decrypted, just return the file + self.proxy_user._decrypt_data.side_effect = lambda file, _key: file + # In order for the cron function to progress to the point that it imports, we cannot be in demo mode + self.proxy_user._get_demo_state.return_value = 'unit_test' + + self.edi_format.with_context({'test_skip_commit': True})._cron_receive_fattura_pa() + # There should be one attachement with this filename + attachment = self.env['ir.attachment'].search([('name', '=', self.invoice_filename2)]) + self.assertEqual(len(attachment), 1) + invoice = self.env['account.move'].search([('payment_reference', '=', 'TWICE_TEST')]) + self.assertEqual(len(invoice), 1) diff --git a/addons/l10n_it_edi_sdicoop/tests/test_edi_reverse_charge_xml.py b/addons/l10n_it_edi/tests/test_edi_reverse_charge.py similarity index 96% rename from addons/l10n_it_edi_sdicoop/tests/test_edi_reverse_charge_xml.py rename to addons/l10n_it_edi/tests/test_edi_reverse_charge.py index 8f5df5ea1f2..34b81b624e9 100644 --- a/addons/l10n_it_edi_sdicoop/tests/test_edi_reverse_charge_xml.py +++ b/addons/l10n_it_edi/tests/test_edi_reverse_charge.py @@ -6,15 +6,16 @@ from lxml import etree from odoo import fields from odoo.tests import tagged -from odoo.addons.l10n_it_edi_sdicoop.tests.test_edi_xml import TestItEdiCommon +from odoo.addons.l10n_it_edi.tests.common import TestItEdi @tagged('post_install_l10n', 'post_install', '-at_install') -class TestItEdiReverseCharge(TestItEdiCommon): +class TestItEdiReverseCharge(TestItEdi): @classmethod def setUpClass(cls): - super().setUpClass() + super().setUpClass(chart_template_ref='l10n_it.l10n_it_chart_template_generic', + edi_format_ref='l10n_it_edi.edi_fatturaPA') # Helper functions ----------- def get_tag_ids(tag_codes): @@ -180,7 +181,7 @@ class TestItEdiReverseCharge(TestItEdiCommon): ) def _test_invoice_with_sample_file(self, invoice, filename, xpaths_file=None, xpaths_result=None): - result = self._cleanup_etree(invoice._export_as_xml(), xpaths_result) + result = self._cleanup_etree(self.edi_format._export_as_xml(invoice), xpaths_result) expected = self._cleanup_etree(self._get_test_file_content(filename), xpaths_file) self.assertXmlTreeEqual(result, expected) diff --git a/addons/l10n_it_edi/tests/test_ir_mail_server.py b/addons/l10n_it_edi/tests/test_ir_mail_server.py deleted file mode 100644 index f1e3bac90d7..00000000000 --- a/addons/l10n_it_edi/tests/test_ir_mail_server.py +++ /dev/null @@ -1,185 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -import datetime -import logging -from collections import namedtuple -from unittest.mock import patch -from freezegun import freeze_time - -from odoo import tools -from odoo.exceptions import UserError -from odoo.tests import tagged -from odoo.addons.account_edi.tests.common import AccountEdiTestCommon -from odoo.addons.l10n_it_edi.tools.remove_signature import remove_signature - -_logger = logging.getLogger(__name__) - -@tagged('post_install_l10n', 'post_install', '-at_install') -class PecMailServerTests(AccountEdiTestCommon): - """ Main test class for the l10n_it_edi vendor bills XML import from a PEC mail account""" - - fake_test_content = """ - - - - - 01234560157 - - - - - - - TD02 - - - - """ - - @classmethod - def setUpClass(cls): - """ Setup the test class with a PEC mail server and a fake fatturaPA content """ - - super().setUpClass(chart_template_ref='l10n_it.l10n_it_chart_template_generic', - edi_format_ref='l10n_it_edi.edi_fatturaPA') - - # Use the company_data_2 to test that the e-invoice is imported for the right company - cls.company = cls.company_data_2['company'] - - # Initialize the company's codice fiscale - cls.company.l10n_it_codice_fiscale = '01234560157' - - # Build test data. - # invoice_filename1 is used for vendor bill receipts tests - # invoice_filename2 is used for vendor bill tests - cls.invoice_filename1 = 'IT01234567890_FPR01.xml' - cls.invoice_filename2 = 'IT01234567890_FPR02.xml' - cls.signed_invoice_filename = 'IT01234567890_FPR01.xml.p7m' - cls.invoice_content = cls._get_test_file_content(cls.invoice_filename1) - cls.signed_invoice_content = cls._get_test_file_content(cls.signed_invoice_filename) - cls.invoice = cls.env['account.move'].create({ - 'move_type': 'in_invoice', - 'ref': '01234567890' - }) - cls.attachment = cls.env['ir.attachment'].create({ - 'name': cls.invoice_filename1, - 'raw': cls.invoice_content, - 'res_id': cls.invoice.id, - 'res_model': 'account.move', - }) - cls.edi_document = cls.env['account.edi.document'].create({ - 'edi_format_id': cls.edi_format.id, - 'move_id': cls.invoice.id, - 'attachment_id': cls.attachment.id, - 'state': 'sent' - }) - - # Initialize the fetchmail server that has to be tested - cls.server = cls.env['fetchmail.server'].sudo().create({ - 'name': 'test_server', - 'server_type': 'imap', - 'l10n_it_is_pec': True}) - - cls.test_invoice_xmls = {k: cls._get_test_file_content(v) for k, v in [ - ('normal_1', 'IT01234567890_FPR01.xml'), - ('signed', 'IT01234567890_FPR01.xml.p7m'), - ]} - - @classmethod - def _get_test_file_content(cls, filename): - """ Get the content of a test file inside this module """ - path = 'l10n_it_edi/tests/expected_xmls/' + filename - with tools.file_open(path, mode='rb') as test_file: - return test_file.read() - - def _create_invoice(self, content, filename): - """ Create an invoice from given attachment content """ - with patch.object(self.server._cr, 'commit', return_value=None): - if filename.endswith(".p7m"): - content = remove_signature(content) - return self.server._create_invoice_from_mail(content, filename, 'fake@address.be') - - # ----------------------------- - # - # Vendor bills - # - # ----------------------------- - - def test_receive_vendor_bill(self): - """ Test a sample e-invoice file from https://www.fatturapa.gov.it/export/documenti/fatturapa/v1.2/IT01234567890_FPR01.xml """ - invoices = self._create_invoice(self.invoice_content, self.invoice_filename2) - self.assertTrue(bool(invoices)) - - def test_receive_signed_vendor_bill(self): - """ Test a signed (P7M) sample e-invoice file from https://www.fatturapa.gov.it/export/documenti/fatturapa/v1.2/IT01234567890_FPR01.xml """ - with freeze_time('2020-04-06'): - invoices = self._create_invoice(self.signed_invoice_content, self.signed_invoice_filename) - self.assertRecordValues(invoices, [{ - 'company_id': self.company.id, - 'name': 'BILL/2014/12/0001', - 'invoice_date': datetime.date(2014, 12, 18), - 'ref': '01234567890', - }]) - - def test_receive_same_vendor_bill_twice(self): - """ Test that the second time we are receiving a PEC mail with the same attachment, the second is discarded """ - content = self.fake_test_content.encode() - for result in [True, False]: - invoice = self._create_invoice(content, self.invoice_filename2) - self.assertEqual(result, bool(invoice)) - - # ----------------------------- - # - # Receipts - # - # ----------------------------- - - def _test_receipt(self, receipt_type, source_state, destination_state): - """ Test a receipt from the ones in the module's test files """ - - # Simulate the 'sent' state of the move, even if we didn't actually send an email in this test - self.invoice.l10n_it_send_state = source_state - - # Create a fake receipt from the test file - receipt_filename = 'IT01234567890_FPR01_%s_001.xml' % receipt_type - receipt_content = self._get_test_file_content(receipt_filename).decode() - - create_mail_attachment = namedtuple('Attachment', ('fname', 'content', 'info')) - receipt_mail_attachment = create_mail_attachment(receipt_filename, receipt_content, {}) - - # Simulate the arrival of the receipt - with patch.object(self.server._cr, 'commit', return_value=None): - self.server._message_receipt_invoice(receipt_type, receipt_mail_attachment) - - # Check the Destination state of the edi_document - self.assertTrue(destination_state, self.edi_document.state) - - def test_ricevuta_consegna(self): - """ Test a receipt adapted from https://www.fatturapa.gov.it/export/documenti/messaggi/v1.0/IT01234567890_11111_RC_001.xml """ - self._test_receipt('RC', 'sent', 'delivered') - - def test_decorrenza_termini(self): - """ Test a receipt adapted from https://www.fatturapa.gov.it/export/documenti/messaggi/v1.0/IT01234567890_11111_DT_001.xml """ - self._test_receipt('DT', 'delivered', 'delivered_expired') - - # ----------------------------- - # Mail server - # ----------------------------- - - def test_server_archived_usage_protection(self): - """ Test the protection against using archived server (servers used cannot be archived) """ - server = self.env['ir.mail_server'].create({ - 'name': 'Server', - 'smtp_host': 'archive-test.smtp.local', - }) - self.company.l10n_it_mail_pec_server_id = server.id - with self.assertRaises(UserError, msg='Server cannot be archived because it is used'): - server.action_archive() - self.assertTrue(server.active) - self.company.l10n_it_mail_pec_server_id = False - server.action_archive() # No more usage -> can be archived - self.assertFalse(server.active) diff --git a/addons/l10n_it_edi/views/l10n_it_view.xml b/addons/l10n_it_edi/views/l10n_it_view.xml index 28df80b21df..6cb63fa4f63 100644 --- a/addons/l10n_it_edi/views/l10n_it_view.xml +++ b/addons/l10n_it_edi/views/l10n_it_view.xml @@ -1,19 +1,5 @@ - - fetchmail.server.form.l10n.it - fetchmail.server - 20 - - - - - - - - - - account.tax.form.l10n.it account.tax @@ -58,11 +44,6 @@ - - - - - @@ -102,46 +83,6 @@ - - account.invoice.supplier.tree.l10n.it - account.move - 20 - - - - - - -