[IMP] l10n_it_edi: remove account_edi from depends
The account_edi framework is no more a dependancy. - Models account.edi.document and account.edi.format have been removed. All the functions have been moved to the new account.move.send, account.move, account.edi.proxy.client.user - The move now holds the l10n_it_edi_state which admits all values that the SdI sends us as a status update - The sending is now not handled by the cron but by the Send and Print dialog. - Vendor bills Tax Integration sending by dedicated button - The move form will now hold the state of the EDI transaction, its number and the EDI attachment as a Binary field on the move itself. - A new banner will keep the latest message from the SdI, that's not the account_edi old banner. - All the user messages have been simplified. IAP-apps PR: odoo/iap-apps#652 Upgrade PR: odoo/upgrade#4893 Task link: https://www.odoo.com/web#id=3339971&cids=1&model=project.task Task-3339971 Part-of: odoo/odoo#122194
This commit is contained in:
@@ -96,6 +96,7 @@ class IrAttachment(models.Model):
|
||||
This function is meant to be overriden to add formats.
|
||||
|
||||
:returns: A list of dictionary.
|
||||
|
||||
* format: Optional but helps debugging.
|
||||
There are other methods that require the attachment
|
||||
to be an XML other than the standard one.
|
||||
|
||||
@@ -7,9 +7,6 @@
|
||||
'version': '0.3',
|
||||
'depends': [
|
||||
'l10n_it',
|
||||
# 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'],
|
||||
@@ -20,13 +17,13 @@ E-invoice implementation
|
||||
'website': 'http://www.odoo.com/',
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'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',
|
||||
],
|
||||
'wizard/account_move_send_views.xml',
|
||||
],
|
||||
'demo': [
|
||||
'data/account_invoice_demo.xml',
|
||||
],
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<record id="edi_fatturaPA" model="account.edi.format">
|
||||
<field name="name">Fattura PA (IT)</field>
|
||||
<field name="code">fattura_pa</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -19,7 +19,7 @@
|
||||
t-out="format_alphanumeric(line.product_uom_id.name, 10)"/>
|
||||
<PrezzoUnitario t-out="'%.06f' % (line_dict['unit_price'])"/>
|
||||
<ScontoMaggiorazione t-if="line.discount != 0">
|
||||
<Tipo t-out="discount_type(line.discount)"/>
|
||||
<Tipo t-out="format_alphanumeric(line_dict['discount_type'])"/>
|
||||
<Percentuale t-out="format_numbers(abs(line.discount))"/>
|
||||
</ScontoMaggiorazione>
|
||||
<PrezzoTotale t-out="format_monetary(line_dict['subtotal_price'], currency)"/>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="ir_cron_receive_fattura_pa_invoice" model="ir.cron">
|
||||
<field name="name">FatturaPA: Receive invoices from the exchange system</field>
|
||||
<record id="ir_cron_l10n_it_edi_download_and_update" model="ir.cron">
|
||||
<field name="name">IT EDI: Receive invoices from the SdI</field>
|
||||
<field name="interval_number">1</field>
|
||||
<field name="interval_type">days</field>
|
||||
<field name="numbercall">-1</field>
|
||||
<field name="model_id" ref="account_edi.model_account_edi_format"/>
|
||||
<field name="code">model._cron_receive_fattura_pa()</field>
|
||||
<field name="model_id" ref="account.model_account_move"/>
|
||||
<field name="code">model.cron_l10n_it_edi_download_and_update()</field>
|
||||
<field name="doall" eval="False"/>
|
||||
<field name="state">code</field>
|
||||
</record>
|
||||
|
||||
+476
-274
File diff suppressed because it is too large
Load Diff
@@ -6,8 +6,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 16.4alpha1+e\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-05-31 09:04+0000\n"
|
||||
"PO-Revision-Date: 2023-05-31 09:04+0000\n"
|
||||
"POT-Creation-Date: 2023-06-09 15:24+0000\n"
|
||||
"PO-Revision-Date: 2023-06-09 15:24+0000\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@@ -17,81 +17,96 @@ msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: 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_move.py:0
|
||||
#: code:addons/l10n_it_edi/models/account_move.py:0
|
||||
#, python-format
|
||||
msgid "%s has an amount of 0.0, you must indicate the kind of exoneration."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_edi_format.py:0
|
||||
#: code:addons/l10n_it_edi/models/account_move.py:0
|
||||
#, python-format
|
||||
msgid "%s must have a VAT number"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: 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_move.py:0
|
||||
#: code:addons/l10n_it_edi/models/account_move.py:0
|
||||
#, python-format
|
||||
msgid "%s must have a city."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_edi_format.py:0
|
||||
#: code:addons/l10n_it_edi/models/account_move.py:0
|
||||
#, python-format
|
||||
msgid "%s must have a codice fiscale number"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_edi_format.py:0
|
||||
#: code:addons/l10n_it_edi/models/account_move.py:0
|
||||
#, python-format
|
||||
msgid "%s must have a country"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: 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_move.py:0
|
||||
#: code:addons/l10n_it_edi/models/account_move.py:0
|
||||
#, python-format
|
||||
msgid "%s must have a country."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: 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_move.py:0
|
||||
#: code:addons/l10n_it_edi/models/account_move.py:0
|
||||
#, python-format
|
||||
msgid "%s must have a post code of length 5."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: 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_move.py:0
|
||||
#: code:addons/l10n_it_edi/models/account_move.py:0
|
||||
#, python-format
|
||||
msgid "%s must have a post code."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: 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_move.py:0
|
||||
#: code:addons/l10n_it_edi/models/account_move.py:0
|
||||
#, python-format
|
||||
msgid "%s must have a street."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_invoice.py:0
|
||||
#: code:addons/l10n_it_edi/models/account_tax.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"'Scissione dei pagamenti' is not compatible with exoneration of kind 'N6'"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#: model_terms:ir.ui.view,arch_db:l10n_it_edi.account_invoice_form_l10n_it
|
||||
msgid ""
|
||||
"<i class=\"fa fa-warning\" role=\"img\" title=\"EDI (Italy)\"/><span "
|
||||
"class=\"mx-1\">E-invoicing (Italy)</span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#: model_terms:ir.ui.view,arch_db:l10n_it_edi.account_move_send_inherit_l10n_it_edi
|
||||
msgid ""
|
||||
"<span attrs=\"{'invisible': [('l10n_it_edi_warning_message', '=', False)]}\">\n"
|
||||
" <b>Tax Agency (Italy)</b>\n"
|
||||
" </span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#: model_terms:ir.ui.view,arch_db:l10n_it_edi.res_config_settings_view_form
|
||||
msgid ""
|
||||
@@ -111,16 +126,6 @@ msgstr ""
|
||||
msgid "A Demo service is in use."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_edi_format.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
|
||||
#: model:ir.model,name:l10n_it_edi.model_account_chart_template
|
||||
msgid "Account Chart Template"
|
||||
@@ -151,7 +156,16 @@ msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_edi_format.py:0
|
||||
#: code:addons/l10n_it_edi/models/account_edi_proxy_user.py:0
|
||||
#: code:addons/l10n_it_edi/models/account_edi_proxy_user.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"An error occurred while downloading updates from the Proxy Server: (%s) %s"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_edi_proxy_user.py:0
|
||||
#, python-format
|
||||
msgid "Attached file is empty"
|
||||
msgstr ""
|
||||
@@ -163,19 +177,24 @@ msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_edi_format.py:0
|
||||
#: code:addons/l10n_it_edi/models/account_move.py:0
|
||||
#, python-format
|
||||
msgid "Attachment from XML"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: 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_move.py:0
|
||||
#: code:addons/l10n_it_edi/models/account_move.py:0
|
||||
#, python-format
|
||||
msgid "Bank account not found, useful informations from XML file:"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#: model:ir.model.fields.selection,name:l10n_it_edi.selection__account_move__l10n_it_edi_state__being_sent
|
||||
msgid "Being Sent To SdI"
|
||||
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."
|
||||
@@ -183,13 +202,18 @@ msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_edi_format.py:0
|
||||
#: code:addons/l10n_it_edi/models/account_move.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Cannot apply Reverse Charge to a bill which contains both services and "
|
||||
"goods."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#: model_terms:ir.ui.view,arch_db:l10n_it_edi.account_invoice_form_l10n_it
|
||||
msgid "Check Sending"
|
||||
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
|
||||
@@ -232,13 +256,6 @@ msgstr ""
|
||||
msgid "Created by"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_invoice.py:0
|
||||
#, python-format
|
||||
msgid "Created from attachment in %s"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#: model:ir.model.fields,field_description:l10n_it_edi.field_l10n_it_ddt__create_date
|
||||
msgid "Created on"
|
||||
@@ -270,7 +287,7 @@ msgid "Dati Bollo"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#: model:ir.model.fields.selection,name:l10n_it_edi.selection__res_config_settings__l10n_it_edi_sdicoop_demo_mode__demo
|
||||
#: model:ir.model.fields.selection,name:l10n_it_edi.selection__res_config_settings__l10n_it_edi_demo_mode__demo
|
||||
msgid "Demo"
|
||||
msgstr ""
|
||||
|
||||
@@ -292,16 +309,9 @@ msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_edi_format.py:0
|
||||
#: code:addons/l10n_it_edi/models/account_move.py:0
|
||||
#, python-format
|
||||
msgid "Document date invalid in XML file:"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_invoice.py:0
|
||||
#, python-format
|
||||
msgid "E-Invoice is generated on %s by %s"
|
||||
msgid "Document date invalid in XML file: %s"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
@@ -309,11 +319,6 @@ msgstr ""
|
||||
msgid "EAN"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#: model:ir.model,name:l10n_it_edi.model_account_edi_format
|
||||
msgid "EDI format"
|
||||
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"
|
||||
@@ -324,11 +329,6 @@ msgstr ""
|
||||
msgid "Electronic Document Invoicing"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#: model:ir.model,name:l10n_it_edi.model_account_edi_document
|
||||
msgid "Electronic Document for an account.move"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#: model_terms:ir.ui.view,arch_db:l10n_it_edi.account_invoice_form_l10n_it
|
||||
#: model_terms:ir.ui.view,arch_db:l10n_it_edi.res_company_form_l10n_it
|
||||
@@ -336,10 +336,21 @@ msgid "Electronic Invoicing"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#: model:ir.model.fields,field_description:l10n_it_edi.field_account_bank_statement_line__l10n_it_einvoice_id
|
||||
#: model:ir.model.fields,field_description:l10n_it_edi.field_account_move__l10n_it_einvoice_id
|
||||
#: model:ir.model.fields,field_description:l10n_it_edi.field_account_payment__l10n_it_einvoice_id
|
||||
msgid "Electronic invoice"
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_move.py:0
|
||||
#: code:addons/l10n_it_edi/models/account_move.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Error uploading the e-invoice file %s.\n"
|
||||
"%s"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_move.py:0
|
||||
#: code:addons/l10n_it_edi/wizard/account_move_send.py:0
|
||||
#, python-format
|
||||
msgid "Errors occured while creating the e-invoice file."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
@@ -376,11 +387,6 @@ msgstr ""
|
||||
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
|
||||
msgid "FatturaPA: Receive invoices from the exchange system"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#: model:ir.model.fields,help:l10n_it_edi.field_res_company__l10n_it_codice_fiscale
|
||||
msgid "Fiscal code of your company"
|
||||
@@ -401,6 +407,18 @@ msgstr ""
|
||||
msgid "INTERNAL"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_move.py:0
|
||||
#, python-format
|
||||
msgid "IT EDI e-move: %s"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#: model:ir.actions.server,name:l10n_it_edi.ir_cron_l10n_it_edi_download_and_update_ir_actions_server
|
||||
msgid "IT EDI: Receive invoices from the SdI"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/res_company.py:0
|
||||
@@ -412,7 +430,7 @@ msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_invoice.py:0
|
||||
#: code:addons/l10n_it_edi/models/account_tax.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"If the tax has exoneration, you must enter a kind of exoneration, a law "
|
||||
@@ -430,7 +448,7 @@ msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_edi_format.py:0
|
||||
#: code:addons/l10n_it_edi/models/account_move.py:0
|
||||
#, python-format
|
||||
msgid "In line %s, you must select one and only one tax."
|
||||
msgstr ""
|
||||
@@ -449,15 +467,6 @@ msgstr ""
|
||||
msgid "Invoice Reference"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_edi_format.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Invoices for PA are not managed by Odoo, you can download the document and "
|
||||
"send it on your own."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#: model:ir.model.fields,field_description:l10n_it_edi.field_res_config_settings__is_edi_proxy_active
|
||||
msgid "Is Edi Proxy Active"
|
||||
@@ -469,16 +478,39 @@ msgid "Italian EDI"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: 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"
|
||||
#: model:ir.model,name:l10n_it_edi.model_account_move
|
||||
msgid "Journal Entry"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#: model:ir.model,name:l10n_it_edi.model_account_move
|
||||
msgid "Journal Entry"
|
||||
#: model:ir.model.fields,field_description:l10n_it_edi.field_account_bank_statement_line__l10n_it_edi_attachment_file
|
||||
#: model:ir.model.fields,field_description:l10n_it_edi.field_account_move__l10n_it_edi_attachment_file
|
||||
#: model:ir.model.fields,field_description:l10n_it_edi.field_account_payment__l10n_it_edi_attachment_file
|
||||
msgid "L10N It Edi Attachment File"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#: model:ir.model.fields,field_description:l10n_it_edi.field_res_config_settings__l10n_it_edi_demo_mode
|
||||
msgid "L10N It Edi Demo Mode"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#: model:ir.model.fields,field_description:l10n_it_edi.field_account_move_send__l10n_it_edi_enable_send
|
||||
msgid "L10N It Edi Enable Send"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#: model:ir.model.fields,field_description:l10n_it_edi.field_account_bank_statement_line__l10n_it_edi_header
|
||||
#: model:ir.model.fields,field_description:l10n_it_edi.field_account_move__l10n_it_edi_header
|
||||
#: model:ir.model.fields,field_description:l10n_it_edi.field_account_payment__l10n_it_edi_header
|
||||
msgid "L10N It Edi Header"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#: model:ir.model.fields,field_description:l10n_it_edi.field_account_bank_statement_line__l10n_it_edi_is_self_invoice
|
||||
#: model:ir.model.fields,field_description:l10n_it_edi.field_account_move__l10n_it_edi_is_self_invoice
|
||||
#: model:ir.model.fields,field_description:l10n_it_edi.field_account_payment__l10n_it_edi_is_self_invoice
|
||||
msgid "L10N It Edi Is Self Invoice"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
@@ -487,20 +519,30 @@ 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"
|
||||
#: model:ir.model.fields,field_description:l10n_it_edi.field_res_company__l10n_it_edi_proxy_user_id
|
||||
msgid "L10N It Edi Proxy User"
|
||||
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"
|
||||
#: model:ir.model.fields,field_description:l10n_it_edi.field_account_move_send__l10n_it_edi_readonly
|
||||
msgid "L10N It Edi Readonly"
|
||||
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
|
||||
#: model:ir.model.fields,field_description:l10n_it_edi.field_account_payment__l10n_it_einvoice_name
|
||||
msgid "L10N It Einvoice Name"
|
||||
#: model:ir.model.fields,field_description:l10n_it_edi.field_res_config_settings__l10n_it_edi_register
|
||||
msgid "L10N It Edi Register"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#: model:ir.model.fields,field_description:l10n_it_edi.field_account_bank_statement_line__l10n_it_edi_state
|
||||
#: model:ir.model.fields,field_description:l10n_it_edi.field_account_move__l10n_it_edi_state
|
||||
#: model:ir.model.fields,field_description:l10n_it_edi.field_account_payment__l10n_it_edi_state
|
||||
msgid "L10N It Edi State"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#: model:ir.model.fields,field_description:l10n_it_edi.field_account_move_send__l10n_it_edi_warning_message
|
||||
msgid "L10N It Edi Warning Message"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
@@ -550,11 +592,10 @@ msgstr ""
|
||||
#: model:ir.model.fields,help:l10n_it_edi.field_res_partner__l10n_it_pa_index
|
||||
#: model:ir.model.fields,help:l10n_it_edi.field_res_users__l10n_it_pa_index
|
||||
msgid ""
|
||||
"Must contain the 6-character (or 7) code, present in the PA "
|
||||
"Index in the information relative to the electronic invoicing service,"
|
||||
" associated with the office which, within the addressee "
|
||||
"administration, deals with receiving (and processing) the "
|
||||
"invoice."
|
||||
"Must contain the 6-character (or 7) code, present in the PA Index in the "
|
||||
"information relative to the electronic invoicing service, associated with "
|
||||
"the office which, within the addressee administration, deals with receiving "
|
||||
"(and processing) the invoice."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
@@ -573,7 +614,7 @@ msgid "Numero DDT"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#: model:ir.model.fields.selection,name:l10n_it_edi.selection__res_config_settings__l10n_it_edi_sdicoop_demo_mode__prod
|
||||
#: model:ir.model.fields.selection,name:l10n_it_edi.selection__res_config_settings__l10n_it_edi_demo_mode__prod
|
||||
msgid "Official"
|
||||
msgstr ""
|
||||
|
||||
@@ -591,9 +632,9 @@ msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_edi_format.py:0
|
||||
#: code:addons/l10n_it_edi/models/account_move.py:0
|
||||
#, python-format
|
||||
msgid "Payment due date invalid in XML file:"
|
||||
msgid "Payment due date invalid in XML file: %s"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
@@ -603,7 +644,7 @@ msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_edi_format.py:0
|
||||
#: code:addons/l10n_it_edi/models/account_edi_proxy_user.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Please fill your codice fiscale to be able to receive invoices from "
|
||||
@@ -625,14 +666,81 @@ msgstr ""
|
||||
msgid "Proxy type"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#: model:ir.model.fields.selection,name:l10n_it_edi.selection__account_move__l10n_it_edi_state__requires_user_signature
|
||||
msgid "Requires user signature"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#: model_terms:ir.ui.view,arch_db:l10n_it_edi.account_invoice_it_FatturaPA_export
|
||||
msgid "SI"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#: model:ir.model.fields.selection,name:l10n_it_edi.selection__account_move__l10n_it_edi_state__accepted_by_pa_partner
|
||||
msgid "SdI Accepted, Accepted by the PA Partner"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#: model:ir.model.fields.selection,name:l10n_it_edi.selection__account_move__l10n_it_edi_state__forward_failed
|
||||
msgid "SdI Accepted, Forward to Partner Failed"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#: model:ir.model.fields.selection,name:l10n_it_edi.selection__account_move__l10n_it_edi_state__forwarded
|
||||
msgid "SdI Accepted, Forwarded to Partner"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#: model:ir.model.fields.selection,name:l10n_it_edi.selection__account_move__l10n_it_edi_state__forward_attempt
|
||||
msgid "SdI Accepted, Forwarding to Partner"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#: model:ir.model.fields.selection,name:l10n_it_edi.selection__account_move__l10n_it_edi_state__accepted_by_pa_partner_after_expiry
|
||||
msgid "SdI Accepted, PA Partner Expired Terms"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#: model:ir.model.fields.selection,name:l10n_it_edi.selection__account_move__l10n_it_edi_state__rejected_by_pa_partner
|
||||
msgid "SdI Accepted, Rejected by the PA Partner"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#: model:ir.model.fields.selection,name:l10n_it_edi.selection__account_move__l10n_it_edi_state__processing
|
||||
msgid "SdI Processing"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#: model:ir.model.fields.selection,name:l10n_it_edi.selection__account_move__l10n_it_edi_state__rejected
|
||||
msgid "SdI Rejected"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#: model_terms:ir.ui.view,arch_db:l10n_it_edi.account_invoice_form_l10n_it
|
||||
msgid "Send Tax Integration"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#: model:ir.model.fields,help:l10n_it_edi.field_account_move_send__l10n_it_edi_checkbox_send
|
||||
msgid ""
|
||||
"Send the invoice to the Italian Tax Agency.\n"
|
||||
"It is set as readonly if a report has already been created, to avoid inconsistencies.\n"
|
||||
"To re-enable it, delete the PDF attachment."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_edi_format.py:0
|
||||
#: code:addons/l10n_it_edi/models/account_move.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Sending invoices to Public Administration partners is not supported.\n"
|
||||
"The IT EDI XML file is generated, please sign the document and upload it through the 'Fatture e Corrispettivi' portal of the Tax Agency."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_edi_proxy_user.py:0
|
||||
#, python-format
|
||||
msgid "Service momentarily unavailable"
|
||||
msgstr ""
|
||||
@@ -674,6 +782,11 @@ msgstr ""
|
||||
msgid "Tax"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#: model:ir.model.fields,field_description:l10n_it_edi.field_account_move_send__l10n_it_edi_checkbox_send
|
||||
msgid "Tax Agency (Italy)"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#: model:ir.model.fields,field_description:l10n_it_edi.field_res_company__l10n_it_tax_system
|
||||
msgid "Tax System"
|
||||
@@ -686,7 +799,7 @@ msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_edi_format.py:0
|
||||
#: code:addons/l10n_it_edi/models/account_move.py:0
|
||||
#, python-format
|
||||
msgid "Tax not found for line with description '%s'"
|
||||
msgstr ""
|
||||
@@ -703,19 +816,19 @@ msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_edi_format.py:0
|
||||
#: code:addons/l10n_it_edi/models/account_move.py:0
|
||||
#, python-format
|
||||
msgid "Tax representative partner %s of %s must have a tax number."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#: model:ir.model.fields.selection,name:l10n_it_edi.selection__res_config_settings__l10n_it_edi_sdicoop_demo_mode__test
|
||||
#: model:ir.model.fields.selection,name:l10n_it_edi.selection__res_config_settings__l10n_it_edi_demo_mode__test
|
||||
msgid "Test (experimental)"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_edi_format.py:0
|
||||
#: code:addons/l10n_it_edi/models/account_move.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"The buyer, %s, or his company must have a VAT number and/or a tax code "
|
||||
@@ -743,86 +856,135 @@ msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_edi_format.py:0
|
||||
#: code:addons/l10n_it_edi/models/account_move.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"The filename is duplicated. Try again (or adjust the FatturaPA Filename "
|
||||
"sequence). Original message from the SDI: %s"
|
||||
"The e-invoice file %s can't be forward to %s (Public Administration) by the SdI at the moment.\n"
|
||||
"It will try again for 10 days, after which it will be considered accepted, but you will still have to send it by post or e-mail."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_edi_format.py:0
|
||||
#: code:addons/l10n_it_edi/models/account_move.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"The invoice has been correctly issued. The Public Administration recipient "
|
||||
"had 15 days to either accept or refused this document, but they did not "
|
||||
"reply, so from now on we consider it accepted."
|
||||
"The e-invoice file %s couldn't be forwarded to %s.\n"
|
||||
"Please remember to send it via post or e-mail."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_edi_format.py:0
|
||||
#: code:addons/l10n_it_edi/models/account_move.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."
|
||||
"The e-invoice file %s has been accepted by %s (Public Administration), a "
|
||||
"payment will be issued soon"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_edi_format.py:0
|
||||
#: code:addons/l10n_it_edi/models/account_move.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."
|
||||
"The e-invoice file %s has been accepted by the SdI.\n"
|
||||
"The SdI is trying to forward it to %s.\n"
|
||||
"It will try for up to 2 days, after which you'll eventually need to send it the invoice to the partner by post or e-mail."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: 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
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_edi_format.py:0
|
||||
#: code:addons/l10n_it_edi/models/account_move.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"The invoice has been succesfully transmitted. The addressee has 15 days to "
|
||||
"accept or reject it."
|
||||
"The e-invoice file %s has been refused by %s (Public Administration).\n"
|
||||
"You have 5 days from now to issue a full refund for this invoice, then contact the PA partner to create a new one according to their requests and submit it."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_edi_format.py:0
|
||||
#: code:addons/l10n_it_edi/models/account_move.py:0
|
||||
#, python-format
|
||||
msgid "The invoice was refused by the addressee."
|
||||
msgid "The e-invoice file %s has been sent in Demo EDI mode."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: 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_move.py:0
|
||||
#, python-format
|
||||
msgid "The e-invoice file %s has not been found on the EDI Proxy server."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_move.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."
|
||||
"The e-invoice file %s is duplicated.\n"
|
||||
"Original message from the SdI: %s"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_edi_format.py:0
|
||||
#: code:addons/l10n_it_edi/models/account_move.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"The e-invoice file %s is succesfully sent to the SdI. The invoice is now considered fiscally relevant.\n"
|
||||
"The %s (Public Administration) had 15 days to either accept or refused this document,but since they did not reply, it's now considered accepted."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_move.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"The e-invoice file %s was accepted and succesfully forwarded it to %s by the"
|
||||
" SdI."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_move.py:0
|
||||
#, python-format
|
||||
msgid "The e-invoice file %s was sent to the SdI for processing."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_move.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"The e-invoice file %s was sent to the SdI for validation.\n"
|
||||
"It is not yet considered accepted, please wait further notifications."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_move.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"The e-invoice file %s was succesfully sent to the SdI.\n"
|
||||
"%s has 15 days to accept or reject it."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_move.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"The e-invoice filename %s is duplicated. Please check the FatturaPA Filename sequence.\n"
|
||||
"Original message from the SdI: %s"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_move.py:0
|
||||
#, python-format
|
||||
msgid "The e-invoice has been refused by the SdI."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_move.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"The maximum length for VAT number is 30. %s have a VAT number too long: %s."
|
||||
@@ -830,7 +992,7 @@ msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_edi_format.py:0
|
||||
#: code:addons/l10n_it_edi/models/account_move.py:0
|
||||
#, python-format
|
||||
msgid "The seller's company must have a tax system."
|
||||
msgstr ""
|
||||
@@ -875,16 +1037,14 @@ msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_edi_format.py:0
|
||||
#: code:addons/l10n_it_edi/models/account_move.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"
|
||||
msgid "This move is not waiting for updates from the SdI."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_edi_format.py:0
|
||||
#: code:addons/l10n_it_edi/models/account_move.py:0
|
||||
#, python-format
|
||||
msgid "Total amount from the XML File: %s"
|
||||
msgstr ""
|
||||
@@ -907,18 +1067,33 @@ msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_edi_format.py:0
|
||||
#: code:addons/l10n_it_edi/models/account_move.py:0
|
||||
#, python-format
|
||||
msgid "Transport informations from XML file:"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_edi_format.py:0
|
||||
#: code:addons/l10n_it_edi/models/account_edi_proxy_user.py:0
|
||||
#, python-format
|
||||
msgid "Unauthorized user"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_edi_proxy_user.py:0
|
||||
#, python-format
|
||||
msgid "Unknown error"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#: model:ir.model.fields,help:l10n_it_edi.field_account_bank_statement_line__l10n_it_edi_header
|
||||
#: model:ir.model.fields,help:l10n_it_edi.field_account_move__l10n_it_edi_header
|
||||
#: model:ir.model.fields,help:l10n_it_edi.field_account_payment__l10n_it_edi_header
|
||||
msgid ""
|
||||
"User description of the current state, with hints to make the flow progress"
|
||||
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"
|
||||
@@ -926,7 +1101,7 @@ msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_edi_format.py:0
|
||||
#: code:addons/l10n_it_edi/models/account_move.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Vendor bills sent as self-invoices to the SdI require a valid PA Index "
|
||||
@@ -935,26 +1110,26 @@ msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_edi_format.py:0
|
||||
#: code:addons/l10n_it_edi/models/account_move.py:0
|
||||
#, python-format
|
||||
msgid "Vendor not found, useful informations from XML file:"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_edi_format.py:0
|
||||
#: code:addons/l10n_it_edi/models/account_move.py:0
|
||||
#, python-format
|
||||
msgid "You are not allowed to check the status of this invoice."
|
||||
msgid ""
|
||||
"We are simulating the sending of the e-invoice file %s, as we are in demo "
|
||||
"mode."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: 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
|
||||
#: code:addons/l10n_it_edi/wizard/account_move_send.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"You must accept the terms and conditions in the settings to use FatturaPA."
|
||||
"You must accept the terms and conditions in the Settings to use the IT EDI."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
@@ -1229,7 +1404,7 @@ msgstr ""
|
||||
|
||||
#. module: l10n_it_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_it_edi/models/account_edi_format.py:0
|
||||
#: code:addons/l10n_it_edi/models/account_move.py:0
|
||||
#, python-format
|
||||
msgid "from XML file:"
|
||||
msgstr ""
|
||||
msgstr ""
|
||||
|
||||
@@ -5,9 +5,8 @@ from . import res_partner
|
||||
from . import res_company
|
||||
from . import res_config_settings
|
||||
from . import account_chart_template
|
||||
from . import account_invoice
|
||||
from . import account_edi_document
|
||||
from . import account_edi_format
|
||||
from . import account_move
|
||||
from . import account_tax
|
||||
from . import ddt
|
||||
from . import ir_attachment
|
||||
from . import account_edi_proxy_user
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import models
|
||||
|
||||
|
||||
class AccountEdiDocument(models.Model):
|
||||
_inherit = "account.edi.document"
|
||||
|
||||
def _filter_edi_attachments_for_mailing(self):
|
||||
self.ensure_one()
|
||||
if self.edi_format_id.code == 'fattura_pa':
|
||||
return {}
|
||||
return super()._filter_edi_attachments_for_mailing()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import logging
|
||||
|
||||
from odoo import _, fields, models
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AccountEdiProxyClientUser(models.Model):
|
||||
@@ -18,16 +22,9 @@ class AccountEdiProxyClientUser(models.Model):
|
||||
}
|
||||
return urls
|
||||
|
||||
def _compute_proxy_type(self):
|
||||
# Extends account_edi_proxy_client
|
||||
super()._compute_proxy_type()
|
||||
for user in self:
|
||||
if user.company_id.country_code == 'IT':
|
||||
user.proxy_type = 'l10n_it_edi'
|
||||
|
||||
def _get_proxy_identification(self, company, proxy_type):
|
||||
if proxy_type == 'l10n_it_edi':
|
||||
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_edi_normalized_codice_fiscale(company.l10n_it_codice_fiscale)
|
||||
return company.partner_id._l10n_it_edi_normalized_codice_fiscale()
|
||||
return super()._get_proxy_identification(company, proxy_type)
|
||||
|
||||
@@ -1,458 +0,0 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import base64
|
||||
from functools import reduce
|
||||
import logging
|
||||
import re
|
||||
|
||||
from datetime import datetime
|
||||
from markupsafe import escape
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.tools import float_repr, float_compare
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_FACTUR_ITALIAN_DATE_FORMAT = '%Y-%m-%d'
|
||||
|
||||
|
||||
class AccountMove(models.Model):
|
||||
_inherit = 'account.move'
|
||||
|
||||
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", ondelete="restrict")
|
||||
l10n_it_stamp_duty = fields.Float(default=0, string="Dati Bollo")
|
||||
l10n_it_ddt_id = fields.Many2one('l10n_it.ddt', string='DDT', copy=False)
|
||||
l10n_it_einvoice_name = fields.Char(compute='_compute_l10n_it_einvoice')
|
||||
l10n_it_einvoice_id = fields.Many2one('ir.attachment', string="Electronic invoice", compute='_compute_l10n_it_einvoice')
|
||||
|
||||
def _get_l10n_it_amount_split_payment(self):
|
||||
self.ensure_one()
|
||||
amount = 0.0
|
||||
if self.is_invoice(True):
|
||||
for line in [line for line in self.line_ids if line.tax_line_id]:
|
||||
if line.tax_line_id._l10n_it_is_split_payment() and line.credit > 0.0:
|
||||
amount += line.credit
|
||||
return amount
|
||||
|
||||
@api.depends('edi_document_ids', 'edi_document_ids.attachment_id')
|
||||
def _compute_l10n_it_einvoice(self):
|
||||
fattura_pa = self.env.ref('l10n_it_edi.edi_fatturaPA')
|
||||
for invoice in self:
|
||||
einvoice = invoice.edi_document_ids.filtered(lambda d: d.edi_format_id == fattura_pa).sudo()
|
||||
invoice.l10n_it_einvoice_id = einvoice.attachment_id
|
||||
invoice.l10n_it_einvoice_name = einvoice.attachment_id.name
|
||||
|
||||
@api.depends('l10n_it_edi_transaction')
|
||||
def _compute_show_reset_to_draft_button(self):
|
||||
super(AccountMove, self)._compute_show_reset_to_draft_button()
|
||||
for move in self.filtered(lambda m: m.l10n_it_edi_transaction):
|
||||
move.show_reset_to_draft_button = False
|
||||
|
||||
def invoice_generate_xml(self):
|
||||
self.ensure_one()
|
||||
report_name = self.env['account.edi.format']._l10n_it_edi_generate_electronic_invoice_filename(self)
|
||||
|
||||
data = "<?xml version='1.0' encoding='UTF-8'?>" + str(self._l10n_it_edi_export_invoice_as_xml())
|
||||
description = _('Italian invoice: %s', self.move_type)
|
||||
attachment = self.env['ir.attachment'].create({
|
||||
'name': report_name,
|
||||
'res_id': self.id,
|
||||
'res_model': self._name,
|
||||
'raw': data.encode(),
|
||||
'description': description,
|
||||
'type': 'binary',
|
||||
})
|
||||
|
||||
self.message_post(
|
||||
body=(_("E-Invoice is generated on %s by %s") % (fields.Datetime.now(), self.env.user.display_name))
|
||||
)
|
||||
return {'attachment': attachment}
|
||||
|
||||
def _is_commercial_partner_pa(self):
|
||||
"""
|
||||
Returns True if the destination of the FatturaPA belongs to the Public Administration.
|
||||
"""
|
||||
return len(self.commercial_partner_id.l10n_it_pa_index or '') == 6
|
||||
|
||||
def _l10n_it_edi_prepare_fatturapa_line_details(self, reverse_charge_refund=False, is_downpayment=False, convert_to_euros=True):
|
||||
""" Returns a list of dictionaries passed to the template for the invoice lines (DettaglioLinee)
|
||||
"""
|
||||
invoice_lines = []
|
||||
lines = self.invoice_line_ids.filtered(lambda l: not l.display_type in ('line_note', 'line_section'))
|
||||
for num, line in enumerate(lines):
|
||||
sign = -1 if line.move_id.is_inbound() else 1
|
||||
price_subtotal = (line.balance * sign) if convert_to_euros else line.price_subtotal
|
||||
# The price_subtotal should be inverted when the line is a reverse charge refund.
|
||||
if reverse_charge_refund:
|
||||
price_subtotal = -price_subtotal
|
||||
|
||||
# Unit price
|
||||
price_unit = 0
|
||||
if line.quantity and line.discount != 100.0:
|
||||
price_unit = price_subtotal / ((1 - (line.discount or 0.0) / 100.0) * abs(line.quantity))
|
||||
else:
|
||||
price_unit = line.price_unit
|
||||
|
||||
description = line.name
|
||||
|
||||
# Down payment lines:
|
||||
# If there was a down paid amount that has been deducted from this move,
|
||||
# we need to put a reference to the down payment invoice in the DatiFattureCollegate tag
|
||||
downpayment_moves = self.env['account.move']
|
||||
if not is_downpayment and line.price_subtotal < 0:
|
||||
downpayment_moves = line._get_downpayment_lines().mapped("move_id")
|
||||
if downpayment_moves:
|
||||
downpayment_moves_description = ', '.join([m.name for m in downpayment_moves])
|
||||
sep = ', ' if description else ''
|
||||
description = f"{description}{sep}{downpayment_moves_description}"
|
||||
|
||||
vat_tax = line.tax_ids.flatten_taxes_hierarchy().filtered(lambda t: t._l10n_it_filter_kind('vat') and t.amount >= 0)
|
||||
invoice_lines.append({
|
||||
'line': line,
|
||||
'line_number': num + 1,
|
||||
'description': description or 'NO NAME',
|
||||
'unit_price': price_unit,
|
||||
'subtotal_price': price_subtotal,
|
||||
'vat_tax': vat_tax,
|
||||
'downpayment_moves': downpayment_moves,
|
||||
})
|
||||
return invoice_lines
|
||||
|
||||
def _l10n_it_edi_prepare_fatturapa_tax_details(self, tax_details):
|
||||
""" Returns a list of dictionaries passed to the template for the invoice lines (DatiRiepilogo)
|
||||
"""
|
||||
tax_lines = []
|
||||
for _tax_name, tax_dict in tax_details['tax_details'].items():
|
||||
# The assumption is that the company currency is EUR.
|
||||
tax = tax_dict['tax']
|
||||
base_amount = tax_dict['base_amount']
|
||||
tax_amount = tax_dict['tax_amount']
|
||||
tax_rate = tax.amount
|
||||
tax_exigibility_code = (
|
||||
'S' if tax._l10n_it_is_split_payment()
|
||||
else 'D' if tax.tax_exigibility == 'on_payment'
|
||||
else 'I' if tax.tax_exigibility == 'on_invoice'
|
||||
else False
|
||||
)
|
||||
expected_base_amount = tax_amount * 100 / tax_rate if tax_rate else False
|
||||
# Constraints within the edi make local rounding on price included taxes a problem.
|
||||
# To solve this there is a <Arrotondamento> or 'rounding' field, such that:
|
||||
# taxable base = sum(taxable base for each unit) + Arrotondamento
|
||||
if tax.price_include and tax.amount_type == 'percent':
|
||||
if expected_base_amount and float_compare(base_amount, expected_base_amount, 2):
|
||||
tax_dict['rounding'] = base_amount - (tax_amount * 100 / tax_rate)
|
||||
tax_dict['base_amount'] = base_amount - tax_dict['rounding']
|
||||
|
||||
tax_line_dict = {
|
||||
'tax': tax,
|
||||
'rounding': tax_dict.get('rounding', False),
|
||||
'base_amount': tax_dict['base_amount'],
|
||||
'tax_amount': tax_dict['tax_amount'],
|
||||
'exigibility_code': tax_exigibility_code,
|
||||
}
|
||||
tax_lines.append(tax_line_dict)
|
||||
return tax_lines
|
||||
|
||||
def _l10n_it_edi_filter_fatturapa_tax_details(self, line, tax_values):
|
||||
"""Filters tax details to only include the positive amounted lines regarding VAT taxes."""
|
||||
repartition_line = tax_values['tax_repartition_line']
|
||||
return (repartition_line.factor_percent >= 0 and repartition_line.tax_id.amount >= 0)
|
||||
|
||||
def _prepare_fatturapa_export_values(self):
|
||||
self.ensure_one()
|
||||
|
||||
def format_date(dt):
|
||||
# Format the date in the italian standard.
|
||||
dt = dt or datetime.now()
|
||||
return dt.strftime(DEFAULT_FACTUR_ITALIAN_DATE_FORMAT)
|
||||
|
||||
def format_monetary(number, currency):
|
||||
# Format the monetary values to avoid trailing decimals (e.g. 90.85000000000001).
|
||||
return float_repr(number, min(2, currency.decimal_places))
|
||||
|
||||
def format_numbers(number):
|
||||
#format number to str with between 2 and 8 decimals (event if it's .00)
|
||||
number_splited = str(number).split('.')
|
||||
if len(number_splited) == 1:
|
||||
return "%.02f" % number
|
||||
|
||||
cents = number_splited[1]
|
||||
if len(cents) > 8:
|
||||
return "%.08f" % number
|
||||
return float_repr(number, max(2, len(cents)))
|
||||
|
||||
def format_numbers_two(number):
|
||||
#format number to str with 2 (event if it's .00)
|
||||
return "%.02f" % number
|
||||
|
||||
def discount_type(discount):
|
||||
return 'SC' if discount > 0 else 'MG'
|
||||
|
||||
def format_alphanumeric(text, maxlen=None):
|
||||
if not text:
|
||||
return False
|
||||
text = text.encode('latin-1', 'replace').decode('latin-1')
|
||||
if maxlen and maxlen > 0:
|
||||
text = text[:maxlen]
|
||||
elif maxlen and maxlen < 0:
|
||||
text = text[maxlen:]
|
||||
return text
|
||||
|
||||
def format_phone(number):
|
||||
if not number:
|
||||
return False
|
||||
number = number.replace(' ', '').replace('/', '').replace('.', '')
|
||||
if len(number) > 4 and len(number) < 13:
|
||||
return format_alphanumeric(number)
|
||||
return False
|
||||
|
||||
def format_address(street, street2, maxlen=60):
|
||||
street, street2 = street or '', street2 or ''
|
||||
if street and len(street) >= maxlen:
|
||||
street2 = ''
|
||||
sep = ' ' if street and street2 else ''
|
||||
return format_alphanumeric(f"{street}{sep}{street2}", maxlen)
|
||||
|
||||
formato_trasmissione = "FPA12" if self._is_commercial_partner_pa() else "FPR12"
|
||||
|
||||
# Flags
|
||||
is_self_invoice = self.env['account.edi.format']._l10n_it_edi_is_self_invoice(self)
|
||||
document_type = self.env['account.edi.format']._l10n_it_get_document_type(self)
|
||||
if self.env['account.edi.format']._l10n_it_is_simplified_document_type(document_type):
|
||||
formato_trasmissione = "FSM10"
|
||||
|
||||
# Represent if the document is a reverse charge refund in a single variable
|
||||
reverse_charge = document_type in ['TD17', 'TD18', 'TD19']
|
||||
is_downpayment = document_type in ['TD02']
|
||||
reverse_charge_refund = self.move_type == 'in_refund' and reverse_charge
|
||||
convert_to_euros = self.currency_id.name != 'EUR'
|
||||
|
||||
# b64encode returns a bytestring, the template tries to turn it to string,
|
||||
# but only gets the repr(pdf) --> "b'<base64_data>'"
|
||||
pdf = self.env['ir.actions.report']._render_qweb_pdf("account.account_invoices", self.id)[0]
|
||||
pdf = base64.b64encode(pdf).decode()
|
||||
pdf_name = re.sub(r'\W+', '', self.name) + '.pdf'
|
||||
|
||||
tax_details = self._prepare_edi_tax_details(filter_to_apply=self._l10n_it_edi_filter_fatturapa_tax_details)
|
||||
|
||||
company = self.company_id
|
||||
partner = self.commercial_partner_id
|
||||
sender = company
|
||||
buyer = partner if not is_self_invoice else company
|
||||
seller = company if not is_self_invoice else partner
|
||||
sender_info_values = company.partner_id._l10n_it_edi_get_values()
|
||||
buyer_info_values = (partner if not is_self_invoice else company.partner_id)._l10n_it_edi_get_values()
|
||||
seller_info_values = (company.partner_id if not is_self_invoice else partner)._l10n_it_edi_get_values()
|
||||
representative_info_values = company.l10n_it_tax_representative_partner_id._l10n_it_edi_get_values()
|
||||
|
||||
# Self-invoices are technically -100%/+100% repartitioned
|
||||
# but functionally need to be exported as 100%
|
||||
document_total = self.amount_total
|
||||
if is_self_invoice:
|
||||
document_total += sum([abs(v['tax_amount_currency']) for k, v in tax_details['tax_details'].items()])
|
||||
if reverse_charge_refund:
|
||||
document_total = -abs(document_total)
|
||||
split_payment_amount = self._get_l10n_it_amount_split_payment()
|
||||
if split_payment_amount:
|
||||
document_total += split_payment_amount
|
||||
|
||||
# Reference line for finding the conversion rate used in the document
|
||||
conversion_line = self.invoice_line_ids.sorted(lambda l: abs(l.balance), reverse=True)[0] if self.invoice_line_ids else None
|
||||
conversion_rate = float_repr(
|
||||
abs(conversion_line.balance / conversion_line.amount_currency), precision_digits=5,
|
||||
) if convert_to_euros and conversion_line else None
|
||||
|
||||
invoice_lines = self._l10n_it_edi_prepare_fatturapa_line_details(reverse_charge_refund, is_downpayment, convert_to_euros)
|
||||
tax_lines = self._l10n_it_edi_prepare_fatturapa_tax_details(tax_details)
|
||||
|
||||
# Reduce downpayment views to a single recordset
|
||||
downpayment_moves = [l.get('downpayment_moves', self.env['account.move']) for l in invoice_lines]
|
||||
downpayment_moves = self.browse(move.id for moves in downpayment_moves for move in moves)
|
||||
|
||||
# Create file content.
|
||||
template_values = {
|
||||
'record': self,
|
||||
'company': company,
|
||||
'partner': partner,
|
||||
'sender': sender,
|
||||
'buyer': buyer,
|
||||
'seller': seller,
|
||||
'representative': company.l10n_it_tax_representative_partner_id,
|
||||
'sender_info': sender_info_values,
|
||||
'buyer_info': buyer_info_values,
|
||||
'seller_info': seller_info_values,
|
||||
'representative_info': representative_info_values,
|
||||
'origin_document_type': False, # see module l10n_it_edi_pa, will be merged in master
|
||||
'currency': self.currency_id or self.company_currency_id if not convert_to_euros else self.env.ref('base.EUR'),
|
||||
'document_total': document_total,
|
||||
'regime_fiscale': company.l10n_it_tax_system if not is_self_invoice else 'RF18',
|
||||
'is_self_invoice': is_self_invoice,
|
||||
'partner_bank': self.partner_bank_id,
|
||||
'discount_type': discount_type,
|
||||
'formato_trasmissione': formato_trasmissione,
|
||||
'document_type': document_type,
|
||||
'pdf': pdf,
|
||||
'pdf_name': pdf_name,
|
||||
'tax_details': tax_details,
|
||||
'downpayment_moves': downpayment_moves,
|
||||
'rc_refund': reverse_charge_refund,
|
||||
'invoice_lines': invoice_lines,
|
||||
'tax_lines': tax_lines,
|
||||
'conversion_rate': conversion_rate,
|
||||
'balance_multiplicator': -1 if self.is_inbound() else 1,
|
||||
'abs': abs,
|
||||
'format_date': format_date,
|
||||
'format_monetary': format_monetary,
|
||||
'format_numbers': format_numbers,
|
||||
'format_numbers_two': format_numbers_two,
|
||||
'format_phone': format_phone,
|
||||
'format_alphanumeric': format_alphanumeric,
|
||||
'format_address': format_address,
|
||||
}
|
||||
return template_values
|
||||
|
||||
def _post(self, soft=True):
|
||||
# OVERRIDE
|
||||
posted = super()._post(soft=soft)
|
||||
return posted
|
||||
|
||||
def _compose_info_message(self, tree, element_tags):
|
||||
output_str = ""
|
||||
elements = tree.xpath(element_tags)
|
||||
for element in elements:
|
||||
output_str += "<ul>"
|
||||
for line in element.iter():
|
||||
if line.text:
|
||||
text = " ".join(line.text.split())
|
||||
if text:
|
||||
output_str += "<li>%s: %s</li>" % (line.tag, text)
|
||||
output_str += "</ul>"
|
||||
return output_str
|
||||
|
||||
def _compose_multi_info_message(self, tree, element_tags):
|
||||
output_str = "<ul>"
|
||||
|
||||
for element_tag in element_tags:
|
||||
elements = tree.xpath(element_tag)
|
||||
if not elements:
|
||||
continue
|
||||
for element in elements:
|
||||
text = " ".join(element.text.split())
|
||||
if text:
|
||||
output_str += "<li>%s: %s</li>" % (element.tag, text)
|
||||
return output_str + "</ul>"
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Import invoice
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
@api.model
|
||||
def _import_invoice_fattura_pa(self, invoice, file_data, new=False):
|
||||
tree_list = file_data['xml_tree'].xpath('//FatturaElettronicaBody')
|
||||
if not tree_list:
|
||||
return
|
||||
|
||||
self.env['account.edi.format']._import_fattura_pa(tree_list[0], invoice)
|
||||
|
||||
# there might be other invoices inside the EDI.
|
||||
for tree in tree_list[1:]:
|
||||
other_invoice = invoice.create({
|
||||
'journal_id': invoice.journal_id.id,
|
||||
})
|
||||
with other_invoice._get_edi_creation() as other_invoice:
|
||||
self.env['account.edi.format']._import_fattura_pa(tree, other_invoice)
|
||||
other_invoice.message_post(body=escape(_("Created from attachment in %s")) % invoice._get_html_link())
|
||||
|
||||
return True
|
||||
|
||||
def _get_edi_decoder(self, file_data, new=False):
|
||||
# EXTENDS 'account'
|
||||
def is_fattura_pa(filename): # TODO: maybe check on etree?
|
||||
return re.search('[A-Z]{2}[A-Za-z0-9]{2,28}_[A-Za-z0-9]{0,5}.((?i:xml.p7m|xml))', filename)
|
||||
|
||||
if file_data['type'] in ('xml', 'xml_p7m') and is_fattura_pa(file_data['filename']):
|
||||
return self._import_invoice_fattura_pa
|
||||
|
||||
return super()._get_edi_decoder(file_data, new=new)
|
||||
|
||||
class AccountTax(models.Model):
|
||||
_name = "account.tax"
|
||||
_inherit = "account.tax"
|
||||
|
||||
l10n_it_vat_due_date = fields.Selection([
|
||||
("I", "[I] IVA ad esigibilità immediata"),
|
||||
("D", "[D] IVA ad esigibilità differita"),
|
||||
("S", "[S] Scissione dei pagamenti")], default="I", string="VAT due date")
|
||||
|
||||
l10n_it_has_exoneration = fields.Boolean(string="Has exoneration of tax (Italy)", help="Tax has a tax exoneration.")
|
||||
l10n_it_kind_exoneration = fields.Selection(selection=[
|
||||
("N1", "[N1] Escluse ex art. 15"),
|
||||
("N2", "[N2] Non soggette"),
|
||||
("N2.1", "[N2.1] Non soggette ad IVA ai sensi degli artt. Da 7 a 7-septies del DPR 633/72"),
|
||||
("N2.2", "[N2.2] Non soggette – altri casi"),
|
||||
("N3", "[N3] Non imponibili"),
|
||||
("N3.1", "[N3.1] Non imponibili – esportazioni"),
|
||||
("N3.2", "[N3.2] Non imponibili – cessioni intracomunitarie"),
|
||||
("N3.3", "[N3.3] Non imponibili – cessioni verso San Marino"),
|
||||
("N3.4", "[N3.4] Non imponibili – operazioni assimilate alle cessioni all’esportazione"),
|
||||
("N3.5", "[N3.5] Non imponibili – a seguito di dichiarazioni d’intento"),
|
||||
("N3.6", "[N3.6] Non imponibili – altre operazioni che non concorrono alla formazione del plafond"),
|
||||
("N4", "[N4] Esenti"),
|
||||
("N5", "[N5] Regime del margine / IVA non esposta in fattura"),
|
||||
("N6", "[N6] Inversione contabile (per le operazioni in reverse charge ovvero nei casi di autofatturazione per acquisti extra UE di servizi ovvero per importazioni di beni nei soli casi previsti)"),
|
||||
("N6.1", "[N6.1] Inversione contabile – cessione di rottami e altri materiali di recupero"),
|
||||
("N6.2", "[N6.2] Inversione contabile – cessione di oro e argento puro"),
|
||||
("N6.3", "[N6.3] Inversione contabile – subappalto nel settore edile"),
|
||||
("N6.4", "[N6.4] Inversione contabile – cessione di fabbricati"),
|
||||
("N6.5", "[N6.5] Inversione contabile – cessione di telefoni cellulari"),
|
||||
("N6.6", "[N6.6] Inversione contabile – cessione di prodotti elettronici"),
|
||||
("N6.7", "[N6.7] Inversione contabile – prestazioni comparto edile esettori connessi"),
|
||||
("N6.8", "[N6.8] Inversione contabile – operazioni settore energetico"),
|
||||
("N6.9", "[N6.9] Inversione contabile – altri casi"),
|
||||
("N7", "[N7] IVA assolta in altro stato UE (prestazione di servizi di telecomunicazioni, tele-radiodiffusione ed elettronici ex art. 7-octies, comma 1 lett. a, b, art. 74-sexies DPR 633/72)")],
|
||||
string="Exoneration",
|
||||
help="Exoneration type",
|
||||
default="N1")
|
||||
l10n_it_law_reference = fields.Char(string="Law Reference", size=100)
|
||||
|
||||
@api.constrains('l10n_it_has_exoneration',
|
||||
'l10n_it_kind_exoneration',
|
||||
'l10n_it_law_reference',
|
||||
'amount',
|
||||
'invoice_repartition_line_ids',
|
||||
'refund_repartition_line_ids')
|
||||
def _check_exoneration_with_no_tax(self):
|
||||
for tax in self:
|
||||
if tax.l10n_it_has_exoneration:
|
||||
if not tax.l10n_it_kind_exoneration or not tax.l10n_it_law_reference or tax.amount != 0:
|
||||
raise ValidationError(_("If the tax has exoneration, you must enter a kind of exoneration, a law reference and the amount of the tax must be 0.0."))
|
||||
if tax.l10n_it_kind_exoneration == 'N6' and tax._l10n_it_is_split_payment():
|
||||
raise UserError(_("Split Payment is not compatible with exoneration of kind 'N6'"))
|
||||
|
||||
def _l10n_it_filter_kind(self, kind):
|
||||
""" This can be overridden by l10n_it_edi_withholding for different kind of taxes (withholding, pension_fund)."""
|
||||
return self if kind == 'vat' else self.env['account.tax']
|
||||
|
||||
def _l10n_it_is_split_payment(self):
|
||||
""" Split payment means that the Public Administration buyer will pay VAT
|
||||
to the tax agency instead of the vendor
|
||||
"""
|
||||
self.ensure_one()
|
||||
|
||||
tax_tags = self.get_tax_tags(is_refund=False, repartition_type='tax')
|
||||
if not tax_tags:
|
||||
return False
|
||||
|
||||
it_tax_report_ve38_lines = self.env['account.report.line'].search([
|
||||
('report_id.country_id.code', '=', 'IT'),
|
||||
('code', '=', 'VE38'),
|
||||
])
|
||||
if not it_tax_report_ve38_lines:
|
||||
return False
|
||||
|
||||
ve38_lines_tags = it_tax_report_ve38_lines.expression_ids._get_matching_tags()
|
||||
return bool(tax_tags & ve38_lines_tags)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,88 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import ValidationError, UserError
|
||||
|
||||
class AccountTax(models.Model):
|
||||
_inherit = "account.tax"
|
||||
|
||||
l10n_it_vat_due_date = fields.Selection(
|
||||
selection=[
|
||||
("I", "[I] IVA ad esigibilità immediata"),
|
||||
("D", "[D] IVA ad esigibilità differita"),
|
||||
("S", "[S] Scissione dei pagamenti")],
|
||||
default="I",
|
||||
string="VAT due date",
|
||||
)
|
||||
l10n_it_has_exoneration = fields.Boolean(string="Has exoneration of tax (Italy)", help="Tax has a tax exoneration.")
|
||||
l10n_it_kind_exoneration = fields.Selection(
|
||||
selection=[
|
||||
("N1", "[N1] Escluse ex art. 15"),
|
||||
("N2", "[N2] Non soggette"),
|
||||
("N2.1", "[N2.1] Non soggette ad IVA ai sensi degli artt. Da 7 a 7-septies del DPR 633/72"),
|
||||
("N2.2", "[N2.2] Non soggette - altri casi"),
|
||||
("N3", "[N3] Non imponibili"),
|
||||
("N3.1", "[N3.1] Non imponibili - esportazioni"),
|
||||
("N3.2", "[N3.2] Non imponibili - cessioni intracomunitarie"),
|
||||
("N3.3", "[N3.3] Non imponibili - cessioni verso San Marino"),
|
||||
("N3.4", "[N3.4] Non imponibili - operazioni assimilate alle cessioni all'esportazione"),
|
||||
("N3.5", "[N3.5] Non imponibili - a seguito di dichiarazioni d'intento"),
|
||||
("N3.6", "[N3.6] Non imponibili - altre operazioni che non concorrono alla formazione del plafond"),
|
||||
("N4", "[N4] Esenti"),
|
||||
("N5", "[N5] Regime del margine / IVA non esposta in fattura"),
|
||||
("N6", "[N6] Inversione contabile (per le operazioni in reverse charge ovvero nei casi di autofatturazione per acquisti extra UE di servizi ovvero per importazioni di beni nei soli casi previsti)"),
|
||||
("N6.1", "[N6.1] Inversione contabile - cessione di rottami e altri materiali di recupero"),
|
||||
("N6.2", "[N6.2] Inversione contabile - cessione di oro e argento puro"),
|
||||
("N6.3", "[N6.3] Inversione contabile - subappalto nel settore edile"),
|
||||
("N6.4", "[N6.4] Inversione contabile - cessione di fabbricati"),
|
||||
("N6.5", "[N6.5] Inversione contabile - cessione di telefoni cellulari"),
|
||||
("N6.6", "[N6.6] Inversione contabile - cessione di prodotti elettronici"),
|
||||
("N6.7", "[N6.7] Inversione contabile - prestazioni comparto edile esettori connessi"),
|
||||
("N6.8", "[N6.8] Inversione contabile - operazioni settore energetico"),
|
||||
("N6.9", "[N6.9] Inversione contabile - altri casi"),
|
||||
("N7", "[N7] IVA assolta in altro stato UE (prestazione di servizi di telecomunicazioni, tele-radiodiffusione ed elettronici ex art. 7-octies, comma 1 lett. a, b, art. 74-sexies DPR 633/72)")
|
||||
],
|
||||
string="Exoneration",
|
||||
help="Exoneration type",
|
||||
default="N1",
|
||||
)
|
||||
l10n_it_law_reference = fields.Char(string="Law Reference", size=100)
|
||||
|
||||
@api.constrains('l10n_it_has_exoneration',
|
||||
'l10n_it_kind_exoneration',
|
||||
'l10n_it_law_reference',
|
||||
'amount',
|
||||
'invoice_repartition_line_ids',
|
||||
'refund_repartition_line_ids')
|
||||
def _l10n_it_edi_check_exoneration_with_no_tax(self):
|
||||
for tax in self:
|
||||
if tax.l10n_it_has_exoneration:
|
||||
if not tax.l10n_it_kind_exoneration or not tax.l10n_it_law_reference or tax.amount != 0:
|
||||
raise ValidationError(_("If the tax has exoneration, you must enter a kind of exoneration, a law reference and the amount of the tax must be 0.0."))
|
||||
if tax.l10n_it_kind_exoneration == 'N6' and tax._l10n_it_is_split_payment():
|
||||
raise UserError(_("Split Payment is not compatible with exoneration of kind 'N6'"))
|
||||
|
||||
def _l10n_it_filter_kind(self, kind):
|
||||
""" This can be overridden by l10n_it_edi_withholding for different kind of taxes (withholding, pension_fund)."""
|
||||
return self if kind == 'vat' else self.env['account.tax']
|
||||
|
||||
def _l10n_it_is_split_payment(self):
|
||||
""" Split payment means that the Public Administration buyer will pay VAT
|
||||
to the tax agency instead of the vendor
|
||||
"""
|
||||
self.ensure_one()
|
||||
|
||||
tax_tags = self.get_tax_tags(is_refund=False, repartition_type='tax')
|
||||
if not tax_tags:
|
||||
return False
|
||||
|
||||
it_tax_report_ve38_lines = self.env['account.report.line'].search([
|
||||
('report_id.country_id.code', '=', 'IT'),
|
||||
('code', '=', 'VE38'),
|
||||
])
|
||||
if not it_tax_report_ve38_lines:
|
||||
return False
|
||||
|
||||
ve38_lines_tags = it_tax_report_ve38_lines.expression_ids._get_matching_tags()
|
||||
return bool(tax_tags & ve38_lines_tags)
|
||||
@@ -1,49 +1,60 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import api, models
|
||||
from odoo.addons.l10n_it_edi.tools.remove_signature import remove_signature
|
||||
|
||||
from lxml import etree
|
||||
import logging
|
||||
import re
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
FATTURAPA_FILENAME_RE = "[A-Z]{2}[A-Za-z0-9]{2,28}_[A-Za-z0-9]{0,5}.((?i:xml.p7m|xml))"
|
||||
|
||||
|
||||
class IrAttachment(models.Model):
|
||||
_inherit = 'ir.attachment'
|
||||
|
||||
@api.model
|
||||
def _decode_edi_xml_p7m(self, filename, content):
|
||||
"""Decodes an xml.p7m into a list of one dictionary representing an attachment.
|
||||
:returns: A list with a dictionary.
|
||||
def _decode_edi_l10n_it_edi(self, name, content):
|
||||
""" Decodes a into a list of one dictionary representing an attachment.
|
||||
:returns: A list with a dictionary.
|
||||
"""
|
||||
decoded_content = remove_signature(content)
|
||||
if not decoded_content:
|
||||
return
|
||||
if not (decoded_content := remove_signature(content)):
|
||||
return []
|
||||
|
||||
parser = etree.XMLParser(recover=True, resolve_entities=False)
|
||||
try:
|
||||
parser = etree.XMLParser(recover=True)
|
||||
xml_tree = etree.fromstring(decoded_content, parser)
|
||||
except Exception as e:
|
||||
except etree.ParseError as e:
|
||||
_logger.exception("Error when converting the xml content to etree: %s", e)
|
||||
return []
|
||||
|
||||
to_process = []
|
||||
if xml_tree is not None:
|
||||
to_process.append({
|
||||
'filename': filename,
|
||||
'content': content,
|
||||
'xml_tree': xml_tree,
|
||||
'type': 'xml_p7m',
|
||||
'sort_weight': 11,
|
||||
})
|
||||
return to_process
|
||||
return [{
|
||||
'filename': name,
|
||||
'content': content,
|
||||
'xml_tree': xml_move_tree,
|
||||
'type': 'l10n_it_edi',
|
||||
'sort_weight': 11,
|
||||
} for xml_move_tree in xml_tree.xpath('//FatturaElettronicaBody')]
|
||||
|
||||
def _is_l10n_it_edi_import_file(self):
|
||||
is_xml = (
|
||||
self.name.endswith('.xml')
|
||||
or self.mimetype.endswith('/xml')
|
||||
or 'text/plain' in self.mimetype
|
||||
and self.raw
|
||||
and self.raw.startswith(b'<?xml'))
|
||||
is_p7m = self.mimetype == 'application/pkcs7-mime'
|
||||
return (is_xml or is_p7m) and re.search(FATTURAPA_FILENAME_RE, self.name)
|
||||
|
||||
@api.model
|
||||
def _get_edi_supported_formats(self):
|
||||
""" XML files could be l10n_it_edi related or not, so check it
|
||||
before demanding the decoding to the the standard XML methods.
|
||||
"""
|
||||
# EXTENDS 'account'
|
||||
decoders = super()._get_edi_supported_formats()
|
||||
decoders.append({
|
||||
'check': lambda attachment: attachment.name.endswith('.xml.p7m'),
|
||||
'decoder': self._decode_edi_xml_p7m,
|
||||
})
|
||||
return decoders
|
||||
return [{
|
||||
'format': 'l10n_it_edi',
|
||||
'check': lambda a: a._is_l10n_it_edi_import_file(),
|
||||
'decoder': self._decode_edi_l10n_it_edi,
|
||||
}] + super()._get_edi_supported_formats()
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
@@ -33,6 +32,10 @@ class ResCompany(models.Model):
|
||||
store=True, readonly=False, help="Fiscal code of your company")
|
||||
l10n_it_tax_system = fields.Selection(selection=TAX_SYSTEM, string="Tax System",
|
||||
help="Please select the Tax system to which you are subjected.")
|
||||
l10n_it_edi_proxy_user_id = fields.Many2one(
|
||||
comodel_name="account_edi_proxy_client.user",
|
||||
compute="_compute_l10n_it_edi_proxy_user_id",
|
||||
)
|
||||
|
||||
# Economic and Administrative Index
|
||||
l10n_it_has_eco_index = fields.Boolean(default=False,
|
||||
@@ -107,3 +110,9 @@ class ResCompany(models.Model):
|
||||
raise ValidationError(_("Your tax representative partner must have a tax number."))
|
||||
if not record.l10n_it_tax_representative_partner_id.country_id:
|
||||
raise ValidationError(_("Your tax representative partner must have a country."))
|
||||
|
||||
@api.depends("account_edi_proxy_client_ids")
|
||||
def _compute_l10n_it_edi_proxy_user_id(self):
|
||||
self.ensure_one()
|
||||
for company in self:
|
||||
company.l10n_it_edi_proxy_user_id = company.account_edi_proxy_client_ids.filtered(lambda x: x.proxy_type == 'l10n_it_edi')
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, models, fields, _
|
||||
@@ -9,30 +8,33 @@ class ResConfigSettings(models.TransientModel):
|
||||
|
||||
is_edi_proxy_active = fields.Boolean(compute='_compute_is_edi_proxy_active')
|
||||
l10n_it_edi_proxy_current_state = fields.Char(compute='_compute_l10n_it_edi_proxy_current_state')
|
||||
l10n_it_edi_sdicoop_register = fields.Boolean(compute='_compute_l10n_it_edi_sdicoop_register', inverse='_set_l10n_it_edi_sdicoop_register_demo_mode')
|
||||
l10n_it_edi_sdicoop_demo_mode = fields.Selection(
|
||||
l10n_it_edi_register = fields.Boolean(compute='_compute_l10n_it_edi_register', inverse='_set_l10n_it_edi_register_demo_mode')
|
||||
l10n_it_edi_demo_mode = fields.Selection(
|
||||
[('demo', 'Demo'),
|
||||
('test', 'Test (experimental)'),
|
||||
('prod', 'Official')],
|
||||
compute='_compute_l10n_it_edi_sdicoop_demo_mode',
|
||||
inverse='_set_l10n_it_edi_sdicoop_register_demo_mode',
|
||||
compute='_compute_l10n_it_edi_demo_mode',
|
||||
inverse='_set_l10n_it_edi_register_demo_mode',
|
||||
readonly=False)
|
||||
|
||||
def _create_proxy_user(self, company_id, edi_mode):
|
||||
self.env['account_edi_proxy_client.user']._register_proxy_user(company_id, 'l10n_it_edi', edi_mode)
|
||||
|
||||
def button_create_proxy_user(self):
|
||||
self._create_proxy_user(self.company_id, self.l10n_it_edi_demo_mode)
|
||||
|
||||
@api.depends('company_id.account_edi_proxy_client_ids', 'company_id.account_edi_proxy_client_ids.active')
|
||||
def _compute_l10n_it_edi_sdicoop_demo_mode(self):
|
||||
def _compute_l10n_it_edi_demo_mode(self):
|
||||
for config in self:
|
||||
edi_user = self.env['account_edi_proxy_client.user'].search([
|
||||
('company_id', '=', config.company_id.id),
|
||||
('proxy_type', '=', 'l10n_it_edi'),
|
||||
], limit=1)
|
||||
config.l10n_it_edi_sdicoop_demo_mode = edi_user.edi_mode
|
||||
config.l10n_it_edi_demo_mode = edi_user.edi_mode
|
||||
|
||||
def _set_l10n_it_edi_sdicoop_demo_mode(self):
|
||||
def _set_l10n_it_edi_demo_mode(self):
|
||||
for config in self:
|
||||
self.env['ir.config_parameter'].set_param('account_edi_proxy_client.demo', config.l10n_it_edi_sdicoop_demo_mode)
|
||||
self.env['ir.config_parameter'].set_param('account_edi_proxy_client.demo', config.l10n_it_edi_demo_mode)
|
||||
|
||||
@api.depends('company_id.account_edi_proxy_client_ids', 'company_id.account_edi_proxy_client_ids.active')
|
||||
def _compute_is_edi_proxy_active(self):
|
||||
@@ -50,19 +52,11 @@ class ResConfigSettings(models.TransientModel):
|
||||
config.l10n_it_edi_proxy_current_state = 'inactive' if not proxy_user else 'demo' if proxy_user.id_client[:4] == 'demo' else 'active'
|
||||
|
||||
@api.depends('company_id')
|
||||
def _compute_l10n_it_edi_sdicoop_register(self):
|
||||
def _compute_l10n_it_edi_register(self):
|
||||
"""Needed because it expects a compute"""
|
||||
self.l10n_it_edi_sdicoop_register = False
|
||||
self.l10n_it_edi_register = False
|
||||
|
||||
def button_create_proxy_user(self):
|
||||
fattura_pa = self.env.ref('l10n_it_edi.edi_fatturaPA')
|
||||
edi_identification = fattura_pa._get_proxy_identification(self.company_id)
|
||||
if not edi_identification:
|
||||
return
|
||||
|
||||
self.env['account_edi_proxy_client.user']._register_proxy_user(self.company_id, 'l10n_it_edi', self.l10n_it_edi_sdicoop_demo_mode, edi_identification)
|
||||
|
||||
def _set_l10n_it_edi_sdicoop_register_demo_mode(self):
|
||||
def _set_l10n_it_edi_register_demo_mode(self):
|
||||
for config in self:
|
||||
|
||||
proxy_user = self.env['account_edi_proxy_client.user'].search([
|
||||
@@ -76,14 +70,14 @@ class ResConfigSettings(models.TransientModel):
|
||||
|
||||
# Update the config as per the selected radio button
|
||||
previous_demo_state = proxy_user.edi_mode
|
||||
edi_mode = config.l10n_it_edi_sdicoop_demo_mode
|
||||
edi_mode = config.l10n_it_edi_demo_mode
|
||||
self.env['ir.config_parameter'].set_param('account_edi_proxy_client.demo', edi_mode)
|
||||
# If the user is trying to change from a state in which they have a registered official or testing proxy client
|
||||
# to another state, we should stop them
|
||||
if real_proxy_users and previous_demo_state != edi_mode:
|
||||
raise UserError(_("The company has already registered with the service as 'Test' or 'Official', it cannot change."))
|
||||
|
||||
if config.l10n_it_edi_sdicoop_register:
|
||||
if config.l10n_it_edi_register:
|
||||
# There should only be one user at a time, if there are no users, register one
|
||||
if not proxy_user:
|
||||
self._create_proxy_user(config.company_id, edi_mode)
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import re
|
||||
from stdnum.it import codicefiscale, iva
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
import re
|
||||
|
||||
|
||||
class ResPartner(models.Model):
|
||||
_name = 'res.partner'
|
||||
@@ -14,12 +14,14 @@ class ResPartner(models.Model):
|
||||
|
||||
l10n_it_pec_email = fields.Char(string="PEC e-mail")
|
||||
l10n_it_codice_fiscale = fields.Char(string="Codice Fiscale", size=16)
|
||||
l10n_it_pa_index = fields.Char(string="Destination Code",
|
||||
l10n_it_pa_index = fields.Char(
|
||||
string="Destination Code",
|
||||
size=7,
|
||||
help="Must contain the 6-character (or 7) code, present in the PA\
|
||||
Index in the information relative to the electronic invoicing service,\
|
||||
associated with the office which, within the addressee administration, deals\
|
||||
with receiving (and processing) the invoice.")
|
||||
help="Must contain the 6-character (or 7) code, present in the PA Index "
|
||||
"in the information relative to the electronic invoicing service, "
|
||||
"associated with the office which, within the addressee administration, deals "
|
||||
"with receiving (and processing) the invoice.",
|
||||
)
|
||||
|
||||
_sql_constraints = [
|
||||
('l10n_it_codice_fiscale',
|
||||
@@ -31,6 +33,11 @@ class ResPartner(models.Model):
|
||||
"Destination Code must have between 6 and 7 characters."),
|
||||
]
|
||||
|
||||
def _l10n_it_edi_is_public_administration(self):
|
||||
""" Returns True if the destination of the FatturaPA belongs to the Public Administration. """
|
||||
self.ensure_one()
|
||||
return len(self.l10n_it_pa_index or '') == 6
|
||||
|
||||
|
||||
def _l10n_it_edi_get_values(self):
|
||||
""" Generates all partner values needed by l10n_it_edi XML export.
|
||||
@@ -122,7 +129,7 @@ class ResPartner(models.Model):
|
||||
If the Tax Code is equal to the Italian VAT, it may mistakenly have the country prefix,
|
||||
so we try and remove it if we can
|
||||
"""
|
||||
if not l10n_it_codice_fiscale:
|
||||
if l10n_it_codice_fiscale is None:
|
||||
self.ensure_one()
|
||||
l10n_it_codice_fiscale = self.l10n_it_codice_fiscale
|
||||
if l10n_it_codice_fiscale and re.match(r'^IT[0-9]{11}$', l10n_it_codice_fiscale):
|
||||
|
||||
@@ -5,14 +5,14 @@ from lxml import etree
|
||||
|
||||
from odoo import tools
|
||||
from odoo.tests import tagged
|
||||
from odoo.addons.account_edi.tests.common import AccountEdiTestCommon
|
||||
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
|
||||
|
||||
@tagged('post_install_l10n', 'post_install', '-at_install')
|
||||
class TestItEdi(AccountEdiTestCommon):
|
||||
class TestItEdi(AccountTestInvoicingCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls, chart_template_ref='it', edi_format_ref="l10n_it_edi.edi_fatturaPA"):
|
||||
super().setUpClass(chart_template_ref=chart_template_ref, edi_format_ref=edi_format_ref)
|
||||
def setUpClass(cls, chart_template_ref='it'):
|
||||
super().setUpClass(chart_template_ref=chart_template_ref)
|
||||
|
||||
# Company data ------
|
||||
cls.company = cls.company_data_2['company']
|
||||
@@ -104,24 +104,14 @@ class TestItEdi(AccountEdiTestCommon):
|
||||
path = f'{self.module}/tests/export_xmls/{filename}'
|
||||
with tools.file_open(path, mode='rb') as fd:
|
||||
expected_tree = etree.fromstring(fd.read())
|
||||
invoice_etree = etree.fromstring(self.edi_format._l10n_it_edi_export_invoice_as_xml(invoice))
|
||||
xml = invoice._l10n_it_edi_render_xml()
|
||||
invoice_etree = etree.fromstring(xml)
|
||||
try:
|
||||
self.assertXmlTreeEqual(invoice_etree, expected_tree)
|
||||
except AssertionError as ae:
|
||||
ae.args = (ae.args[0] + f"\nFile used for comparison: {filename}", )
|
||||
raise
|
||||
|
||||
def _cleanup_etree(self, content, xpaths=None):
|
||||
xpaths = {
|
||||
**(xpaths or {}),
|
||||
'//FatturaElettronicaBody/Allegati': 'Allegati',
|
||||
'//DatiTrasmissione/ProgressivoInvio': 'ProgressivoInvio',
|
||||
}
|
||||
return self.with_applied_xpath(
|
||||
etree.fromstring(content),
|
||||
"".join([f"<xpath expr='{x}' position='replace'>{y}</xpath>" for x, y in xpaths.items()])
|
||||
)
|
||||
|
||||
def _assert_import_invoice(self, filename, expected_values_list, xml_to_apply=None):
|
||||
path = f'{self.module}/tests/import_xmls/{filename}'
|
||||
with tools.file_open(path, mode='rb') as fd:
|
||||
@@ -138,9 +128,8 @@ class TestItEdi(AccountEdiTestCommon):
|
||||
'name': filename,
|
||||
'raw': import_content,
|
||||
})
|
||||
invoices = self.company_data_2['default_journal_purchase']\
|
||||
.with_context(default_move_type='in_invoice')\
|
||||
._create_document_from_attachment(attachment.ids)
|
||||
purchase_journal = self.company_data_2['default_journal_purchase'].with_context(default_move_type='in_invoice')
|
||||
invoices = purchase_journal._create_document_from_attachment(attachment.ids)
|
||||
|
||||
expected_invoice_values_list = []
|
||||
expected_invoice_line_ids_values_list = []
|
||||
|
||||
@@ -106,10 +106,5 @@
|
||||
<ImportoPagamento>1600.80</ImportoPagamento>
|
||||
</DettaglioPagamento>
|
||||
</DatiPagamento>
|
||||
<Allegati>
|
||||
<NomeAttachment>BILL2022030001.pdf</NomeAttachment>
|
||||
<FormatoAttachment>PDF</FormatoAttachment>
|
||||
<Attachment>___ignore___</Attachment>
|
||||
</Allegati>
|
||||
</FatturaElettronicaBody>
|
||||
</p:FatturaElettronica>
|
||||
|
||||
@@ -102,10 +102,5 @@
|
||||
<ImportoPagamento>1600.80</ImportoPagamento>
|
||||
</DettaglioPagamento>
|
||||
</DatiPagamento>
|
||||
<Allegati>
|
||||
<NomeAttachment>BILL2022030001.pdf</NomeAttachment>
|
||||
<FormatoAttachment>PDF</FormatoAttachment>
|
||||
<Attachment>___ignore___</Attachment>
|
||||
</Allegati>
|
||||
</FatturaElettronicaBody>
|
||||
</p:FatturaElettronica>
|
||||
|
||||
@@ -102,10 +102,5 @@
|
||||
<ImportoPagamento>1600.80</ImportoPagamento>
|
||||
</DettaglioPagamento>
|
||||
</DatiPagamento>
|
||||
<Allegati>
|
||||
<NomeAttachment>BILL2022030001.pdf</NomeAttachment>
|
||||
<FormatoAttachment>PDF</FormatoAttachment>
|
||||
<Attachment>___ignore___</Attachment>
|
||||
</Allegati>
|
||||
</FatturaElettronicaBody>
|
||||
</p:FatturaElettronica>
|
||||
|
||||
@@ -107,10 +107,5 @@
|
||||
<EsigibilitaIVA>I</EsigibilitaIVA>
|
||||
</DatiRiepilogo>
|
||||
</DatiBeniServizi>
|
||||
<Allegati>
|
||||
<NomeAttachment>RINV202200001.pdf</NomeAttachment>
|
||||
<FormatoAttachment>PDF</FormatoAttachment>
|
||||
<Attachment>___ignore___</Attachment>
|
||||
</Allegati>
|
||||
</FatturaElettronicaBody>
|
||||
</p:FatturaElettronica>
|
||||
|
||||
@@ -106,10 +106,5 @@
|
||||
<ImportoPagamento>1600.80</ImportoPagamento>
|
||||
</DettaglioPagamento>
|
||||
</DatiPagamento>
|
||||
<Allegati>
|
||||
<NomeAttachment>RBILL2022030001.pdf</NomeAttachment>
|
||||
<FormatoAttachment>PDF</FormatoAttachment>
|
||||
<Attachment>___ignore___</Attachment>
|
||||
</Allegati>
|
||||
</FatturaElettronicaBody>
|
||||
</p:FatturaElettronica>
|
||||
|
||||
@@ -63,10 +63,5 @@
|
||||
<Imposta>4.40</Imposta>
|
||||
</DatiIVA>
|
||||
</DatiBeniServizi>
|
||||
<Allegati>
|
||||
<NomeAttachment>INV202200001.pdf</NomeAttachment>
|
||||
<FormatoAttachment>PDF</FormatoAttachment>
|
||||
<Attachment>___ignore___</Attachment>
|
||||
</Allegati>
|
||||
</FatturaElettronicaBody>
|
||||
</p:FatturaElettronicaSemplificata>
|
||||
|
||||
@@ -94,10 +94,5 @@
|
||||
<CodicePagamento>INV/2022/00001</CodicePagamento>
|
||||
</DettaglioPagamento>
|
||||
</DatiPagamento>
|
||||
<Allegati>
|
||||
<NomeAttachment>INV202200001.pdf</NomeAttachment>
|
||||
<FormatoAttachment>PDF</FormatoAttachment>
|
||||
<Attachment>___ignore___</Attachment>
|
||||
</Allegati>
|
||||
</FatturaElettronicaBody>
|
||||
</p:FatturaElettronica>
|
||||
|
||||
@@ -112,10 +112,5 @@
|
||||
<CodicePagamento>INV/2022/00001</CodicePagamento>
|
||||
</DettaglioPagamento>
|
||||
</DatiPagamento>
|
||||
<Allegati>
|
||||
<NomeAttachment>INV202200001.pdf</NomeAttachment>
|
||||
<FormatoAttachment>PDF</FormatoAttachment>
|
||||
<Attachment>___ignore___</Attachment>
|
||||
</Allegati>
|
||||
</FatturaElettronicaBody>
|
||||
</p:FatturaElettronica>
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
</DettaglioLinee>
|
||||
<DettaglioLinee>
|
||||
<NumeroLinea>2</NumeroLinea>
|
||||
<Descrizione>?-</Descrizione>
|
||||
<Descrizione>--</Descrizione>
|
||||
<Quantita>1.00</Quantita>
|
||||
<PrezzoUnitario>800.400000</PrezzoUnitario>
|
||||
<PrezzoTotale>800.40</PrezzoTotale>
|
||||
@@ -106,10 +106,5 @@
|
||||
<CodicePagamento>INV/2022/00001</CodicePagamento>
|
||||
</DettaglioPagamento>
|
||||
</DatiPagamento>
|
||||
<Allegati>
|
||||
<NomeAttachment>INV202200001.pdf</NomeAttachment>
|
||||
<FormatoAttachment>PDF</FormatoAttachment>
|
||||
<Attachment>___ignore___</Attachment>
|
||||
</Allegati>
|
||||
</FatturaElettronicaBody>
|
||||
</p:FatturaElettronica>
|
||||
|
||||
@@ -114,10 +114,5 @@
|
||||
<CodicePagamento>INV/2022/00001</CodicePagamento>
|
||||
</DettaglioPagamento>
|
||||
</DatiPagamento>
|
||||
<Allegati>
|
||||
<NomeAttachment>INV202200001.pdf</NomeAttachment>
|
||||
<FormatoAttachment>PDF</FormatoAttachment>
|
||||
<Attachment>___ignore___</Attachment>
|
||||
</Allegati>
|
||||
</FatturaElettronicaBody>
|
||||
</p:FatturaElettronica>
|
||||
|
||||
@@ -113,10 +113,5 @@
|
||||
<CodicePagamento>INV/2022/00001</CodicePagamento>
|
||||
</DettaglioPagamento>
|
||||
</DatiPagamento>
|
||||
<Allegati>
|
||||
<NomeAttachment>INV202200001.pdf</NomeAttachment>
|
||||
<FormatoAttachment>PDF</FormatoAttachment>
|
||||
<Attachment>___ignore___</Attachment>
|
||||
</Allegati>
|
||||
</FatturaElettronicaBody>
|
||||
</p:FatturaElettronica>
|
||||
|
||||
@@ -101,10 +101,5 @@
|
||||
<CodicePagamento>___ignore___</CodicePagamento>
|
||||
</DettaglioPagamento>
|
||||
</DatiPagamento>
|
||||
<Allegati>
|
||||
<NomeAttachment>INV202200001.pdf</NomeAttachment>
|
||||
<FormatoAttachment>PDF</FormatoAttachment>
|
||||
<Attachment>___ignore___</Attachment>
|
||||
</Allegati>
|
||||
</FatturaElettronicaBody>
|
||||
</p:FatturaElettronica>
|
||||
|
||||
@@ -53,10 +53,5 @@
|
||||
<Imposta>72.13</Imposta>
|
||||
</DatiIVA>
|
||||
</DatiBeniServizi>
|
||||
<Allegati>
|
||||
<NomeAttachment>INV202200001.pdf</NomeAttachment>
|
||||
<FormatoAttachment>PDF</FormatoAttachment>
|
||||
<Attachment>___ignore___</Attachment>
|
||||
</Allegati>
|
||||
</FatturaElettronicaBody>
|
||||
</p:FatturaElettronicaSemplificata>
|
||||
|
||||
@@ -102,10 +102,5 @@
|
||||
<CodicePagamento>INV/2022/00001</CodicePagamento>
|
||||
</DettaglioPagamento>
|
||||
</DatiPagamento>
|
||||
<Allegati>
|
||||
<NomeAttachment>INV202200001.pdf</NomeAttachment>
|
||||
<FormatoAttachment>PDF</FormatoAttachment>
|
||||
<Attachment>___ignore___</Attachment>
|
||||
</Allegati>
|
||||
</FatturaElettronicaBody>
|
||||
</p:FatturaElettronica>
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from odoo import Command
|
||||
from odoo.tests import tagged
|
||||
from odoo.addons.l10n_it_edi.tests.common import TestItEdi
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
@tagged('post_install_l10n', 'post_install', '-at_install')
|
||||
@@ -145,7 +141,7 @@ class TestItEdiExport(TestItEdi):
|
||||
'tax_ids': [Command.set(self.default_tax.ids)],
|
||||
}),
|
||||
Command.create({
|
||||
'name': '–-',
|
||||
'name': '--',
|
||||
'price_unit': 800.40,
|
||||
'tax_ids': [Command.set(self.default_tax.ids)],
|
||||
}),
|
||||
@@ -214,8 +210,9 @@ class TestItEdiExport(TestItEdi):
|
||||
],
|
||||
})
|
||||
|
||||
with self.assertRaises(UserError):
|
||||
invoice.action_post()
|
||||
expected = ['Alessi must have a street.', 'Alessi must have a country.', 'Alessi must have a post code.', 'Alessi must have a city.']
|
||||
actual = invoice._l10n_it_edi_export_data_check()
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
def test_invoice_non_domestic_simplified(self):
|
||||
invoice = self.env['account.move'].with_company(self.company).create({
|
||||
@@ -231,9 +228,9 @@ class TestItEdiExport(TestItEdi):
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
with self.assertRaises(UserError):
|
||||
invoice.action_post()
|
||||
expected = ['Alessi must have a street.', 'Alessi must have a post code.', 'Alessi must have a city.']
|
||||
actual = invoice._l10n_it_edi_export_data_check()
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
def test_invoice_zero_percent_taxes(self):
|
||||
tax_zero_percent_hundred_percent_repartition = self.env['account.tax'].with_company(self.company).create({
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from unittest.mock import MagicMock, patch
|
||||
import uuid
|
||||
from unittest.mock import patch
|
||||
|
||||
from odoo import fields, sql_db
|
||||
from odoo import fields, sql_db, tools
|
||||
from odoo.tests import tagged
|
||||
from odoo.addons.l10n_it_edi.tests.common import TestItEdi
|
||||
|
||||
@@ -73,28 +74,40 @@ class TestItEdiImport(TestItEdi):
|
||||
def test_receive_same_vendor_bill_twice(self):
|
||||
""" Test that the second time we are receiving an SdiCoop invoice, the second is discarded """
|
||||
|
||||
fattura_pa = self.env.ref('l10n_it_edi.edi_fatturaPA')
|
||||
content = self.fake_test_content.encode()
|
||||
|
||||
# Our test content is not encrypted
|
||||
proxy_user = MagicMock()
|
||||
proxy_user.company_id = self.company
|
||||
proxy_user._decrypt_data.return_value = content
|
||||
ProxyUser = self.env['account_edi_proxy_client.user']
|
||||
proxy_user = ProxyUser.create({
|
||||
'company_id': self.company.id,
|
||||
'proxy_type': 'l10n_it_edi',
|
||||
'id_client': str(uuid.uuid4()),
|
||||
'edi_identification': ProxyUser._get_proxy_identification(self.company, 'l10n_it_edi'),
|
||||
'private_key': str(uuid.uuid4()),
|
||||
})
|
||||
|
||||
filename = 'IT01234567890_FPR02.xml'
|
||||
|
||||
def mock_commit(self):
|
||||
pass
|
||||
|
||||
with patch.object(sql_db.Cursor, "commit", mock_commit):
|
||||
with (patch.object(proxy_user.__class__, '_decrypt_data', return_value=self.fake_test_content),
|
||||
patch.object(sql_db.Cursor, "commit", mock_commit),
|
||||
tools.mute_logger("odoo.addons.l10n_it_edi.models.account_move")):
|
||||
for dummy in range(2):
|
||||
fattura_pa._save_incoming_attachment_fattura_pa(
|
||||
proxy_user=proxy_user,
|
||||
id_transaction='9999999999',
|
||||
filename='IT01234567890_FPR02.xml',
|
||||
content=content,
|
||||
key=None)
|
||||
self.env['account.move']._l10n_it_edi_process_downloads({
|
||||
'999999999': {
|
||||
'filename': filename,
|
||||
'file': self.fake_test_content,
|
||||
'key': str(uuid.uuid4()),
|
||||
}},
|
||||
proxy_user,
|
||||
)
|
||||
|
||||
# There should be one attachement with this filename
|
||||
attachments = self.env['ir.attachment'].search([('name', '=', 'IT01234567890_FPR02.xml')])
|
||||
attachments = self.env['ir.attachment'].search([
|
||||
('name', '=', 'IT01234567890_FPR02.xml'),
|
||||
('res_model', '=', 'account.move'),
|
||||
('res_field', '=', 'l10n_it_edi_attachment_file'),
|
||||
])
|
||||
self.assertEqual(len(attachments), 1)
|
||||
invoices = self.env['account.move'].search([('payment_reference', '=', 'TWICE_TEST')])
|
||||
self.assertEqual(len(invoices), 1)
|
||||
|
||||
@@ -39,7 +39,7 @@ def remove_signature(content):
|
||||
try:
|
||||
loaded_data = ssl_crypto.load_pkcs7_data(ssl_crypto.FILETYPE_ASN1, content)
|
||||
except ssl_crypto.Error:
|
||||
_logger.warning("Error reading the content, PKCS#7 signature missing or invalid. Content will be tentatively used as it is.")
|
||||
_logger.debug("PKCS#7 signature missing or invalid. Content will be tentatively used as plain text.")
|
||||
return content
|
||||
|
||||
# Verify the signature
|
||||
|
||||
@@ -91,6 +91,7 @@
|
||||
<field name="state" position="before">
|
||||
<field name="l10n_it_edi_transaction" optional="hide"/>
|
||||
<field name="l10n_it_edi_attachment_id" optional="hide"/>
|
||||
<field name="l10n_it_edi_state" optional="hide"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
@@ -98,48 +99,12 @@
|
||||
<record id="view_account_invoice_filter" model="ir.ui.view">
|
||||
<field name="name">account.invoice.select.inherit</field>
|
||||
<field name="model">account.move</field>
|
||||
<field name="inherit_id" ref="account_edi.view_account_invoice_filter"/>
|
||||
<field name="inherit_id" ref="account.view_account_invoice_filter"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//search/field[@name='journal_id']" position="after">
|
||||
<field name="l10n_it_edi_transaction" groups="base.group_no_one"/>
|
||||
<field name="l10n_it_edi_attachment_id" groups="base.group_no_one"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_in_bill_tree_inherit" model="ir.ui.view">
|
||||
<field name="name">account.move.tree.inherit</field>
|
||||
<field name="model">account.move</field>
|
||||
<field name="inherit_id" ref="account.view_in_invoice_bill_tree" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="state" position="before">
|
||||
<field name="edi_state" optional="hide"/>
|
||||
<field name="edi_blocking_level" optional="hide"/>
|
||||
<field name="edi_error_message" optional="hide"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_out_invoice_tree_inherit" model="ir.ui.view">
|
||||
<field name="name">account.move.tree.inherit</field>
|
||||
<field name="model">account.move</field>
|
||||
<field name="inherit_id" ref="account_edi.view_out_invoice_tree_inherit" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="state" position="before">
|
||||
<field name="l10n_it_edi_transaction" optional="hide" invisible="1"/>
|
||||
<field name="l10n_it_edi_attachment_id" optional="hide" invisible="1"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_account_invoice_filter" model="ir.ui.view">
|
||||
<field name="name">account.invoice.select.inherit</field>
|
||||
<field name="model">account.move</field>
|
||||
<field name="inherit_id" ref="account_edi.view_account_invoice_filter"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//search/field[@name='journal_id']" position="after">
|
||||
<field name="l10n_it_edi_transaction" groups="base.group_no_one" invisible="1"/>
|
||||
<field name="l10n_it_edi_attachment_id" groups="base.group_no_one" invisible="1"/>
|
||||
<field name="l10n_it_edi_state" groups="base.group_no_one"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
@@ -150,23 +115,56 @@
|
||||
<field name="priority">20</field>
|
||||
<field name="inherit_id" ref="account.view_move_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<data>
|
||||
<xpath expr="//page[@name='other_info']" position="after">
|
||||
<page string="Electronic Invoicing"
|
||||
name="electronic_invoicing"
|
||||
invisible="move_type not in ('out_invoice', 'out_refund', 'in_invoice', 'in_refund') or country_code != 'IT'">
|
||||
<group>
|
||||
<data>
|
||||
<xpath expr="//header" position="inside">
|
||||
<button name="action_l10n_it_edi_send"
|
||||
type="object"
|
||||
string="Send Tax Integration"
|
||||
invisible="state != 'posted' or not l10n_it_edi_is_self_invoice or is_move_sent or country_code != 'IT'"
|
||||
data-hotkey="y"/>
|
||||
</xpath>
|
||||
<xpath expr="//sheet" position="before">
|
||||
<field name="l10n_it_edi_is_self_invoice" invisible="1"/>
|
||||
<field name="l10n_it_edi_attachment_id" invisible="1"/>
|
||||
<div class="alert alert-warning" role="alert"
|
||||
invisible="not l10n_it_edi_header
|
||||
or state == 'draft'
|
||||
or l10n_it_edi_state in (False, 'forwarded', 'accepted_by_pa_partner', 'accepted_by_pa_partner_after_expiry', 'forward_failed')">
|
||||
<div class="p-0 m-0"><i class='fa fa-warning' role="img" title="EDI (Italy)"/><span class="mx-1">E-invoicing (Italy)</span></div>
|
||||
<field name="l10n_it_edi_header"/>
|
||||
</div>
|
||||
</xpath>
|
||||
<xpath expr="//button[@name='action_invoice_sent']" position="before">
|
||||
<button
|
||||
name="action_check_l10n_it_edi"
|
||||
type="object"
|
||||
string="Check Sending"
|
||||
class="oe_highlight"
|
||||
data-hotkey="c"
|
||||
invisible="l10n_it_edi_state not in ('processing', 'forward_attempt')"
|
||||
/>
|
||||
</xpath>
|
||||
<xpath expr="//page[@name='other_info']" position="after">
|
||||
<page string="Electronic Invoicing"
|
||||
name="electronic_invoicing"
|
||||
invisible="move_type not in ('out_invoice', 'out_refund', 'in_invoice', 'in_refund') or country_code != 'IT'">
|
||||
<group>
|
||||
<field name="l10n_it_edi_transaction" groups="base.group_no_one" readonly="1"/>
|
||||
<field name="l10n_it_stamp_duty" readonly="state != 'draft'"/>
|
||||
<field name="l10n_it_ddt_id"
|
||||
invisible="move_type not in ('out_invoice', 'out_refund')"
|
||||
readonly="state != 'draft'"/>
|
||||
<group>
|
||||
<field name="l10n_it_edi_transaction" groups="base.group_no_one" readonly="1"/>
|
||||
<field name="l10n_it_edi_attachment_id" groups="base.group_no_one" readonly="1"/>
|
||||
<field name="l10n_it_stamp_duty" readonly="state != 'draft'"/>
|
||||
<field name="l10n_it_ddt_id" readonly="state != 'draft'" invisible="move_type not in ('out_invoice', 'out_refund')"/>
|
||||
</group>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
</xpath>
|
||||
</data>
|
||||
</page>
|
||||
</xpath>
|
||||
<xpath expr="//div[@name='journal_div']" position="after">
|
||||
<label for="l10n_it_edi_state" invisible="not l10n_it_edi_state"/>
|
||||
<div name="l10n_it_edi_div" class="d-flex" invisible="not l10n_it_edi_state">
|
||||
<field name="l10n_it_edi_state" class="oe_inline"/>
|
||||
</div>
|
||||
</xpath>
|
||||
</data>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
@@ -21,24 +21,24 @@
|
||||
Saving this change will direct all companies on this database to this use this configuration.
|
||||
Once registered for testing or official, the mode cannot be changed.
|
||||
</div>
|
||||
<field name="l10n_it_edi_sdicoop_demo_mode"
|
||||
<field name="l10n_it_edi_demo_mode"
|
||||
widget="radio"
|
||||
options="{'horizontal': true}"/>
|
||||
</div>
|
||||
<div class="mt8 content-group" invisible="l10n_it_edi_proxy_current_state == 'active' or l10n_it_edi_proxy_current_state == 'demo' and l10n_it_edi_sdicoop_demo_mode == 'demo'">
|
||||
<div class="mt8 content-group" invisible="l10n_it_edi_proxy_current_state == 'active' or l10n_it_edi_proxy_current_state == 'demo' and l10n_it_edi_demo_mode == 'demo'">
|
||||
<span class="o_form_label">Allow Odoo to process invoices</span>
|
||||
<div class="text-muted">
|
||||
By checking this box, I accept that Odoo may process my invoices.
|
||||
</div>
|
||||
<div class="content-group">
|
||||
<field name="l10n_it_edi_sdicoop_register"/>
|
||||
<field name="l10n_it_edi_register"/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="text-success mt8" invisible="l10n_it_edi_proxy_current_state in ['inactive', 'demo']">
|
||||
An Official or Test service has been registered.
|
||||
</div>
|
||||
<div class="text-success mt8" invisible="l10n_it_edi_proxy_current_state != 'demo' or l10n_it_edi_sdicoop_demo_mode != 'demo'">
|
||||
<div class="text-success mt8" invisible="l10n_it_edi_proxy_current_state != 'demo' or l10n_it_edi_demo_mode != 'demo'">
|
||||
A Demo service is in use.
|
||||
</div>
|
||||
</setting>
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import account_move_send
|
||||
|
||||
@@ -1,12 +1,102 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import models
|
||||
from odoo import _, api, fields, models
|
||||
|
||||
|
||||
class AccountMoveSend(models.Model):
|
||||
_inherit = 'account.move.send'
|
||||
|
||||
def _get_mail_attachment_from_doc(self, doc):
|
||||
if doc.edi_format_id.code == 'fattura_pa':
|
||||
return self.env['ir.attachment']
|
||||
return super()._get_mail_attachment_from_doc(doc)
|
||||
l10n_it_edi_enable_send = fields.Boolean(compute='_compute_send_mail_extra_fields')
|
||||
l10n_it_edi_checkbox_send = fields.Boolean('Tax Agency (Italy)', compute='_compute_l10n_it_edi_checkbox_send',
|
||||
store=True, readonly=False, help=(
|
||||
"Send the invoice to the Italian Tax Agency.\n"
|
||||
"It is set as readonly if a report has already been created, to avoid inconsistencies.\n"
|
||||
"To re-enable it, delete the PDF attachment."))
|
||||
l10n_it_edi_readonly = fields.Boolean(compute='_compute_send_mail_extra_fields')
|
||||
l10n_it_edi_warning_message = fields.Html(compute='_compute_send_mail_extra_fields')
|
||||
|
||||
def _get_available_field_values_in_multi(self, move):
|
||||
# EXTENDS 'account'
|
||||
values = super()._get_available_field_values_in_multi(move)
|
||||
values['l10n_it_edi_checkbox_send'] = self.l10n_it_edi_checkbox_send and self._get_default_l10n_it_edi_enable_send(move)
|
||||
return values
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# COMPUTE/CONSTRAINS METHODS
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
@api.depends('l10n_it_edi_enable_send')
|
||||
def _compute_send_mail_extra_fields(self):
|
||||
# EXTENDS account
|
||||
super()._compute_send_mail_extra_fields()
|
||||
for wizard in self:
|
||||
wizard.l10n_it_edi_enable_send = any(
|
||||
wizard._get_default_l10n_it_edi_enable_send(m)
|
||||
and not m.l10n_it_edi_attachment_id
|
||||
for m in wizard.move_ids)
|
||||
|
||||
if not wizard.company_id.l10n_it_edi_proxy_user_id:
|
||||
wizard.l10n_it_edi_warning_message = _("You must accept the terms and conditions in the Settings to use the IT EDI.")
|
||||
else:
|
||||
wizard.l10n_it_edi_warning_message = wizard.move_ids._l10n_it_edi_format_export_data_errors()
|
||||
|
||||
already_has_pdf = any(wizard.move_ids.mapped("invoice_pdf_report_id"))
|
||||
already_has_xml = any(x._is_l10n_it_edi_import_file() for x in wizard.move_ids.mapped("attachment_ids"))
|
||||
wizard.l10n_it_edi_readonly = wizard.l10n_it_edi_warning_message or already_has_pdf or already_has_xml
|
||||
|
||||
@api.depends('l10n_it_edi_readonly', 'l10n_it_edi_enable_send')
|
||||
def _compute_l10n_it_edi_checkbox_send(self):
|
||||
for wizard in self:
|
||||
wizard.l10n_it_edi_checkbox_send = wizard.l10n_it_edi_enable_send and not wizard.l10n_it_edi_readonly
|
||||
|
||||
@api.depends('move_ids')
|
||||
def _get_default_l10n_it_edi_enable_send(self, move):
|
||||
return (
|
||||
move.company_id.account_fiscal_country_id.code == 'IT'
|
||||
and move.journal_id.type == 'sale'
|
||||
and move.l10n_it_edi_state in (False, 'rejected')
|
||||
)
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# BUSINESS ACTIONS
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
@api.model
|
||||
def _get_invoice_extra_attachments(self, move):
|
||||
# EXTENDS 'account'
|
||||
return super()._get_invoice_extra_attachments(move) + move.l10n_it_edi_attachment_id
|
||||
|
||||
def _hook_invoice_document_before_pdf_report_render(self, invoice, invoice_data):
|
||||
# EXTENDS 'account'
|
||||
super()._hook_invoice_document_before_pdf_report_render(invoice, invoice_data)
|
||||
if self.l10n_it_edi_checkbox_send and self._get_default_l10n_it_edi_enable_send(invoice):
|
||||
if errors := invoice._l10n_it_edi_export_data_check():
|
||||
message = _("Errors occured while creating the e-invoice file.")
|
||||
message += "\n- " + "\n- ".join(errors)
|
||||
invoice_data['error'] = message
|
||||
|
||||
def _hook_invoice_document_after_pdf_report_render(self, invoice, invoice_data):
|
||||
# EXTENDS 'account'
|
||||
super()._hook_invoice_document_after_pdf_report_render(invoice, invoice_data)
|
||||
if self.l10n_it_edi_checkbox_send and self._get_default_l10n_it_edi_enable_send(invoice):
|
||||
invoice_data['l10n_it_edi_values'] = invoice._l10n_it_edi_get_attachment_values(
|
||||
pdf_values=invoice_data['pdf_attachment_values'])
|
||||
|
||||
def _call_web_service_after_invoice_pdf_render(self, invoices_data):
|
||||
# EXTENDS 'account'
|
||||
super()._call_web_service_after_invoice_pdf_render(invoices_data)
|
||||
if self.l10n_it_edi_checkbox_send:
|
||||
attachments_vals = {}
|
||||
moves = self.env['account.move']
|
||||
for move in invoices_data:
|
||||
if self._get_default_l10n_it_edi_enable_send(move):
|
||||
moves |= move
|
||||
attachments_vals[move] = invoices_data[move]['l10n_it_edi_values']
|
||||
moves._l10n_it_edi_send(attachments_vals)
|
||||
|
||||
def _link_invoice_documents(self, invoice, invoice_data):
|
||||
# EXTENDS 'account'
|
||||
super()._link_invoice_documents(invoice, invoice_data)
|
||||
if attachment_vals := invoice_data.get('l10n_it_edi_values'):
|
||||
self.env['ir.attachment'].sudo().create(attachment_vals)
|
||||
invoice.invalidate_recordset(fnames=['l10n_it_edi_attachment_id', 'l10n_it_edi_attachment_file'])
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<odoo>
|
||||
<data>
|
||||
<record model="ir.ui.view" id="account_move_send_inherit_l10n_it_edi">
|
||||
<field name="name">account.move.send.form.inherit.l10n_it_edi</field>
|
||||
<field name="model">account.move.send</field>
|
||||
<field name="inherit_id" ref="account.account_move_send_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[@name='warnings']" position="inside">
|
||||
<field name="l10n_it_edi_readonly" invisible="1"/>
|
||||
<field name="l10n_it_edi_enable_send" invisible="1"/>
|
||||
<span invisible="not l10n_it_edi_warning_message">
|
||||
<b>Tax Agency (Italy)</b>
|
||||
</span>
|
||||
<div class="alert alert-warning mb-8" role="alert" invisible="not l10n_it_edi_warning_message">
|
||||
<field name="l10n_it_edi_warning_message"/>
|
||||
</div>
|
||||
</xpath>
|
||||
<xpath expr="//div[@name='option_send_mail']" position='after'>
|
||||
<div name="option_l10n_it_edi" invisible="not l10n_it_edi_enable_send">
|
||||
<field name="l10n_it_edi_checkbox_send" readonly="l10n_it_edi_readonly"/>
|
||||
<b><label for="l10n_it_edi_checkbox_send"/></b>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -1,11 +1,8 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import datetime
|
||||
from lxml import etree
|
||||
from odoo.addons.l10n_it_edi.tests.common import TestItEdi
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tests.common import tagged
|
||||
from odoo import tools
|
||||
from odoo import Command
|
||||
|
||||
@tagged('post_install_l10n', 'post_install', '-at_install')
|
||||
|
||||
@@ -16,7 +16,9 @@ def _l10n_it_edi_withholding_post_init(env):
|
||||
'account.tax',
|
||||
]
|
||||
}
|
||||
env['account.chart.template']._deref_account_tags(template_code, data['account.tax'])
|
||||
for company in env['res.company'].search([('chart_template', '=', 'it')]):
|
||||
for company in env['res.company'].search([('chart_template', '=', template_code)]):
|
||||
_logger.info("Company %s already has the Italian localization installed, updating...", company.name)
|
||||
env['account.chart.template'].with_company(company)._load_data(data)
|
||||
company_chart_template = env['account.chart.template'].with_company(company)
|
||||
company_data = dict(data)
|
||||
company_chart_template._deref_account_tags(template_code, company_data['account.tax'])
|
||||
company_chart_template._load_data(company_data)
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<AliquotaIVA t-esc="format_numbers(pension_fund.vat_tax.amount or 0.0)"/>
|
||||
<Ritenuta t-if="pension_fund.withholding_tax and pension_fund.withholding_tax.sequence > pension_fund.tax.sequence">SI</Ritenuta>
|
||||
<Natura t-if="pension_fund.tax.l10n_it_has_exoneration" t-esc="format_alphanumeric(pension_fund.tax.l10n_it_kind_exoneration)"/>
|
||||
<RiferimentoAmministrazione t-if="pension_fund.tax.description" t-esc="format_alphanumeric(pension_fund.tax.description)[:20]"/>
|
||||
<RiferimentoAmministrazione t-if="pension_fund.tax.description" t-esc="format_alphanumeric(pension_fund.tax.description, 20)"/>
|
||||
</DatiCassaPrevidenziale>
|
||||
</t>
|
||||
</xpath>
|
||||
|
||||
@@ -4,4 +4,3 @@
|
||||
from . import account_tax
|
||||
from . import account_chart_template
|
||||
from . import account_move
|
||||
from . import account_edi_format
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import models, _
|
||||
import logging
|
||||
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AccountEdiFormat(models.Model):
|
||||
_inherit = 'account.edi.format'
|
||||
|
||||
def _l10n_it_edi_search_tax_for_import(self, company, percentage, extra_domain=None, vat_only=True):
|
||||
""" In case no withholding_type or pension_fund is specified, exclude taxes that have it.
|
||||
It means that we're searching for VAT taxes, especially in the base l10n_it_edi module
|
||||
"""
|
||||
if vat_only:
|
||||
extra_domain += [('l10n_it_withholding_type', '=', False), ('l10n_it_pension_fund_type', '=', False)]
|
||||
return super()._l10n_it_edi_search_tax_for_import(company, percentage, extra_domain)
|
||||
|
||||
def _l10n_it_edi_check_taxes_configuration(self, invoice):
|
||||
"""
|
||||
Override to also allow pension_fund, withholding taxes.
|
||||
Needs not to call super, because super checks for one tax only per line.
|
||||
"""
|
||||
errors = []
|
||||
for invoice_line in invoice.invoice_line_ids.filtered(lambda x: x.display_type == 'product'):
|
||||
all_taxes = invoice_line.tax_ids.flatten_taxes_hierarchy()
|
||||
vat_taxes, withholding_taxes, pension_fund_taxes = (all_taxes._l10n_it_filter_kind(kind) for kind in ('vat', 'withholding', 'pension_fund'))
|
||||
if len(vat_taxes.filtered(lambda x: x.amount >= 0)) != 1:
|
||||
errors.append(_("Bad tax configuration for line %s, there must be one and only one VAT tax per line", invoice_line.name))
|
||||
if len(pension_fund_taxes) > 1 or len(withholding_taxes) > 1:
|
||||
errors.append(_("Bad tax configuration for line %s, there must be one Withholding tax and one Pension Fund tax at max.", invoice_line.name))
|
||||
return errors
|
||||
|
||||
def _l10n_it_edi_get_extra_info(self, company, document_type, body_tree):
|
||||
extra_info, message_to_log = super()._l10n_it_edi_get_extra_info(company, document_type, body_tree)
|
||||
|
||||
withholding_elements = body_tree.xpath('.//DatiGeneraliDocumento/DatiRitenuta')
|
||||
withholding_taxes = []
|
||||
for withholding in (withholding_elements or []):
|
||||
tipo_ritenuta = withholding.find("TipoRitenuta")
|
||||
reason = withholding.find("CausalePagamento")
|
||||
percentage = withholding.find('AliquotaRitenuta')
|
||||
withholding_type = tipo_ritenuta.text if tipo_ritenuta is not None else "RT02"
|
||||
withholding_reason = reason.text if reason is not None else "A"
|
||||
withholding_percentage = -float(percentage.text if percentage is not None else "0.0")
|
||||
withholding_tax = self._l10n_it_edi_search_tax_for_import(
|
||||
company,
|
||||
withholding_percentage,
|
||||
[('l10n_it_withholding_type', '=', withholding_type),
|
||||
('l10n_it_withholding_reason', '=', withholding_reason)],
|
||||
vat_only=False)
|
||||
if withholding_tax:
|
||||
withholding_taxes.append(withholding_tax)
|
||||
else:
|
||||
message_to_log.append("%s<br/>%s" % (
|
||||
_("Withholding tax not found"),
|
||||
self.env['account.move']._compose_info_message(body_tree, '.'),
|
||||
))
|
||||
extra_info["withholding_taxes"] = withholding_taxes
|
||||
|
||||
pension_fund_elements = body_tree.xpath('.//DatiGeneraliDocumento/DatiCassaPrevidenziale')
|
||||
pension_fund_taxes = []
|
||||
for pension_fund in (pension_fund_elements or []):
|
||||
pension_fund_type = pension_fund.find("TipoCassa")
|
||||
tax_factor_percent = pension_fund.find("AlCassa")
|
||||
vat_tax_factor_percent = pension_fund.find("AliquotaIVA")
|
||||
pension_fund_type = pension_fund_type.text if pension_fund_type is not None else ""
|
||||
tax_factor_percent = float(tax_factor_percent.text or "0.0")
|
||||
vat_tax_factor_percent = float(vat_tax_factor_percent.text or "0.0")
|
||||
pension_fund_tax = self._l10n_it_edi_search_tax_for_import(
|
||||
company,
|
||||
tax_factor_percent,
|
||||
[('l10n_it_pension_fund_type', '=', pension_fund_type)],
|
||||
vat_only=False)
|
||||
if pension_fund_tax:
|
||||
pension_fund_taxes.append(pension_fund_tax)
|
||||
else:
|
||||
message_to_log.append("%s<br/>%s" % (
|
||||
_("Pension Fund tax not found"),
|
||||
self.env['account.move']._compose_info_message(body_tree, '.'),
|
||||
))
|
||||
extra_info["pension_fund_taxes"] = pension_fund_taxes
|
||||
|
||||
return extra_info, message_to_log
|
||||
|
||||
def _import_fattura_pa_line(self, element, invoice_line_form, extra_info):
|
||||
messages_to_log = super()._import_fattura_pa_line(element, invoice_line_form, extra_info)
|
||||
|
||||
for withholding_tax in extra_info.get('withholding_taxes', []):
|
||||
withholding_tags = element.xpath("Ritenuta")
|
||||
if withholding_tags and withholding_tags[0].text == 'SI':
|
||||
invoice_line_form.tax_ids |= withholding_tax
|
||||
for pension_fund_tax in extra_info.get('pension_fund_taxes', []):
|
||||
invoice_line_form.tax_ids |= pension_fund_tax
|
||||
|
||||
if extra_info['simplified']:
|
||||
return messages_to_log
|
||||
|
||||
price_subtotal = invoice_line_form.price_unit
|
||||
company = invoice_line_form.company_id
|
||||
|
||||
# ENASARCO Pension Fund tax (works as a withholding)
|
||||
for other_data_element in element.xpath('.//AltriDatiGestionali'):
|
||||
data_kind_element = other_data_element.xpath("./TipoDato")
|
||||
text_element = other_data_element.xpath("./RiferimentoTesto")
|
||||
number_element = other_data_element.xpath("./RiferimentoNumero")
|
||||
if not data_kind_element or not text_element or not number_element:
|
||||
continue
|
||||
data_kind, data_text, number_text = data_kind_element[0].text.lower(), text_element[0].text.lower(), number_element[0].text
|
||||
if data_kind != 'cassa-prev' or ('enasarco' not in data_text and not 'tc07' in data_text):
|
||||
continue
|
||||
enasarco_amount = float(number_text)
|
||||
enasarco_percentage = -self.env.company.currency_id.round(enasarco_amount / price_subtotal * 100)
|
||||
enasarco_tax = self._l10n_it_edi_search_tax_for_import(
|
||||
company,
|
||||
enasarco_percentage,
|
||||
[('l10n_it_pension_fund_type', '=', 'TC07')],
|
||||
vat_only=False)
|
||||
if enasarco_tax:
|
||||
invoice_line_form.tax_ids |= enasarco_tax
|
||||
else:
|
||||
messages_to_log.append("%s<br/>%s" % (
|
||||
_("Enasarco tax not found for line with description '%s'", invoice_line_form.name),
|
||||
self.env['account.move']._compose_info_message(other_data_element, '.'),
|
||||
))
|
||||
|
||||
return messages_to_log
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import logging
|
||||
from collections import namedtuple
|
||||
from odoo import api, fields, models
|
||||
from odoo import _, api, fields, models
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -28,15 +28,15 @@ class AccountMove(models.Model):
|
||||
move.l10n_it_amount_pension_fund_signed = totals['pension_fund']
|
||||
move.l10n_it_amount_before_withholding_signed = move.amount_untaxed_signed + totals['vat'] + totals['pension_fund']
|
||||
|
||||
def _l10n_it_edi_filter_fatturapa_tax_details(self, line, tax_values):
|
||||
def _l10n_it_edi_filter_tax_details(self, line, tax_values):
|
||||
"""Filters tax details to only include the positive amounted lines regarding VAT taxes."""
|
||||
repartition_line = tax_values['tax_repartition_line']
|
||||
repartition_line_vat = repartition_line.tax_id._l10n_it_filter_kind('vat')
|
||||
return repartition_line.factor_percent >= 0 and repartition_line_vat and repartition_line_vat.amount >= 0
|
||||
|
||||
def _prepare_fatturapa_export_values(self):
|
||||
def _l10n_it_edi_get_values(self, pdf_values=None):
|
||||
"""Add withholding and pension_fund features."""
|
||||
template_values = super()._prepare_fatturapa_export_values()
|
||||
template_values = super()._l10n_it_edi_get_values(pdf_values)
|
||||
|
||||
# Withholding tax data
|
||||
WithholdingTaxData = namedtuple('TaxData', ['tax', 'tax_amount'])
|
||||
@@ -75,7 +75,8 @@ class AccountMove(models.Model):
|
||||
enasarco_values = False
|
||||
if enasarco_taxes:
|
||||
enasarco_values = {}
|
||||
enasarco_details = self._prepare_edi_tax_details(filter_to_apply=lambda line, tax_values: self.env['account.tax'].browse([tax_values['id']]).l10n_it_pension_fund_type == 'TC07')
|
||||
enasarco_details = self._prepare_invoice_aggregated_taxes(
|
||||
filter_tax_values_to_apply=lambda line, tax_values: self.env['account.tax'].browse([tax_values['id']]).l10n_it_pension_fund_type == 'TC07')
|
||||
for detail in enasarco_details['tax_details_per_record'].values():
|
||||
for subdetail in detail['tax_details'].values():
|
||||
# Withholdings are removed from the total, we have to re-add them
|
||||
@@ -94,3 +95,126 @@ class AccountMove(models.Model):
|
||||
'document_total': document_total,
|
||||
})
|
||||
return template_values
|
||||
|
||||
def _l10n_it_edi_export_taxes_data_check(self):
|
||||
"""
|
||||
Override to also allow pension_fund, withholding taxes.
|
||||
Needs not to call super, because super checks for one tax only per line.
|
||||
"""
|
||||
errors = []
|
||||
for invoice_line in self.invoice_line_ids.filtered(lambda x: x.display_type == 'product'):
|
||||
all_taxes = invoice_line.tax_ids.flatten_taxes_hierarchy()
|
||||
vat_taxes, withholding_taxes, pension_fund_taxes = (all_taxes._l10n_it_filter_kind(kind) for kind in
|
||||
('vat', 'withholding', 'pension_fund'))
|
||||
if len(vat_taxes.filtered(lambda x: x.amount >= 0)) != 1:
|
||||
errors.append(_("Bad tax configuration for line %s, there must be one and only one VAT tax per line", invoice_line.name))
|
||||
if len(pension_fund_taxes) > 1 or len(withholding_taxes) > 1:
|
||||
errors.append(_("Bad tax configuration for line %s, there must be one Withholding tax and one Pension Fund tax at max.", invoice_line.name))
|
||||
return errors
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Import
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
def _l10n_it_edi_search_tax_for_import(self, company, percentage, extra_domain=None, vat_only=True):
|
||||
""" In case no withholding_type or pension_fund is specified, exclude taxes that have it.
|
||||
It means that we're searching for VAT taxes, especially in the base l10n_it_edi module
|
||||
"""
|
||||
if vat_only:
|
||||
extra_domain += [('l10n_it_withholding_type', '=', False), ('l10n_it_pension_fund_type', '=', False)]
|
||||
return super()._l10n_it_edi_search_tax_for_import(company, percentage, extra_domain)
|
||||
|
||||
def _l10n_it_edi_get_extra_info(self, company, document_type, body_tree):
|
||||
extra_info, message_to_log = super()._l10n_it_edi_get_extra_info(company, document_type, body_tree)
|
||||
|
||||
withholding_elements = body_tree.xpath('.//DatiGeneraliDocumento/DatiRitenuta')
|
||||
withholding_taxes = []
|
||||
for withholding in (withholding_elements or []):
|
||||
tipo_ritenuta = withholding.find("TipoRitenuta")
|
||||
reason = withholding.find("CausalePagamento")
|
||||
percentage = withholding.find('AliquotaRitenuta')
|
||||
withholding_type = tipo_ritenuta.text if tipo_ritenuta is not None else "RT02"
|
||||
withholding_reason = reason.text if reason is not None else "A"
|
||||
withholding_percentage = -float(percentage.text if percentage is not None else "0.0")
|
||||
withholding_tax = self._l10n_it_edi_search_tax_for_import(
|
||||
company,
|
||||
withholding_percentage,
|
||||
[('l10n_it_withholding_type', '=', withholding_type),
|
||||
('l10n_it_withholding_reason', '=', withholding_reason)],
|
||||
vat_only=False)
|
||||
if withholding_tax:
|
||||
withholding_taxes.append(withholding_tax)
|
||||
else:
|
||||
message_to_log.append("%s<br/>%s" % (
|
||||
_("Withholding tax not found"),
|
||||
self.env['account.move']._compose_info_message(body_tree, '.'),
|
||||
))
|
||||
extra_info["withholding_taxes"] = withholding_taxes
|
||||
|
||||
pension_fund_elements = body_tree.xpath('.//DatiGeneraliDocumento/DatiCassaPrevidenziale')
|
||||
pension_fund_taxes = []
|
||||
for pension_fund in (pension_fund_elements or []):
|
||||
pension_fund_type = pension_fund.find("TipoCassa")
|
||||
tax_factor_percent = pension_fund.find("AlCassa")
|
||||
vat_tax_factor_percent = pension_fund.find("AliquotaIVA")
|
||||
pension_fund_type = pension_fund_type.text if pension_fund_type is not None else ""
|
||||
tax_factor_percent = float(tax_factor_percent.text or "0.0")
|
||||
vat_tax_factor_percent = float(vat_tax_factor_percent.text or "0.0")
|
||||
pension_fund_tax = self._l10n_it_edi_search_tax_for_import(
|
||||
company,
|
||||
tax_factor_percent,
|
||||
[('l10n_it_pension_fund_type', '=', pension_fund_type)],
|
||||
vat_only=False)
|
||||
if pension_fund_tax:
|
||||
pension_fund_taxes.append(pension_fund_tax)
|
||||
else:
|
||||
message_to_log.append("%s<br/>%s" % (
|
||||
_("Pension Fund tax not found"),
|
||||
self.env['account.move']._compose_info_message(body_tree, '.'),
|
||||
))
|
||||
extra_info["pension_fund_taxes"] = pension_fund_taxes
|
||||
|
||||
return extra_info, message_to_log
|
||||
|
||||
def _l10n_it_edi_import_line(self, element, move_line_form, extra_info=None):
|
||||
messages_to_log = super()._l10n_it_edi_import_line(element, move_line_form, extra_info)
|
||||
|
||||
for withholding_tax in extra_info.get('withholding_taxes', []):
|
||||
withholding_tags = element.xpath("Ritenuta")
|
||||
if withholding_tags and withholding_tags[0].text == 'SI':
|
||||
move_line_form.tax_ids |= withholding_tax
|
||||
for pension_fund_tax in extra_info.get('pension_fund_taxes', []):
|
||||
move_line_form.tax_ids |= pension_fund_tax
|
||||
|
||||
if extra_info['simplified']:
|
||||
return messages_to_log
|
||||
|
||||
price_subtotal = move_line_form.price_unit
|
||||
company = move_line_form.company_id
|
||||
|
||||
# ENASARCO Pension Fund tax (works as a withholding)
|
||||
for other_data_element in element.xpath('.//AltriDatiGestionali'):
|
||||
data_kind_element = other_data_element.xpath("./TipoDato")
|
||||
text_element = other_data_element.xpath("./RiferimentoTesto")
|
||||
number_element = other_data_element.xpath("./RiferimentoNumero")
|
||||
if not data_kind_element or not text_element or not number_element:
|
||||
continue
|
||||
data_kind, data_text, number_text = data_kind_element[0].text.lower(), text_element[0].text.lower(), number_element[0].text
|
||||
if data_kind != 'cassa-prev' or ('enasarco' not in data_text and 'tc07' not in data_text):
|
||||
continue
|
||||
enasarco_amount = float(number_text)
|
||||
enasarco_percentage = -self.env.company.currency_id.round(enasarco_amount / price_subtotal * 100)
|
||||
enasarco_tax = self._l10n_it_edi_search_tax_for_import(
|
||||
company,
|
||||
enasarco_percentage,
|
||||
[('l10n_it_pension_fund_type', '=', 'TC07')],
|
||||
vat_only=False)
|
||||
if enasarco_tax:
|
||||
move_line_form.tax_ids |= enasarco_tax
|
||||
else:
|
||||
messages_to_log.append("%s<br/>%s" % (
|
||||
_("Enasarco tax not found for line with description '%s'", move_line_form.name),
|
||||
self.env['account.move']._compose_info_message(other_data_element, '.'),
|
||||
))
|
||||
|
||||
return messages_to_log
|
||||
|
||||
@@ -143,10 +143,5 @@
|
||||
<CodicePagamento>___ignore___</CodicePagamento>
|
||||
</DettaglioPagamento>
|
||||
</DatiPagamento>
|
||||
<Allegati>
|
||||
<NomeAttachment>___ignore___</NomeAttachment>
|
||||
<FormatoAttachment>PDF</FormatoAttachment>
|
||||
<Attachment>___ignore___</Attachment>
|
||||
</Allegati>
|
||||
</FatturaElettronicaBody>
|
||||
</p:FatturaElettronica>
|
||||
|
||||
@@ -130,10 +130,5 @@
|
||||
<CodicePagamento>___ignore___</CodicePagamento>
|
||||
</DettaglioPagamento>
|
||||
</DatiPagamento>
|
||||
<Allegati>
|
||||
<NomeAttachment>___ignore___</NomeAttachment>
|
||||
<FormatoAttachment>PDF</FormatoAttachment>
|
||||
<Attachment>___ignore___</Attachment>
|
||||
</Allegati>
|
||||
</FatturaElettronicaBody>
|
||||
</p:FatturaElettronica>
|
||||
|
||||
@@ -123,10 +123,5 @@
|
||||
<CodicePagamento>___ignore___</CodicePagamento>
|
||||
</DettaglioPagamento>
|
||||
</DatiPagamento>
|
||||
<Allegati>
|
||||
<NomeAttachment>___ignore___</NomeAttachment>
|
||||
<FormatoAttachment>PDF</FormatoAttachment>
|
||||
<Attachment>___ignore___</Attachment>
|
||||
</Allegati>
|
||||
</FatturaElettronicaBody>
|
||||
</p:FatturaElettronica>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import datetime
|
||||
from lxml import etree
|
||||
from collections import namedtuple
|
||||
|
||||
from odoo import fields
|
||||
|
||||
@@ -3,4 +3,3 @@
|
||||
|
||||
from . import stock_picking
|
||||
from . import account_invoice
|
||||
from . import account_edi_format
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import models
|
||||
|
||||
|
||||
class AccountEdiFormat(models.Model):
|
||||
_inherit = 'account.edi.format'
|
||||
|
||||
def _l10n_it_invoice_is_direct(self, invoice):
|
||||
""" An invoice is only direct if the Transport Documents are all done the same day as the invoice. """
|
||||
for ddt in invoice.l10n_it_ddt_ids:
|
||||
if not ddt.date_done or ddt.date_done != invoice.invoice_date:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _l10n_it_get_invoice_features_for_document_type_selection(self, invoice):
|
||||
res = super()._l10n_it_get_invoice_features_for_document_type_selection(invoice)
|
||||
res['direct_invoice'] = self._l10n_it_invoice_is_direct(invoice)
|
||||
return res
|
||||
|
||||
def _l10n_it_document_type_mapping(self):
|
||||
""" Deferred invoices (not direct) require TD24 FatturaPA Document Type. """
|
||||
res = super()._l10n_it_document_type_mapping()
|
||||
for document_type, infos in res.items():
|
||||
if document_type == 'TD07':
|
||||
continue
|
||||
infos['direct_invoice'] = True
|
||||
res['TD24'] = dict(move_types=['out_invoice'], import_type='in_invoice', direct_invoice=False)
|
||||
return res
|
||||
@@ -11,6 +11,28 @@ class AccountMove(models.Model):
|
||||
l10n_it_ddt_ids = fields.Many2many('stock.picking', compute="_compute_ddt_ids")
|
||||
l10n_it_ddt_count = fields.Integer(compute="_compute_ddt_ids")
|
||||
|
||||
def _l10n_it_edi_document_type_mapping(self):
|
||||
""" Deferred invoices (not direct) require TD24 FatturaPA Document Type. """
|
||||
res = super()._l10n_it_edi_document_type_mapping()
|
||||
for document_type, infos in res.items():
|
||||
if document_type == 'TD07':
|
||||
continue
|
||||
infos['direct_invoice'] = True
|
||||
res['TD24'] = {'move_types': ['out_invoice'], 'import_type': 'in_invoice', 'direct_invoice': False}
|
||||
return res
|
||||
|
||||
def _l10n_it_edi_invoice_is_direct(self):
|
||||
""" An invoice is only direct if the Transport Documents are all done the same day as the invoice. """
|
||||
for ddt in self.l10n_it_ddt_ids:
|
||||
if not ddt.date_done or ddt.date_done != self.invoice_date:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _l10n_it_edi_features_for_document_type_selection(self):
|
||||
res = super()._l10n_it_edi_features_for_document_type_selection()
|
||||
res['direct_invoice'] = self._l10n_it_edi_invoice_is_direct()
|
||||
return res
|
||||
|
||||
def _get_ddt_values(self):
|
||||
"""
|
||||
We calculate the link between the invoice lines and the deliveries related to the invoice through the
|
||||
@@ -79,7 +101,7 @@ class AccountMove(models.Model):
|
||||
'domain': [('id', 'in', self.l10n_it_ddt_ids.ids)],
|
||||
}
|
||||
|
||||
def _prepare_fatturapa_export_values(self):
|
||||
template_values = super()._prepare_fatturapa_export_values()
|
||||
def _l10n_it_edi_get_values(self, pdf_values=None):
|
||||
template_values = super()._l10n_it_edi_get_values(pdf_values)
|
||||
template_values['ddt_dict'] = self._get_ddt_values()
|
||||
return template_values
|
||||
|
||||
@@ -127,6 +127,5 @@
|
||||
<CodicePagamento>INV/2020/00001</CodicePagamento>
|
||||
</DettaglioPagamento>
|
||||
</DatiPagamento>
|
||||
<Allegati/>
|
||||
</FatturaElettronicaBody>
|
||||
</p:FatturaElettronica>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import logging
|
||||
from lxml import etree
|
||||
from freezegun import freeze_time
|
||||
from odoo import tools
|
||||
from odoo.tests import tagged, Form
|
||||
@@ -133,10 +134,10 @@ class TestItEdiDDT(TestItEdi):
|
||||
deferred_invoice.action_post()
|
||||
|
||||
# Check the XML output of the invoice
|
||||
invoice_xml = self.edi_format._l10n_it_edi_export_invoice_as_xml(deferred_invoice)
|
||||
invoice_xml = deferred_invoice._l10n_it_edi_render_xml()
|
||||
expected_xml = self._get_stock_ddt_test_file_content("deferred_invoice.xml")
|
||||
result = self._cleanup_etree(invoice_xml, {"//DatiGeneraliDocumento/Numero": "<Numero/>",})
|
||||
expected = self._cleanup_etree(expected_xml, {"//DatiGeneraliDocumento/Numero": "<Numero/>",})
|
||||
result = etree.fromstring(invoice_xml)
|
||||
expected = etree.fromstring(expected_xml)
|
||||
self.assertXmlTreeEqual(result, expected)
|
||||
|
||||
def _create_delivery(self, sale_order, qty=1):
|
||||
|
||||
Reference in New Issue
Block a user