diff --git a/l10n_it_fatturapa_out_rc/__manifest__.py b/l10n_it_fatturapa_out_rc/__manifest__.py index 1ca94dc0cf42..0dd7a500ca0c 100644 --- a/l10n_it_fatturapa_out_rc/__manifest__.py +++ b/l10n_it_fatturapa_out_rc/__manifest__.py @@ -1,9 +1,10 @@ # Copyright 2020 Lorenzo Battistini @ TAKOBI +# Copyright 2021 Alex Comba - Agile Business Group # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). { "name": "ITA - Emissione e-fattura con reverse charge", "summary": "Integrazione l10n_it_fatturapa_out e l10n_it_reverse_charge", - "version": "12.0.1.0.4", + "version": "14.0.1.0.0", "development_status": "Beta", "category": "Hidden", "website": "https://github.com/OCA/l10n-italy", @@ -20,5 +21,6 @@ ], "data": [ "views/rc_type_views.xml", + "views/invoice_it_template.xml", ], } diff --git a/l10n_it_fatturapa_out_rc/i18n/it.po b/l10n_it_fatturapa_out_rc/i18n/it.po index a35f408740bb..5d0c65a8f304 100644 --- a/l10n_it_fatturapa_out_rc/i18n/it.po +++ b/l10n_it_fatturapa_out_rc/i18n/it.po @@ -4,7 +4,7 @@ # msgid "" msgstr "" -"Project-Id-Version: Odoo Server 12.0\n" +"Project-Id-Version: Odoo Server 14.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-12-25 05:47+0000\n" "PO-Revision-Date: 2020-12-25 05:47+0000\n" diff --git a/l10n_it_fatturapa_out_rc/i18n/l10n_it_fatturapa_out_rc.pot b/l10n_it_fatturapa_out_rc/i18n/l10n_it_fatturapa_out_rc.pot index bd4a1ddab4ae..554221902564 100644 --- a/l10n_it_fatturapa_out_rc/i18n/l10n_it_fatturapa_out_rc.pot +++ b/l10n_it_fatturapa_out_rc/i18n/l10n_it_fatturapa_out_rc.pot @@ -4,7 +4,7 @@ # msgid "" msgstr "" -"Project-Id-Version: Odoo Server 12.0\n" +"Project-Id-Version: Odoo Server 14.0\n" "Report-Msgid-Bugs-To: \n" "Last-Translator: <>\n" "Language-Team: \n" diff --git a/l10n_it_fatturapa_out_rc/models/__init__.py b/l10n_it_fatturapa_out_rc/models/__init__.py index b67b8767be29..166e50c64ad5 100644 --- a/l10n_it_fatturapa_out_rc/models/__init__.py +++ b/l10n_it_fatturapa_out_rc/models/__init__.py @@ -1,2 +1,2 @@ from . import rc_type -from . import account_invoice +from . import account_move diff --git a/l10n_it_fatturapa_out_rc/models/account_invoice.py b/l10n_it_fatturapa_out_rc/models/account_invoice.py deleted file mode 100644 index ce59ec091279..000000000000 --- a/l10n_it_fatturapa_out_rc/models/account_invoice.py +++ /dev/null @@ -1,47 +0,0 @@ -from odoo import _, api, models -from odoo.exceptions import UserError - - -class Invoice(models.Model): - _inherit = "account.invoice" - - def generate_self_invoice(self): - res = super(Invoice, self).generate_self_invoice() - if self.rc_self_invoice_id: - rc_type = self.fiscal_position_id.rc_type_id - if rc_type.fiscal_document_type_id: - self.rc_self_invoice_id.fiscal_document_type_id = ( - rc_type.fiscal_document_type_id.id - ) - if self.fatturapa_attachment_in_id: - doc_id = self.fatturapa_attachment_in_id.name - else: - doc_id = self.reference if self.reference else self.number - self.rc_self_invoice_id.related_documents = [ - ( - 0, - 0, - { - "type": "invoice", - "name": doc_id, - "date": self.date_invoice, - }, - ) - ] - return res - - @api.multi - def action_invoice_draft(self): - super().action_invoice_draft() - for inv in self: - if not inv.env.context.get( - "rc_set_to_draft" - ) and inv.rc_purchase_invoice_id.state in ["draft", "cancel"]: - raise UserError( - _( - "Vendor invoice that has generated this self invoice isn't " - "validated. " - "Validate vendor invoice before." - ) - ) - return True diff --git a/l10n_it_fatturapa_out_rc/models/account_move.py b/l10n_it_fatturapa_out_rc/models/account_move.py new file mode 100644 index 000000000000..2bc82ea44d47 --- /dev/null +++ b/l10n_it_fatturapa_out_rc/models/account_move.py @@ -0,0 +1,119 @@ +from odoo import _, models +from odoo.exceptions import UserError + + +class AccountMove(models.Model): + _inherit = "account.move" + + def generate_self_invoice(self): + res = super().generate_self_invoice() + if self.rc_self_invoice_id: + rc_type = self.fiscal_position_id.rc_type_id + if rc_type.fiscal_document_type_id: + self.rc_self_invoice_id.fiscal_document_type_id = ( + rc_type.fiscal_document_type_id.id + ) + if self.fatturapa_attachment_in_id: + doc_id = self.fatturapa_attachment_in_id.name + else: + doc_id = self.ref if self.ref else self.name + self.rc_self_invoice_id.related_documents = [ + ( + 0, + 0, + { + "type": "invoice", + "name": doc_id, + "date": self.invoice_date, + }, + ) + ] + return res + + def button_draft(self): + super().button_draft() + for inv in self: + if not inv.env.context.get( + "rc_set_to_draft" + ) and inv.rc_purchase_invoice_id.state in ["draft", "cancel"]: + raise UserError( + _( + "Vendor invoice that has generated this self invoice isn't " + "validated. " + "Validate vendor invoice before." + ) + ) + return True + + def preventive_checks(self): + super().preventive_checks() + invoices = self + invoices_with_rc = invoices.filtered(lambda x: x.rc_purchase_invoice_id) + invoices_without_rc = invoices - invoices_with_rc + if invoices_with_rc and invoices_without_rc: + raise UserError( + _( + "Selected invoices are both with and without reverse charge. You " + "should selected a smaller set of invoices" + ) + ) + invoices_with_document_type_codes = invoices.filtered( + lambda x: x.fiscal_document_type_id.code in ["TD17", "TD18", "TD19"] + ) + invoices_without_document_type_codes = ( + invoices - invoices_with_document_type_codes + ) + if invoices_with_document_type_codes and invoices_without_document_type_codes: + raise UserError( + _( + "Select invoices are of too many fiscal document types: " + "select invoices exclusively of type 'TD17', 'TD18', 'TD19' " + "or exclusively of other types." + ) + ) + rc_suppliers = invoices.mapped("rc_purchase_invoice_id.partner_id") + if len(rc_suppliers) > 1: + raise UserError( + _( + "Selected reverse charge invoices have different suppliers. Please " + "select invoices with same supplier" + ) + ) + # --- preventive checks related to set CedentePrestatore.DatiAnagrafici --- # + partner = rc_suppliers[0] + fiscal_document_type_codes = invoices.mapped("fiscal_document_type_id.code") + # Se vale IT , il sistema verifica che il TipoDocumento sia diverso da + # TD17, TD18 e TD19; in caso contrario il file viene scartato + if partner.vat: + if partner.vat[0:2] == "IT" and any( + [x in ["TD17", "TD18", "TD19"] for x in fiscal_document_type_codes] + ): + raise UserError( + _( + "A self-invoice cannot be issued with IT country code and " + "fiscal document type in 'TD17', 'TD18', 'TD19'." + ) + ) + if partner.vat[0:2] not in self.env["res.country"].search([]).mapped( + "code" + ): + raise ValueError( + _( + "Country code does not exist or it is not mapped in countries: " + "%s" % partner.vat[0:2] + ) + ) + elif not partner.country_id.code or partner.country_id.code == "IT": + raise UserError( + _("Impossible to set IdFiscaleIVA for %s") % partner.display_name + ) + # --- preventive checks related to set CedentePrestatore.Sede --- # + if not partner.street: + raise UserError(_("Partner %s, Street is not set.") % partner.display_name) + if not partner.city: + raise UserError(_("Partner %s, City is not set.") % partner.display_name) + if not partner.country_id: + raise UserError(_("Partner %s, Country is not set.") % partner.display_name) + if not partner.zip: + raise UserError(_("Partner %s, ZIP is not set.") % partner.display_name) + return diff --git a/l10n_it_fatturapa_out_rc/readme/CONTRIBUTORS.rst b/l10n_it_fatturapa_out_rc/readme/CONTRIBUTORS.rst index 2b476d752064..27fc8303500e 100644 --- a/l10n_it_fatturapa_out_rc/readme/CONTRIBUTORS.rst +++ b/l10n_it_fatturapa_out_rc/readme/CONTRIBUTORS.rst @@ -1,3 +1,6 @@ * `TAKOBI `_: * Lorenzo Battistini +* `Agile Business Group `_: + + * Alex Comba diff --git a/l10n_it_fatturapa_out_rc/tests/__init__.py b/l10n_it_fatturapa_out_rc/tests/__init__.py index 2dfe10960100..c4c14b87a47c 100644 --- a/l10n_it_fatturapa_out_rc/tests/__init__.py +++ b/l10n_it_fatturapa_out_rc/tests/__init__.py @@ -1 +1,2 @@ -from . import test_e_invoice_out_rc +from . import fatturapa_common +from . import test_fatturapa_xml_validation diff --git a/l10n_it_fatturapa_out_rc/tests/data/IT10538570960_00002.xml b/l10n_it_fatturapa_out_rc/tests/data/IT10538570960_00002.xml index 2b5158f903b3..7d30bc92fce8 100644 --- a/l10n_it_fatturapa_out_rc/tests/data/IT10538570960_00002.xml +++ b/l10n_it_fatturapa_out_rc/tests/data/IT10538570960_00002.xml @@ -1,5 +1,8 @@ - - + + @@ -57,13 +60,13 @@ TD17 EUR 2020-12-01 - SLF/2020/0015 + SLF/2020/12/0001 122.00 Reverse charge self invoice. Supplier: Intra EU supplier Reference: EU-SUPPLIER-REF Date: 2020-12-01 - Internal reference: BILL/2020/0025 + Internal reference: BILL/2020/12/0001 EU-SUPPLIER-REF diff --git a/l10n_it_fatturapa_out_rc/tests/data/IT10538570960_00003.xml b/l10n_it_fatturapa_out_rc/tests/data/IT10538570960_00003.xml index dc609f5d4cb7..ae91d199dd4f 100644 --- a/l10n_it_fatturapa_out_rc/tests/data/IT10538570960_00003.xml +++ b/l10n_it_fatturapa_out_rc/tests/data/IT10538570960_00003.xml @@ -1,5 +1,8 @@ - - + + @@ -57,13 +60,13 @@ TD17 EUR 2020-12-01 - SLF/2020/0016 + RSLF/2020/12/0001 -122.00 Reverse charge self invoice. Supplier: Intra EU supplier Reference: EU-SUPPLIER-REF Date: 2020-12-01 - Internal reference: BILL/2020/0026 + Internal reference: RBILL/2020/12/0001 EU-SUPPLIER-REF diff --git a/l10n_it_fatturapa_out_rc/tests/data/IT10538570960_00004.xml b/l10n_it_fatturapa_out_rc/tests/data/IT10538570960_00004.xml index 936d5cb2b35f..46aedcac83b4 100644 --- a/l10n_it_fatturapa_out_rc/tests/data/IT10538570960_00004.xml +++ b/l10n_it_fatturapa_out_rc/tests/data/IT10538570960_00004.xml @@ -60,13 +60,13 @@ TD17 EUR 2020-12-01 - SLFEX/2020/0001 + SLFEX/2020/12/0001 122.00 Reverse charge self invoice. Supplier: Extra EU supplier Reference: EXEU-SUPPLIER-REF Date: 2020-12-01 - Internal reference: BILL/2020/0027 + Internal reference: BILL/2020/12/0001 EXEU-SUPPLIER-REF diff --git a/l10n_it_fatturapa_out_rc/tests/fatturapa_common.py b/l10n_it_fatturapa_out_rc/tests/fatturapa_common.py new file mode 100644 index 000000000000..c49043651785 --- /dev/null +++ b/l10n_it_fatturapa_out_rc/tests/fatturapa_common.py @@ -0,0 +1,27 @@ +from odoo.addons.l10n_it_fatturapa_out.tests.fatturapa_common import ( + FatturaPACommon as _FatturaPACommon, +) + + +class FatturaPACommon(_FatturaPACommon): + def setUp(self): + super().setUp() + + def _fix_payability(tax, ref): + reftax = self.env.ref(ref).sudo() + tax.payability = reftax.payability + + # XXX - to be moved to l10n_it_fatturapa_out.tests.fatturapa_common ? + _fix_payability(self.tax_22, "l10n_it_fatturapa.tax_22") + _fix_payability(self.tax_10, "l10n_it_fatturapa.tax_10") + _fix_payability(self.tax_22_SP, "l10n_it_fatturapa.tax_22_SP") + + def getAttachment(self, name, module_name=None): + if module_name is None: + module_name = "l10n_it_fatturapa_out_rc" + return super().getAttachment(name, module_name) + + def getFile(self, filename, module_name=None): + if module_name is None: + module_name = "l10n_it_fatturapa_out_rc" + return super().getFile(filename, module_name) diff --git a/l10n_it_fatturapa_out_rc/tests/test_e_invoice_out_rc.py b/l10n_it_fatturapa_out_rc/tests/test_e_invoice_out_rc.py deleted file mode 100644 index a67a75780f69..000000000000 --- a/l10n_it_fatturapa_out_rc/tests/test_e_invoice_out_rc.py +++ /dev/null @@ -1,315 +0,0 @@ -import base64 - -from odoo.exceptions import UserError - -from odoo.addons.l10n_it_fatturapa_out.tests.fatturapa_common import FatturaPACommon -from odoo.addons.l10n_it_reverse_charge.tests.rc_common import ReverseChargeCommon - - -class TestReverseCharge(ReverseChargeCommon, FatturaPACommon): - def setUp(self): - super(TestReverseCharge, self).setUp() - self.company = self.rc_type_ieu.company_id - self.company.vat = "IT10538570960" # 06363391001 gives 00399 error - self.company.fatturapa_art73 = False - self.rc_type_ieu.partner_type = "other" - self.rc_type_ieu.partner_id = self.company.partner_id.id - self.rc_type_ieu.fiscal_document_type_id = self.env.ref( - "l10n_it_fiscal_document_type.15" - ).id - self.rc_type_eeu.partner_id = self.company.partner_id.id - self.rc_type_eeu.fiscal_document_type_id = self.env.ref( - "l10n_it_fiscal_document_type.15" - ).id - self.tax_22vi.kind_id = self.env.ref("l10n_it_account_tax_kind.n6").id - self.supplier_intraEU.customer = True - self.customer_invoice_account = ( - self.env["account.account"] - .search( - [ - ( - "user_type_id", - "=", - self.env.ref("account.data_account_type_receivable").id, - ) - ], - limit=1, - ) - .id - ) - self.supplier_invoice_account = ( - self.env["account.account"] - .search( - [ - ( - "user_type_id", - "=", - self.env.ref("account.data_account_type_payable").id, - ) - ], - limit=1, - ) - .id - ) - self.sale_invoice_line_account = ( - self.env["account.account"] - .search( - [ - ( - "user_type_id", - "=", - self.env.ref("account.data_account_type_revenue").id, - ) - ], - limit=1, - ) - .id - ) - self.purchase_invoice_line_account = ( - self.env["account.account"] - .search( - [ - ( - "user_type_id", - "=", - self.env.ref("account.data_account_type_direct_costs").id, - ) - ], - limit=1, - ) - .id - ) - - def set_sequence_journal_selfinvoice(self, invoice_number, dt): - inv_seq = self.journal_selfinvoice.sequence_id - seq_date = self.env["ir.sequence.date_range"].search( - [ - ("sequence_id", "=", inv_seq.id), - ("date_from", "<=", dt), - ("date_to", ">=", dt), - ], - limit=1, - ) - if not seq_date: - seq_date = inv_seq._create_date_range_seq(dt) - seq_date.number_next_actual = invoice_number - - def set_bill_sequence(self, invoice_number, dt): - seq_pool = self.env["ir.sequence"] - inv_seq = seq_pool.search([("name", "=", "BILL Sequence")])[0] - seq_date = self.env["ir.sequence.date_range"].search( - [ - ("sequence_id", "=", inv_seq.id), - ("date_from", "<=", dt), - ("date_to", ">=", dt), - ], - limit=1, - ) - if not seq_date: - seq_date = inv_seq._create_date_range_seq(dt) - seq_date.number_next_actual = invoice_number - - def test_intra_EU(self): - self.set_sequence_journal_selfinvoice(15, "2020-12-01") - self.set_bill_sequence(25, "2020-12-01") - self.supplier_intraEU.property_payment_term_id = self.term_15_30.id - invoice = self.invoice_model.create( - { - "partner_id": self.supplier_intraEU.id, - "account_id": self.invoice_account, - "type": "in_invoice", - "date_invoice": "2020-12-01", - "reference": "EU-SUPPLIER-REF", - } - ) - - invoice_line_vals = { - "name": "Invoice for sample product", - "account_id": self.invoice_line_account, - "invoice_id": invoice.id, - "product_id": self.sample_product.id, - "price_unit": 100, - "invoice_line_tax_ids": [(4, self.tax_22ai.id, 0)], - } - invoice_line = self.invoice_line_model.create(invoice_line_vals) - invoice_line.onchange_invoice_line_tax_id() - invoice.compute_taxes() - invoice.action_invoice_open() - self.assertEqual( - invoice.rc_self_invoice_id.fiscal_document_type_id.code, "TD17" - ) - with self.assertRaises(UserError): - # Impossible to set IdFiscaleIVA - self.run_wizard(invoice.rc_self_invoice_id.id) - self.supplier_intraEU.vat = "BE0477472701" - with self.assertRaises(UserError): - # Street is not set - self.run_wizard(invoice.rc_self_invoice_id.id) - self.supplier_intraEU.street = "Street" - self.supplier_intraEU.zip = "12345" - self.supplier_intraEU.city = "city" - self.supplier_intraEU.country_id = self.env.ref("base.be") - res = self.run_wizard(invoice.rc_self_invoice_id.id) - attachment = self.attach_model.browse(res["res_id"]) - self.set_e_invoice_file_id(attachment, "IT10538570960_00002.xml") - xml_content = base64.decodebytes(attachment.datas) - self.check_content( - xml_content, "IT10538570960_00002.xml", "l10n_it_fatturapa_out_rc" - ) - - def test_intra_EU_customer(self): - self.set_sequence_journal_selfinvoice(15, "2020-12-01") - self.set_bill_sequence(25, "2020-12-01") - self.supplier_intraEU.property_payment_term_id = self.term_15_30.id - invoice = self.invoice_model.create( - { - "partner_id": self.supplier_intraEU.id, - "account_id": self.customer_invoice_account, - "type": "out_invoice", - "date_invoice": "2020-12-01", - "reference": "EU-CUSTOMER-REF", - } - ) - - invoice_line_vals = { - "name": "Invoice for sample product", - "account_id": self.sale_invoice_line_account, - "invoice_id": invoice.id, - "product_id": self.sample_product.id, - "price_unit": 100, - "invoice_line_tax_ids": [(4, self.tax_22vi.id, 0)], - } - invoice_line = self.invoice_line_model.create(invoice_line_vals) - invoice_line.onchange_invoice_line_tax_id() - invoice.compute_taxes() - invoice.action_invoice_open() - self.assertFalse(invoice.rc_self_invoice_id) - - def test_intra_EU_draft(self): - self.set_sequence_journal_selfinvoice(15, "2020-12-01") - self.set_bill_sequence(25, "2020-12-01") - self.supplier_intraEU.property_payment_term_id = self.term_15_30.id - invoice = self.invoice_model.create( - { - "partner_id": self.supplier_intraEU.id, - "account_id": self.invoice_account, - "type": "in_invoice", - "date_invoice": "2020-12-01", - "reference": "EU-SUPPLIER-REF", - } - ) - - invoice_line_vals = { - "name": "Invoice for sample product", - "account_id": self.invoice_line_account, - "invoice_id": invoice.id, - "product_id": self.sample_product.id, - "price_unit": 100, - "invoice_line_tax_ids": [(4, self.tax_22ai.id, 0)], - } - invoice_line = self.invoice_line_model.create(invoice_line_vals) - invoice_line.onchange_invoice_line_tax_id() - invoice.compute_taxes() - invoice.action_invoice_open() - self.assertEqual(invoice.rc_self_invoice_id.state, "paid") - invoice.journal_id.update_posted = True - invoice.action_invoice_cancel() - self.assertEqual(invoice.rc_self_invoice_id.state, "cancel") - with self.assertRaises(UserError): - invoice.rc_self_invoice_id.action_invoice_draft() - - def test_intra_EU_supplier_refund(self): - self.set_sequence_journal_selfinvoice(16, "2020-12-01") - self.set_bill_sequence(26, "2020-12-01") - self.supplier_intraEU.property_payment_term_id = self.term_15_30.id - invoice = self.invoice_model.create( - { - "partner_id": self.supplier_intraEU.id, - "account_id": self.supplier_invoice_account, - "type": "in_refund", - "date_invoice": "2020-12-01", - "reference": "EU-SUPPLIER-REF", - } - ) - self.assertEqual(invoice.fiscal_document_type_id.code, "TD04") - invoice_line_vals = { - "name": "Invoice for sample product", - "account_id": self.purchase_invoice_line_account, - "invoice_id": invoice.id, - "product_id": self.sample_product.id, - "price_unit": 100, - "invoice_line_tax_ids": [(4, self.tax_22ai.id, 0)], - } - invoice_line = self.invoice_line_model.create(invoice_line_vals) - invoice_line.onchange_invoice_line_tax_id() - invoice.compute_taxes() - invoice.action_invoice_open() - self.assertEqual( - invoice.rc_self_invoice_id.fiscal_document_type_id.code, "TD17" - ) - with self.assertRaises(UserError): - # Impossible to set IdFiscaleIVA - self.run_wizard(invoice.rc_self_invoice_id.id) - self.supplier_intraEU.vat = "BE0477472701" - with self.assertRaises(UserError): - # Street is not set - self.run_wizard(invoice.rc_self_invoice_id.id) - self.supplier_intraEU.street = "Street" - self.supplier_intraEU.zip = "12345" - self.supplier_intraEU.city = "city" - self.supplier_intraEU.country_id = self.env.ref("base.be") - res = self.run_wizard(invoice.rc_self_invoice_id.id) - attachment = self.attach_model.browse(res["res_id"]) - self.set_e_invoice_file_id(attachment, "IT10538570960_00003.xml") - xml_content = base64.decodebytes(attachment.datas) - self.check_content( - xml_content, "IT10538570960_00003.xml", "l10n_it_fatturapa_out_rc" - ) - - def test_extra_EU(self): - self.set_bill_sequence(27, "2020-12-01") - self.supplier_extraEU.property_payment_term_id = self.term_15_30.id - self.rc_type_eeu.with_supplier_self_invoice = False - invoice = self.invoice_model.create( - { - "partner_id": self.supplier_extraEU.id, - "account_id": self.invoice_account, - "type": "in_invoice", - "date_invoice": "2020-12-01", - "reference": "EXEU-SUPPLIER-REF", - } - ) - - invoice_line_vals = { - "name": "Invoice for sample product", - "account_id": self.invoice_line_account, - "invoice_id": invoice.id, - "product_id": self.sample_product.id, - "price_unit": 100, - "invoice_line_tax_ids": [(4, self.tax_22ae.id, 0)], - } - invoice_line = self.invoice_line_model.create(invoice_line_vals) - invoice_line.onchange_invoice_line_tax_id() - invoice.compute_taxes() - invoice.action_invoice_open() - self.assertEqual( - invoice.rc_self_invoice_id.fiscal_document_type_id.code, "TD17" - ) - with self.assertRaises(UserError): - # Impossible to set IdFiscaleIVA - self.run_wizard(invoice.rc_self_invoice_id.id) - self.supplier_extraEU.vat = "US484762844" - with self.assertRaises(UserError): - # Street is not set - self.run_wizard(invoice.rc_self_invoice_id.id) - self.supplier_extraEU.street = "Street" - self.supplier_extraEU.zip = "12345" - self.supplier_extraEU.city = "city" - self.supplier_extraEU.country_id = self.env.ref("base.us") - res = self.run_wizard(invoice.rc_self_invoice_id.id) - attachment = self.attach_model.browse(res["res_id"]) - self.set_e_invoice_file_id(attachment, "IT10538570960_00004.xml") - xml_content = base64.decodebytes(attachment.datas) - self.check_content( - xml_content, "IT10538570960_00004.xml", "l10n_it_fatturapa_out_rc" - ) diff --git a/l10n_it_fatturapa_out_rc/tests/test_fatturapa_xml_validation.py b/l10n_it_fatturapa_out_rc/tests/test_fatturapa_xml_validation.py new file mode 100644 index 000000000000..72fa21d46e05 --- /dev/null +++ b/l10n_it_fatturapa_out_rc/tests/test_fatturapa_xml_validation.py @@ -0,0 +1,237 @@ +# Copyright 2021 Alex Comba - Agile Business Group +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import base64 + +from odoo import fields +from odoo.exceptions import UserError +from odoo.tests.common import Form + +from odoo.addons.l10n_it_fatturapa_out.tests.fatturapa_common import FatturaPACommon +from odoo.addons.l10n_it_reverse_charge.tests.rc_common import ReverseChargeCommon + + +class TestFatturaPAXMLValidation(ReverseChargeCommon, FatturaPACommon): + def setUp(self): + super().setUp() + + # XXX - a company named "YourCompany" already exists + # we move it out of the way but we should do better here + self.env.company.sudo().search([("name", "=", "YourCompany")]).write( + {"name": "YourCompany_"} + ) + + self.env.company.name = "YourCompany" + self.env.company.vat = "IT10538570960" + self.env.company.fatturapa_art73 = False + self.env.company.partner_id.street = "Via Milano, 1" + self.env.company.partner_id.city = "Roma" + self.env.company.partner_id.state_id = self.env.ref("base.state_us_2").id + self.env.company.partner_id.zip = "00100" + self.env.company.partner_id.phone = "06543534343" + self.env.company.email = "info@yourcompany.example.com" + self.env.company.partner_id.country_id = self.env.ref("base.it").id + self.env.company.fatturapa_fiscal_position_id = self.env.ref( + "l10n_it_fatturapa.fatturapa_RF01" + ).id + + self.env["decimal.precision"].search( + [("name", "=", "Product Unit of Measure")] + ).digits = 3 + self.env["uom.uom"].search([("name", "=", "Units")]).name = "Unit(s)" + + self.rc_type_ieu.partner_type = "other" + self.rc_type_ieu.partner_id = self.env.company.partner_id.id + self.rc_type_ieu.fiscal_document_type_id = self.env.ref( + "l10n_it_fiscal_document_type.15" + ).id + self.rc_type_eeu.partner_id = self.env.company.partner_id.id + self.rc_type_eeu.fiscal_document_type_id = self.env.ref( + "l10n_it_fiscal_document_type.15" + ).id + # l'auto fattura non deve avere la natura di esenzione + # self.tax_22vi.kind_id = self.env.ref("l10n_it_account_tax_kind.n6").id + self.supplier_intraEU.customer_rank = 0 + self.bill_sequence_name = "Vendor Bills : Check Number Sequence" + self.selfinvoice_sequence_name = "selfinvoice : Check Number Sequence" + + @classmethod + def _create_invoice(cls, move_type, partner, name, invoice_date, ref, taxes): + invoice_form = Form( + cls.env["account.move"].with_context({"default_move_type": move_type}) + ) + invoice_form.partner_id = partner + invoice_form.name = name + invoice_form.invoice_date = fields.Date.from_string(invoice_date) + invoice_form.date = fields.Date.from_string(invoice_date) + invoice_form.ref = ref + with invoice_form.line_ids.new() as line_form: + line_form.product_id = cls.env.ref("product.product_product_4d") + line_form.name = "Invoice for sample product" + line_form.price_unit = 100 + line_form.tax_ids.clear() + line_form.tax_ids.add(taxes) + return invoice_form + + def test_intra_EU(self): + self.set_sequences( + 15, "2020-12-01", sequence_name=self.selfinvoice_sequence_name + ) + self.set_sequences(25, "2020-12-01", sequence_name=self.bill_sequence_name) + self.supplier_intraEU.property_payment_term_id = self.term_15_30.id + + invoice_form = self._create_invoice( + move_type="in_invoice", + partner=self.supplier_intraEU, + name="BILL/2021/12/0001", + invoice_date="2020-12-01", + ref="EU-SUPPLIER-REF", + taxes=self.tax_22ai, + ) + invoice = invoice_form.save() + invoice.action_post() + + self.assertEqual( + invoice.rc_self_invoice_id.fiscal_document_type_id.code, "TD17" + ) + with self.assertRaises(UserError): + # Impossible to set IdFiscaleIVA + self.run_wizard(invoice.rc_self_invoice_id.id) + self.supplier_intraEU.vat = "BE0477472701" + with self.assertRaises(UserError): + # Street is not set + self.run_wizard(invoice.rc_self_invoice_id.id) + self.supplier_intraEU.street = "Street" + self.supplier_intraEU.zip = "12345" + self.supplier_intraEU.city = "city" + self.supplier_intraEU.country_id = self.env.ref("base.be") + self.supplier_intraEU.is_company = True + res = self.run_wizard(invoice.rc_self_invoice_id.id) + attachment = self.attach_model.browse(res["res_id"]) + self.set_e_invoice_file_id(attachment, "IT10538570960_00002.xml") + xml_content = base64.decodebytes(attachment.datas) + self.check_content( + xml_content, "IT10538570960_00002.xml", "l10n_it_fatturapa_out_rc" + ) + + def test_intra_EU_customer(self): + self.set_sequences( + 15, "2020-12-01", sequence_name=self.selfinvoice_sequence_name + ) + self.set_sequences(25, "2020-12-01", sequence_name=self.bill_sequence_name) + self.supplier_intraEU.property_payment_term_id = self.term_15_30.id + + invoice_form = self._create_invoice( + move_type="out_invoice", + partner=self.supplier_intraEU, + name="BILL/2021/12/0002", + invoice_date="2020-12-01", + ref="EU-CUSTOMER-REF", + taxes=self.tax_22vi, + ) + invoice = invoice_form.save() + invoice.action_post() + self.assertFalse(invoice.rc_self_invoice_id) + + def test_intra_EU_draft(self): + self.set_sequences( + 15, "2020-12-01", sequence_name=self.selfinvoice_sequence_name + ) + self.set_sequences(25, "2020-12-01", sequence_name=self.bill_sequence_name) + self.supplier_intraEU.property_payment_term_id = self.term_15_30.id + + invoice_form = self._create_invoice( + move_type="in_invoice", + partner=self.supplier_intraEU, + name="BILL/2021/12/0003", + invoice_date="2020-12-01", + ref="EU-SUPPLIER-REF", + taxes=self.tax_22ai, + ) + invoice = invoice_form.save() + invoice.action_post() + invoice.button_cancel() + self.assertEqual(invoice.state, "cancel") + self.assertEqual(invoice.state, "cancel") + self.assertFalse(invoice.rc_self_invoice_id.state) + invoice.button_draft() + self.assertEqual(invoice.state, "draft") + + def test_intra_EU_supplier_refund(self): + self.set_sequences( + 16, "2020-12-01", sequence_name=self.selfinvoice_sequence_name + ) + self.set_sequences(26, "2020-12-01", sequence_name=self.bill_sequence_name) + self.supplier_intraEU.property_payment_term_id = self.term_15_30.id + + invoice_form = self._create_invoice( + move_type="in_refund", + partner=self.supplier_intraEU, + name="BILL/2021/12/0004", + invoice_date="2020-12-01", + ref="EU-SUPPLIER-REF", + taxes=self.tax_22ai, + ) + invoice = invoice_form.save() + invoice.action_post() + self.assertEqual( + invoice.rc_self_invoice_id.fiscal_document_type_id.code, "TD17" + ) + with self.assertRaises(UserError): + # Impossible to set IdFiscaleIVA + self.run_wizard(invoice.rc_self_invoice_id.id) + self.supplier_intraEU.vat = "BE0477472701" + with self.assertRaises(UserError): + # Street is not set + self.run_wizard(invoice.rc_self_invoice_id.id) + self.supplier_intraEU.street = "Street" + self.supplier_intraEU.zip = "12345" + self.supplier_intraEU.city = "city" + self.supplier_intraEU.country_id = self.env.ref("base.be") + self.supplier_intraEU.is_company = True + res = self.run_wizard(invoice.rc_self_invoice_id.id) + attachment = self.attach_model.browse(res["res_id"]) + self.set_e_invoice_file_id(attachment, "IT10538570960_00003.xml") + xml_content = base64.decodebytes(attachment.datas) + self.check_content( + xml_content, "IT10538570960_00003.xml", "l10n_it_fatturapa_out_rc" + ) + + def test_extra_EU(self): + self.set_sequences(27, "2020-12-01", sequence_name=self.bill_sequence_name) + self.supplier_extraEU.property_payment_term_id = self.term_15_30.id + self.rc_type_eeu.with_supplier_self_invoice = False + + invoice_form = self._create_invoice( + move_type="in_invoice", + partner=self.supplier_extraEU, + name="BILL/2021/12/0005", + invoice_date="2020-12-01", + ref="EXEU-SUPPLIER-REF", + taxes=self.tax_22ae, + ) + invoice = invoice_form.save() + invoice.action_post() + + self.assertEqual( + invoice.rc_self_invoice_id.fiscal_document_type_id.code, "TD17" + ) + with self.assertRaises(UserError): + # Impossible to set IdFiscaleIVA + self.run_wizard(invoice.rc_self_invoice_id.id) + self.supplier_extraEU.vat = "US484762844" + with self.assertRaises(UserError): + # Street is not set + self.run_wizard(invoice.rc_self_invoice_id.id) + self.supplier_extraEU.street = "Street" + self.supplier_extraEU.zip = "12345" + self.supplier_extraEU.city = "city" + self.supplier_extraEU.country_id = self.env.ref("base.us") + self.supplier_extraEU.is_company = True + res = self.run_wizard(invoice.rc_self_invoice_id.id) + attachment = self.attach_model.browse(res["res_id"]) + self.set_e_invoice_file_id(attachment, "IT10538570960_00004.xml") + xml_content = base64.decodebytes(attachment.datas) + self.check_content( + xml_content, "IT10538570960_00004.xml", "l10n_it_fatturapa_out_rc" + ) diff --git a/l10n_it_fatturapa_out_rc/views/invoice_it_template.xml b/l10n_it_fatturapa_out_rc/views/invoice_it_template.xml new file mode 100644 index 000000000000..5b5046e8b6b7 --- /dev/null +++ b/l10n_it_fatturapa_out_rc/views/invoice_it_template.xml @@ -0,0 +1,95 @@ + + + + + + + + + + diff --git a/l10n_it_fatturapa_out_rc/wizard/__init__.py b/l10n_it_fatturapa_out_rc/wizard/__init__.py index b3ee575fd1d2..861a0af3e6aa 100644 --- a/l10n_it_fatturapa_out_rc/wizard/__init__.py +++ b/l10n_it_fatturapa_out_rc/wizard/__init__.py @@ -1 +1,2 @@ -from . import wizard_export_e_invoice +from . import efattura +from . import wizard_export_fatturapa diff --git a/l10n_it_fatturapa_out_rc/wizard/efattura.py b/l10n_it_fatturapa_out_rc/wizard/efattura.py new file mode 100644 index 000000000000..abce603bd7c5 --- /dev/null +++ b/l10n_it_fatturapa_out_rc/wizard/efattura.py @@ -0,0 +1,26 @@ +# Copyright 2021 Alex Comba - Agile Business Group +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo.addons.l10n_it_fatturapa_out.wizard.efattura import ( + EFatturaOut as _EFatturaOut, +) + + +class EFatturaOut(_EFatturaOut): + def get_template_values(self): + def get_sign(invoice): + sign = 1 + if ( + invoice.move_type + in [ + "out_refund", + "in_refund", + ] + and invoice.fiscal_document_type_id.code not in ["TD04", "TD08"] + ): + sign = -1 + return sign + + template_values = super().get_template_values() + template_values.update({"get_sign": get_sign}) + return template_values diff --git a/l10n_it_fatturapa_out_rc/wizard/wizard_export_e_invoice.py b/l10n_it_fatturapa_out_rc/wizard/wizard_export_e_invoice.py deleted file mode 100644 index 0cf58c77b900..000000000000 --- a/l10n_it_fatturapa_out_rc/wizard/wizard_export_e_invoice.py +++ /dev/null @@ -1,248 +0,0 @@ -from odoo import _, models -from odoo.exceptions import UserError - -from odoo.addons.l10n_it_account.tools.account_tools import encode_for_export -from odoo.addons.l10n_it_fatturapa.bindings.fatturapa import ( - AnagraficaType, - IdFiscaleType, - IndirizzoType, -) - - -class WizardExportFatturapa(models.TransientModel): - _inherit = "wizard.export.fatturapa" - - def exportInvoiceXML( - self, company, partner, invoice_ids, attach=False, context=None - ): - if context is None: - context = {} - invoices = self.env["account.invoice"].browse(invoice_ids) - invoices_with_rc = False - invoices_without_rc = False - for invoice in invoices: - if invoice.rc_purchase_invoice_id: - invoices_with_rc = True - else: - invoices_without_rc = True - if invoices_with_rc and invoices_without_rc: - raise UserError( - _( - "Selected invoices are both with and without reverse charge. You " - "should selected a smaller set of invoices" - ) - ) - invoices_fiscal_document_type_codes = invoices.filtered( - lambda x: x.fiscal_document_type_id.code in ["TD17", "TD18", "TD19"] - ) - invoices_fiscal_document_type1_codes = invoices.filtered( - lambda x: x.fiscal_document_type_id.code not in ["TD17", "TD18", "TD19"] - ) - if invoices_fiscal_document_type_codes and invoices_fiscal_document_type1_codes: - raise UserError( - _( - "Select invoices are of too many fiscal document types: " - "select invoices exclusively of type 'TD17', 'TD18', 'TD19' " - "or exclusively of other types." - ) - ) - rc_suppliers = invoices.mapped("rc_purchase_invoice_id.partner_id") - if len(rc_suppliers) > 1: - raise UserError( - _( - "Selected reverse charge invoices have different suppliers. Please " - "select invoices with same supplier" - ) - ) - if rc_suppliers: - context["rc_supplier"] = rc_suppliers[0] - context["invoices_fiscal_document_type_codes"] = invoices.mapped( - "fiscal_document_type_id.code" - ) - return super(WizardExportFatturapa, self).exportInvoiceXML( - company, partner, invoice_ids, attach, context - ) - - def _setDatiAnagraficiCedente(self, CedentePrestatore, company): - res = super(WizardExportFatturapa, self)._setDatiAnagraficiCedente( - CedentePrestatore, company - ) - if self.env.context.get("rc_supplier"): - partner = self.env.context["rc_supplier"] - CedentePrestatore.DatiAnagrafici.CodiceFiscale = None - fiscal_document_type_codes = self.env.context.get( - "invoices_fiscal_document_type_codes" - ) - # Se vale IT , il sistema verifica che il TipoDocumento sia diverso da - # TD17, TD18 e TD19; in caso contrario il file viene scartato - if partner.vat: - if partner.vat[0:2] == "IT" and any( - [x in ["TD17", "TD18", "TD19"] for x in fiscal_document_type_codes] - ): - raise UserError( - _( - "A self-invoice cannot be issued with IT country code and " - "fiscal document type in 'TD17', 'TD18', 'TD19'." - ) - ) - if partner.vat[0:2] not in self.env["res.country"].search([]).mapped( - "code" - ): - raise ValueError( - _( - "Country code does not exist or it is not mapped in countries: " - "%s" % partner.vat[0:2] - ) - ) - CedentePrestatore.DatiAnagrafici.IdFiscaleIVA = IdFiscaleType( - IdPaese=partner.vat[0:2], IdCodice=partner.vat[2:] - ) - elif partner.country_id.code and partner.country_id.code != "IT": - CedentePrestatore.DatiAnagrafici.IdFiscaleIVA = IdFiscaleType( - IdPaese=partner.country_id.code, IdCodice="99999999999" - ) - else: - raise UserError( - _("Impossible to set IdFiscaleIVA for %s") % partner.display_name - ) - CedentePrestatore.DatiAnagrafici.Anagrafica = AnagraficaType( - Denominazione=partner.name - ) - return res - - def _setSedeCedente(self, CedentePrestatore, company): - res = super(WizardExportFatturapa, self)._setSedeCedente( - CedentePrestatore, company - ) - if self.env.context.get("rc_supplier"): - partner = self.env.context["rc_supplier"] - if not partner.street: - raise UserError( - _("Partner %s, Street is not set.") % partner.display_name - ) - if not partner.city: - raise UserError( - _("Partner %s, City is not set.") % partner.display_name - ) - if not partner.country_id: - raise UserError( - _("Partner %s, Country is not set.") % partner.display_name - ) - if partner.codice_destinatario == "XXXXXXX": - CedentePrestatore.Sede = IndirizzoType( - Indirizzo=encode_for_export(partner.street, 60), - CAP="00000", - Comune=encode_for_export(partner.city, 60), - Provincia="EE", - Nazione=partner.country_id.code, - ) - else: - if not partner.zip: - raise UserError( - _("Partner %s, ZIP is not set.") % partner.display_name - ) - CedentePrestatore.Sede = IndirizzoType( - Indirizzo=encode_for_export(partner.street, 60), - CAP=partner.zip, - Comune=encode_for_export(partner.city, 60), - Nazione=partner.country_id.code, - ) - if partner.state_id: - CedentePrestatore.Sede.Provincia = partner.state_id.code - return res - - def _setStabileOrganizzazione(self, CedentePrestatore, company): - res = super(WizardExportFatturapa, self)._setStabileOrganizzazione( - CedentePrestatore, company - ) - if self.env.context.get("rc_supplier"): - CedentePrestatore.StabileOrganizzazione = None - return res - - def _setRea(self, CedentePrestatore, company): - res = super(WizardExportFatturapa, self)._setRea(CedentePrestatore, company) - if self.env.context.get("rc_supplier"): - CedentePrestatore.IscrizioneREA = None - return res - - def _setContatti(self, CedentePrestatore, company): - res = super(WizardExportFatturapa, self)._setContatti( - CedentePrestatore, company - ) - if self.env.context.get("rc_supplier"): - CedentePrestatore.Contatti = None - return res - - def _setPubAdministrationRef(self, CedentePrestatore, company): - res = super(WizardExportFatturapa, self)._setPubAdministrationRef( - CedentePrestatore, company - ) - if self.env.context.get("rc_supplier"): - CedentePrestatore.RiferimentoAmministrazione = None - return res - - def setDatiGeneraliDocumento(self, invoice, body): - res = super(WizardExportFatturapa, self).setDatiGeneraliDocumento(invoice, body) - if ( - invoice.rc_purchase_invoice_id - and invoice.rc_purchase_invoice_id.fiscal_position_id - and invoice.rc_purchase_invoice_id.fiscal_position_id.rc_type_id - and invoice.rc_purchase_invoice_id.fiscal_position_id.rc_type_id.fiscal_document_type_id - ): - body.DatiGenerali.DatiGeneraliDocumento.TipoDocumento = ( - invoice.rc_purchase_invoice_id.fiscal_position_id.rc_type_id.fiscal_document_type_id.code - ) - if ( - invoice.type - in [ - "out_refund", - "in_refund", - ] - and invoice.fiscal_document_type_id.code not in ["TD04", "TD08"] - ): - body.DatiGenerali.DatiGeneraliDocumento.ImportoTotaleDocumento = ( - -body.DatiGenerali.DatiGeneraliDocumento.ImportoTotaleDocumento - ) - return res - - def setDettaglioLinea(self, line_no, line, body, price_precision, uom_precision): - DettaglioLinea = super(WizardExportFatturapa, self).setDettaglioLinea( - line_no, line, body, price_precision, uom_precision - ) - if ( - line.invoice_id.type - in [ - "out_refund", - "in_refund", - ] - and line.invoice_id.fiscal_document_type_id.code not in ["TD04", "TD08"] - ): - DettaglioLinea.PrezzoUnitario = -DettaglioLinea.PrezzoUnitario - DettaglioLinea.PrezzoTotale = -DettaglioLinea.PrezzoTotale - return DettaglioLinea - - def setDatiRiepilogo(self, invoice, body): - super(WizardExportFatturapa, self).setDatiRiepilogo(invoice, body) - for DatiRiepilogo in body.DatiBeniServizi.DatiRiepilogo: - if ( - invoice.type - in [ - "out_refund", - "in_refund", - ] - and invoice.fiscal_document_type_id.code not in ["TD04", "TD08"] - ): - DatiRiepilogo.ImponibileImporto = -DatiRiepilogo.ImponibileImporto - DatiRiepilogo.Imposta = -DatiRiepilogo.Imposta - return True - - def setDatiPagamento(self, invoice, body): - super(WizardExportFatturapa, self).setDatiPagamento(invoice, body) - for DatiPagamento in body.DatiPagamento: - if ( - invoice.type in ["out_refund", "in_refund"] - and invoice.fiscal_document_type_id.code not in ["TD04", "TD08"] - and DatiPagamento.ImportoPagamento - ): - DatiPagamento.ImportoPagamento = -DatiPagamento.ImportoPagamento - return True diff --git a/l10n_it_fatturapa_out_rc/wizard/wizard_export_fatturapa.py b/l10n_it_fatturapa_out_rc/wizard/wizard_export_fatturapa.py new file mode 100644 index 000000000000..0b6c85346375 --- /dev/null +++ b/l10n_it_fatturapa_out_rc/wizard/wizard_export_fatturapa.py @@ -0,0 +1,13 @@ +# Copyright 2021 Alex Comba - Agile Business Group +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import models + +from .efattura import EFatturaOut + + +class WizardExportFatturapa(models.TransientModel): + _inherit = "wizard.export.fatturapa" + + def _get_efattura_class(self): + return EFatturaOut diff --git a/setup/l10n_it_fatturapa_out_rc/odoo/addons/l10n_it_fatturapa_out_rc b/setup/l10n_it_fatturapa_out_rc/odoo/addons/l10n_it_fatturapa_out_rc new file mode 120000 index 000000000000..5060727cacdd --- /dev/null +++ b/setup/l10n_it_fatturapa_out_rc/odoo/addons/l10n_it_fatturapa_out_rc @@ -0,0 +1 @@ +../../../../l10n_it_fatturapa_out_rc \ No newline at end of file diff --git a/setup/l10n_it_fatturapa_out_rc/setup.py b/setup/l10n_it_fatturapa_out_rc/setup.py new file mode 100644 index 000000000000..28c57bb64031 --- /dev/null +++ b/setup/l10n_it_fatturapa_out_rc/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)