From 86ae01e5984c5e813318a782c9b2aa59dcb7c831 Mon Sep 17 00:00:00 2001 From: Simone Rubino Date: Mon, 31 Jul 2023 14:32:42 +0200 Subject: [PATCH 01/10] [COV] l10n_it_fatturapa_in: Company management The test's user like real users is logged in only one company at a time. That company is implicitly used in `search`es thanks to multi-company record rules. --- .../tests/fatturapa_common.py | 93 ++++++++++--------- 1 file changed, 50 insertions(+), 43 deletions(-) diff --git a/l10n_it_fatturapa_in/tests/fatturapa_common.py b/l10n_it_fatturapa_in/tests/fatturapa_common.py index 587d7a4e14bc..638c8c137656 100644 --- a/l10n_it_fatturapa_in/tests/fatturapa_common.py +++ b/l10n_it_fatturapa_in/tests/fatturapa_common.py @@ -1,5 +1,7 @@ import base64 +import operator import tempfile +from functools import reduce from odoo.modules import get_module_resource from odoo.tests import Form @@ -166,7 +168,7 @@ def run_wizard( wizard_form = Form( self.wizard_model.with_context( active_ids=[attach_id], active_model="fatturapa.attachment.in" - ).with_company(self.env.company) + ) ) wizard = wizard_form.save() return wizard.importFatturaPA() @@ -174,7 +176,7 @@ def run_wizard( wizard_form = Form( self.wizard_link_model.with_context( active_ids=[attach_id], active_model="fatturapa.attachment.in" - ).with_company(self.env.company) + ) ) wizard = wizard_form.save() if wiz_values: @@ -202,20 +204,35 @@ def run_wizard_multi(self, file_name_list, module_name=None): @classmethod def _setup_taxes(cls): - # duplicate US purchase taxes in our current country - for tax in cls.env["account.tax"].search( + company = cls.env.company + cls._copy_taxes_to_company(company) + cls.env.company.arrotondamenti_tax_id = cls.env["account.tax"].search( [ - ("country_id", "=", cls.env.ref("base.us").id), ("type_tax_use", "=", "purchase"), - ] - ): - tax_data = tax.sudo().copy_data()[0] - default_account_tax_purchase = cls.env["account.account"].search( - [ - ("company_id", "=", cls.env.company.id), - ("code", "=", "251000"), # Tax receivable - ] - ) + ("amount", "=", 0.0), + ], + order="sequence", + limit=1, + ) + + @classmethod + def _copy_taxes_to_company(cls, company): + """Copy specific taxes to `company`.""" + # Demo taxes have been created for another company, + # copy them in current company + external_ids = [ + "l10n_it_fatturapa.tax_22_acq", + "l10n_it_fatturapa.tax_00_minimi_acq", + ] + taxes_list = [cls.env.ref(external_id) for external_id in external_ids] + taxes = reduce( + operator.ior, + taxes_list, + ) + sudo_taxes = taxes.sudo() + taxes_values = [] + for tax in sudo_taxes: + tax_data = tax.copy_data()[0] invoice_rpls = [ ( 0, @@ -223,7 +240,7 @@ def _setup_taxes(cls): { "factor_percent": rpl.factor_percent, "repartition_type": rpl.repartition_type, - "account_id": default_account_tax_purchase.id + "account_id": cls.tax_receivable_account.id if rpl.account_id else None, }, @@ -237,7 +254,7 @@ def _setup_taxes(cls): { "factor_percent": rpl.factor_percent, "repartition_type": rpl.repartition_type, - "account_id": default_account_tax_purchase.id + "account_id": cls.tax_receivable_account.id if rpl.account_id else None, }, @@ -246,13 +263,14 @@ def _setup_taxes(cls): ] tax_data.update( { - "country_id": cls.env.company.country_id.id, - "company_id": cls.env.company.id, + "country_id": company.country_id.id, + "company_id": company.id, "invoice_repartition_line_ids": invoice_rpls, "refund_repartition_line_ids": refund_rpls, } ) - tax.create(tax_data) + taxes_values.append(tax_data) + return cls.env["account.tax"].create(taxes_values) @classmethod def _setup_accounts(cls): @@ -279,15 +297,6 @@ def _setup_accounts(cls): ) .id ) - cls.env.company.arrotondamenti_tax_id = cls.env["account.tax"].search( - [ - ("type_tax_use", "=", "purchase"), - ("amount", "=", 0.0), - ("company_id", "=", cls.env.company.id), - ], - order="sequence", - limit=1, - ) cls.env.company.arrotondamenti_attivi_account_id = ( arrotondamenti_attivi_account_id ) @@ -308,6 +317,11 @@ def _setup_accounts(cls): ) .id ) + cls.tax_receivable_account = cls.env["account.account"].search( + [ + ("code", "=", "251000"), # Tax receivable + ] + ) @classmethod def _set_it_user(cls): @@ -337,8 +351,9 @@ def setUpClass(cls): currency_id=cls.env.ref("base.EUR").id, country_id=cls.env.ref("base.it").id, ) - cls.env.user.company_ids |= cls.company_data_it["company"] - cls.env.company = cls.company_data_it["company"] + it_company = cls.company_data_it["company"] + cls.env.user.company_id = it_company + cls.env.user.company_ids = it_company cls.env["res.lang"]._activate_lang("it_IT") # we need a fiscal position in the current country @@ -353,19 +368,11 @@ def setUpClass(cls): cls._setup_taxes() cls._setup_accounts() - cls.wizard_model = cls.env["wizard.import.fatturapa"].with_company( - cls.env.company - ) - cls.wizard_link_model = cls.env["wizard.link.to.invoice"].with_company( - cls.env.company - ) - cls.wizard_link_inv_line_model = cls.env[ - "wizard.link.to.invoice.line" - ].with_company(cls.env.company) - cls.attach_model = cls.env["fatturapa.attachment.in"].with_company( - cls.env.company - ) - cls.invoice_model = cls.env["account.move"].with_company(cls.env.company) + cls.wizard_model = cls.env["wizard.import.fatturapa"] + cls.wizard_link_model = cls.env["wizard.link.to.invoice"] + cls.wizard_link_inv_line_model = cls.env["wizard.link.to.invoice.line"] + cls.attach_model = cls.env["fatturapa.attachment.in"] + cls.invoice_model = cls.env["account.move"] cls.headphones = cls.env.ref("product.product_product_7_product_template") cls.imac = cls.env.ref("product.product_product_8_product_template") cls.service = cls.env.ref("product.product_product_1") From a45e97c598fe380bf7008f07efca23a3502b8e50 Mon Sep 17 00:00:00 2001 From: Simone Rubino Date: Fri, 28 Jul 2023 15:04:35 +0200 Subject: [PATCH 02/10] [REF] l10n_it_fatturapa_in: Allow overrides for importing invoices Also allow to create withholding taxes in other tests --- .../tests/fatturapa_common.py | 193 ++++++-- .../tests/test_import_fatturapa_xml.py | 118 +---- .../wizard/wizard_import_fatturapa.py | 463 +++++++++++------- 3 files changed, 447 insertions(+), 327 deletions(-) diff --git a/l10n_it_fatturapa_in/tests/fatturapa_common.py b/l10n_it_fatturapa_in/tests/fatturapa_common.py index 638c8c137656..edb6536dba10 100644 --- a/l10n_it_fatturapa_in/tests/fatturapa_common.py +++ b/l10n_it_fatturapa_in/tests/fatturapa_common.py @@ -28,7 +28,7 @@ def create_wt(cls): { "name": "1040", "code": "1040", - "account_receivable_id": cls.payable_account_id, + "account_receivable_id": cls.receivable_account.id, "account_payable_id": cls.payable_account_id, "payment_term": cls.env.ref( "account.account_payment_term_immediate" @@ -44,7 +44,7 @@ def create_wt_23_20(cls): { "name": "2320", "code": "2320", - "account_receivable_id": cls.payable_account_id, + "account_receivable_id": cls.receivable_account.id, "account_payable_id": cls.payable_account_id, "payment_term": cls.env.ref( "account.account_payment_term_immediate" @@ -60,7 +60,7 @@ def create_wt_23_50(cls): { "name": "2320", "code": "2320", - "account_receivable_id": cls.payable_account_id, + "account_receivable_id": cls.receivable_account.id, "account_payable_id": cls.payable_account_id, "payment_term": cls.env.ref( "account.account_payment_term_immediate" @@ -76,7 +76,7 @@ def create_wt_26_20q(cls): { "name": "2620q", "code": "2620q", - "account_receivable_id": cls.payable_account_id, + "account_receivable_id": cls.receivable_account.id, "account_payable_id": cls.payable_account_id, "payment_term": cls.env.ref( "account.account_payment_term_immediate" @@ -92,7 +92,7 @@ def create_wt_26_40q(cls): { "name": "2640q", "code": "2640q", - "account_receivable_id": cls.payable_account_id, + "account_receivable_id": cls.receivable_account.id, "account_payable_id": cls.payable_account_id, "payment_term": cls.env.ref( "account.account_payment_term_immediate" @@ -108,7 +108,7 @@ def create_wt_27_20q(cls): { "name": "2720q", "code": "2720q", - "account_receivable_id": cls.payable_account_id, + "account_receivable_id": cls.receivable_account.id, "account_payable_id": cls.payable_account_id, "payment_term": cls.env.ref( "account.account_payment_term_immediate" @@ -125,7 +125,7 @@ def create_wt_4q(cls): "name": "4q", "code": "4q", "wt_types": "enasarco", - "account_receivable_id": cls.payable_account_id, + "account_receivable_id": cls.receivable_account.id, "account_payable_id": cls.payable_account_id, "payment_term": cls.env.ref( "account.account_payment_term_immediate" @@ -135,6 +135,134 @@ def create_wt_4q(cls): } ) + @classmethod + def create_misc_journal(cls): + return cls.env["account.journal"].create( + { + "name": "Test Miscellaneous Journal", + "code": "TMJ", + "type": "general", + } + ) + + def create_wt_115_r(self): + return self.env["withholding.tax"].create( + { + "name": "1040 R", + "code": "1040R", + "account_receivable_id": self.receivable_account.id, + "account_payable_id": self.payable_account.id, + "journal_id": self.misc_journal.id, + "payment_term": self.env.ref("account.account_payment_term_advance").id, + "wt_types": "ritenuta", + "payment_reason_id": self.env.ref("l10n_it_payment_reason.r").id, + "rate_ids": [ + ( + 0, + 0, + { + "tax": 11.50, + "base": 1.0, + }, + ) + ], + } + ) + + def create_wt_enasarco_115_a(self): + return self.env["withholding.tax"].create( + { + "name": "1040/3", + "code": "1040", + "account_receivable_id": self.receivable_account.id, + "account_payable_id": self.payable_account.id, + "journal_id": self.misc_journal.id, + "payment_term": self.env.ref("account.account_payment_term_advance").id, + "wt_types": "ritenuta", + "payment_reason_id": self.env.ref("l10n_it_payment_reason.a").id, + "rate_ids": [ + ( + 0, + 0, + { + "tax": 11.50, + "base": 1.0, + }, + ) + ], + } + ) + + def create_wt_enasarco_85_r(self): + return self.env["withholding.tax"].create( + { + "name": "Enasarco 8,50", + "code": "TC07", + "account_receivable_id": self.receivable_account.id, + "account_payable_id": self.payable_account.id, + "journal_id": self.misc_journal.id, + "payment_term": self.env.ref("account.account_payment_term_advance").id, + "wt_types": "enasarco", + "payment_reason_id": self.env.ref("l10n_it_payment_reason.r").id, + "rate_ids": [ + ( + 0, + 0, + { + "tax": 8.5, + "base": 1.0, + }, + ) + ], + } + ) + + def create_wt_enasarco_157_r(self): + return self.env["withholding.tax"].create( + { + "name": "Enasarco", + "code": "TC07", + "account_receivable_id": self.receivable_account.id, + "account_payable_id": self.payable_account.id, + "journal_id": self.misc_journal.id, + "payment_term": self.env.ref("account.account_payment_term_advance").id, + "wt_types": "enasarco", + "payment_reason_id": self.env.ref("l10n_it_payment_reason.r").id, + "rate_ids": [ + ( + 0, + 0, + { + "tax": 1.57, + "base": 1.0, + }, + ) + ], + } + ) + + @classmethod + def create_receivable_account(cls): + return cls.env["account.account"].create( + { + "name": "Test WH tax", + "code": "whtaxrec2", + "account_type": "asset_receivable", + "reconcile": True, + } + ) + + @classmethod + def create_payable_account(cls): + return cls.env["account.account"].create( + { + "name": "Test WH tax", + "code": "whtaxpay2", + "account_type": "liability_payable", + "reconcile": True, + } + ) + def create_res_bank(self): return self.env["res.bank"].create( { @@ -189,19 +317,28 @@ def run_wizard( def run_wizard_multi(self, file_name_list, module_name=None): if module_name is None: module_name = "l10n_it_fatturapa_in" - active_ids = [] - for file_name in file_name_list: - active_ids.append( - self.attach_model.create( - { - "name": file_name, - "datas": self.getFile(file_name, module_name)[1], - } - ).id - ) - wizard = self.wizard_model.with_context(active_ids=active_ids).create({}) + + attachments = self.attach_model.create( + [ + { + "name": file_name, + "datas": self.getFile(file_name, module_name=module_name)[1], + } + for file_name in file_name_list + ] + ) + + wizard = self.wizard_model.with_context( + active_model=attachments._name, + active_ids=attachments.ids, + ).create({}) + return wizard.importFatturaPA() + @classmethod + def _setup_journals(cls): + cls.misc_journal = cls.create_misc_journal() + @classmethod def _setup_taxes(cls): company = cls.env.company @@ -303,20 +440,9 @@ def _setup_accounts(cls): cls.env.company.arrotondamenti_passivi_account_id = ( arrotondamenti_passivi_account_id ) - cls.payable_account_id = ( - cls.env["account.account"] - .search( - [ - ( - "account_type", - "=", - "liability_payable", - ) - ], - limit=1, - ) - .id - ) + cls.payable_account = cls.create_payable_account() + cls.payable_account_id = cls.payable_account.id + cls.receivable_account = cls.create_receivable_account() cls.tax_receivable_account = cls.env["account.account"].search( [ ("code", "=", "251000"), # Tax receivable @@ -365,8 +491,9 @@ def setUpClass(cls): } ) - cls._setup_taxes() cls._setup_accounts() + cls._setup_journals() + cls._setup_taxes() cls.wizard_model = cls.env["wizard.import.fatturapa"] cls.wizard_link_model = cls.env["wizard.link.to.invoice"] diff --git a/l10n_it_fatturapa_in/tests/test_import_fatturapa_xml.py b/l10n_it_fatturapa_in/tests/test_import_fatturapa_xml.py index 3e94611f6350..7168238e115c 100644 --- a/l10n_it_fatturapa_in/tests/test_import_fatturapa_xml.py +++ b/l10n_it_fatturapa_in/tests/test_import_fatturapa_xml.py @@ -209,9 +209,9 @@ def test_08_xml_import(self): def test_08_xml_import_no_account(self): """Check that a useful error message is raised when - the credit account is missing in purchase journal.""" + the credit account is missing in journal.""" company = self.env.company - journal = self.wizard_model.get_purchase_journal(company) + journal = self.wizard_model.get_journal(company) journal_account = journal.default_account_id journal.default_account_id = False @@ -1057,116 +1057,10 @@ def setUp(self): self.invoice_model = self.env["account.move"] def test_01_xml_import_enasarco(self): - account_payable = self.env["account.account"].create( - { - "name": "Test WH tax", - "code": "whtaxpay2", - "account_type": "liability_payable", - "reconcile": True, - } - ) - account_receivable = self.env["account.account"].create( - { - "name": "Test WH tax", - "code": "whtaxrec2", - "account_type": "asset_receivable", - "reconcile": True, - } - ) - misc_journal = self.env["account.journal"].search( - [ - ("company_id", "=", self.env.company.id), - ("code", "=", "MISC"), - ] - ) - self.env["withholding.tax"].create( - { - "name": "Enasarco", - "code": "TC07", - "account_receivable_id": account_receivable.id, - "account_payable_id": account_payable.id, - "journal_id": misc_journal.id, - "payment_term": self.env.ref("account.account_payment_term_advance").id, - "wt_types": "enasarco", - "payment_reason_id": self.env.ref("l10n_it_payment_reason.r").id, - "rate_ids": [ - ( - 0, - 0, - { - "tax": 1.57, - "base": 1.0, - }, - ) - ], - } - ) - self.env["withholding.tax"].create( - { - "name": "Enasarco 8,50", - "code": "TC07", - "account_receivable_id": account_receivable.id, - "account_payable_id": account_payable.id, - "journal_id": misc_journal.id, - "payment_term": self.env.ref("account.account_payment_term_advance").id, - "wt_types": "enasarco", - "payment_reason_id": self.env.ref("l10n_it_payment_reason.r").id, - "rate_ids": [ - ( - 0, - 0, - { - "tax": 8.5, - "base": 1.0, - }, - ) - ], - } - ) - self.env["withholding.tax"].create( - { - "name": "1040/3", - "code": "1040", - "account_receivable_id": account_receivable.id, - "account_payable_id": account_payable.id, - "journal_id": misc_journal.id, - "payment_term": self.env.ref("account.account_payment_term_advance").id, - "wt_types": "ritenuta", - "payment_reason_id": self.env.ref("l10n_it_payment_reason.a").id, - "rate_ids": [ - ( - 0, - 0, - { - "tax": 11.50, - "base": 1.0, - }, - ) - ], - } - ) - self.env["withholding.tax"].create( - { - "name": "1040 R", - "code": "1040R", - "account_receivable_id": account_receivable.id, - "account_payable_id": account_payable.id, - "journal_id": misc_journal.id, - "payment_term": self.env.ref("account.account_payment_term_advance").id, - "wt_types": "ritenuta", - "payment_reason_id": self.env.ref("l10n_it_payment_reason.r").id, - "rate_ids": [ - ( - 0, - 0, - { - "tax": 11.50, - "base": 1.0, - }, - ) - ], - } - ) + self.create_wt_enasarco_157_r() + self.create_wt_enasarco_85_r() + self.create_wt_enasarco_115_a() + self.create_wt_115_r() # case with ENASARCO only in DatiCassaPrevidenziale and not in DatiRitenuta. # This should not happen, but it is valid for SDI res = self.run_wizard("test01", "IT05979361218_014.xml") diff --git a/l10n_it_fatturapa_in/wizard/wizard_import_fatturapa.py b/l10n_it_fatturapa_in/wizard/wizard_import_fatturapa.py index 2261efcb1b17..61b3db74d8ce 100644 --- a/l10n_it_fatturapa_in/wizard/wizard_import_fatturapa.py +++ b/l10n_it_fatturapa_in/wizard/wizard_import_fatturapa.py @@ -8,6 +8,7 @@ from odoo import api, fields, models, registry from odoo.exceptions import UserError from odoo.fields import first +from odoo.osv import expression from odoo.tools import float_is_zero, frozendict from odoo.tools.translate import _ @@ -66,9 +67,28 @@ class WizardImportFatturapa(models.TransientModel): help='Decimal digits used for discount field. See "Prices decimal digits".', ) + def _get_selected_model(self): + context = self.env.context + model_name = context.get("active_model") + return model_name + + def _get_selected_records(self): + context = self.env.context + ids = context.get("active_ids", []) + model_name = self._get_selected_model() + attachments = self.env[model_name].browse(ids) + return attachments + + def _check_attachment(self, attachment): + if attachment.in_invoice_ids: + raise UserError(_("File %s is linked to bills yet.", attachment.name)) + + def _extract_supplier(self, fatturapa_attachment): + return fatturapa_attachment.xml_supplier_id + @api.model - def default_get(self, fields): - res = super(WizardImportFatturapa, self).default_get(fields) + def default_get(self, fields_list): + res = super().default_get(fields_list) res["price_decimal_digits"] = self.env["decimal.precision"].precision_get( "Product Price" ) @@ -79,18 +99,12 @@ def default_get(self, fields): "Discount" ) res["e_invoice_detail_level"] = "2" - fatturapa_attachment_ids = self.env.context.get("active_ids", False) - fatturapa_attachment_obj = self.env["fatturapa.attachment.in"] - partners = self.env["res.partner"] - for fatturapa_attachment_id in fatturapa_attachment_ids: - fatturapa_attachment = fatturapa_attachment_obj.browse( - fatturapa_attachment_id - ) - if fatturapa_attachment.in_invoice_ids: - raise UserError( - _("File %s is linked to bills yet.") % fatturapa_attachment.name - ) - partners |= fatturapa_attachment.xml_supplier_id + + fatturapa_attachments = self._get_selected_records() + partners = self.env["res.partner"].browse() + for fatturapa_attachment in fatturapa_attachments: + self._check_attachment(fatturapa_attachment) + partners |= self._extract_supplier(fatturapa_attachment) if len(partners) == 1: res["e_invoice_detail_level"] = partners[0].e_invoice_detail_level if partners[0].e_invoice_price_decimal_digits >= 0: @@ -432,7 +446,6 @@ def getCarrirerPartner(self, Carrier): partner_model.browse(partner_id).write(vals) return partner_id - # move_line.tax_ids def _prepare_generic_line_data(self, line): retLine = {} account_taxes = self.get_account_taxes(line.AliquotaIVA, line.Natura) @@ -442,84 +455,118 @@ def _prepare_generic_line_data(self, line): retLine["tax_ids"] = [fields.Command.clear()] return retLine - def get_account_taxes(self, AliquotaIVA, Natura): - account_tax_model = self.env["account.tax"] - # check if a default tax exists and generate def_purchase_tax object - ir_values = self.env["ir.default"] - company_id = self.env.company.id - supplier_taxes_ids = ir_values.get( - "product.product", "supplier_taxes_id", company_id=company_id + def _get_default_product_taxes(self, tax_field_name): + """Return default tax for field `product.product.`.""" + company = self.env.company + default_taxes_ids = self.env["ir.default"].get( + "product.product", + tax_field_name, + company_id=company.id, ) - def_purchase_tax = False - if supplier_taxes_ids: - def_purchase_tax = account_tax_model.browse(supplier_taxes_ids)[0] - if float(AliquotaIVA) == 0.0 and Natura: - account_taxes = account_tax_model.search( + tax_model = self.env["account.tax"] + if default_taxes_ids is not None: + default_taxes = tax_model.browse(default_taxes_ids) + default_tax = first(default_taxes) + else: + default_tax = tax_model.browse() + return default_tax + + def _get_account_tax_domain(self, amount): + return [ + ("type_tax_use", "=", "purchase"), + ("amount", "=", amount), + ] + + def _get_zero_kind_account_tax(self, Natura): + tax_amount = 0 + tax_domain = self._get_account_tax_domain(tax_amount) + tax_domain = expression.AND( + [ + tax_domain, [ - ("type_tax_use", "=", "purchase"), ("kind_id.code", "=", Natura), - ("amount", "=", 0.0), ], - order="sequence", - ) - if not account_taxes: - self.log_inconsistency( - _( - "No tax with percentage " - "%(percentage)s and nature %(nature)s found. Please configure this tax." - ) - % {"percentage": AliquotaIVA, "nature": Natura} + ] + ) + account_taxes = self.env["account.tax"].search( + tax_domain, + order="sequence", + ) + account_tax = first(account_taxes) + if not account_taxes: + self.log_inconsistency( + _( + "No tax with percentage " + "%(percentage)s and nature %(nature)s found. Please configure this tax.", + percentage=tax_amount, + nature=Natura, ) - if len(account_taxes) > 1: - self.log_inconsistency( - _( - "Too many taxes with percentage " - "%(percentage)s and nature %(nature)s found. " - "Tax %(tax)s with lower priority has " - "been set on invoice lines." - ) - % { - "percentage": AliquotaIVA, - "nature": Natura, - "tax": account_taxes[0].description, - } + ) + elif len(account_taxes) > 1: + self.log_inconsistency( + _( + "Too many taxes with percentage " + "%(percentage)s and nature %(nature)s found. " + "Tax %(tax)s with lower priority has " + "been set on invoice lines.", + percentage=tax_amount, + nature=Natura, + tax=account_tax.description, ) - else: - account_taxes = account_tax_model.search( + ) + return account_tax + + def _get_amount_account_tax(self, tax_amount): + tax_domain = self._get_account_tax_domain(tax_amount) + tax_domain = expression.AND( + [ + tax_domain, [ - ("type_tax_use", "=", "purchase"), - ("amount", "=", float(AliquotaIVA)), ("price_include", "=", False), # partially deductible VAT must be set by user ("children_tax_ids", "=", False), ], - order="sequence", - ) - if not account_taxes: - self.log_inconsistency( - _( - "XML contains tax with percentage '%s' " - "but it does not exist in your system" - ) - % AliquotaIVA + ] + ) + account_taxes = self.env["account.tax"].search( + tax_domain, + order="sequence", + ) + account_tax = first(account_taxes) + if not account_taxes: + self.log_inconsistency( + _( + "XML contains tax with percentage '%s' " + "but it does not exist in your system", + tax_amount, ) - # check if there are multiple taxes with - # same percentage - if len(account_taxes) > 1: - # just logging because this is an usual case: see split payment - _logger.warning( - _( - "Too many taxes with percentage equals " - "to '%s'.\nFix it if required" - ) - % AliquotaIVA + ) + # check if there are multiple taxes with + # same percentage + elif len(account_taxes) > 1: + # just logging because this is an usual case: see split payment + _logger.warning( + _( + "Too many taxes with percentage equals " + "to '%s'.\nFix it if required", + tax_amount, ) - # if there are multiple taxes with same percentage - # and there is a default tax with this percentage, - # set taxes list equal to supplier_taxes_id, loaded before - if def_purchase_tax and def_purchase_tax.amount == (float(AliquotaIVA)): - account_taxes = def_purchase_tax - return account_taxes + ) + # if there are multiple taxes with same percentage + # and there is a default tax with this percentage, + # set taxes list equal to supplier_taxes_id + default_tax = self._get_default_product_taxes("supplier_taxes_id") + if default_tax and default_tax.amount == tax_amount: + account_tax = default_tax + return account_tax + + def get_account_taxes(self, AliquotaIVA, Natura): + tax_amount = float(AliquotaIVA) + if tax_amount == 0.0 and Natura: + account_tax = self._get_zero_kind_account_tax(Natura) + else: + account_tax = self._get_amount_account_tax(tax_amount) + return account_tax def get_line_product(self, line, partner): product = self.env["product.product"].browse() @@ -945,19 +992,31 @@ def set_StabileOrganizzazione(self, CedentePrestatore, invoice): CedentePrestatore.StabileOrganizzazione.Nazione ) - def get_purchase_journal(self, company): - journal_model = self.env["account.journal"] - journals = journal_model.search( - [("type", "=", "purchase"), ("company_id", "=", company.id)], limit=1 + def _get_journal_domain(self, company): + return [ + ("type", "=", "purchase"), + ("company_id", "=", company.id), + ] + + def get_journal(self, company): + domain = self._get_journal_domain(company) + journal = self.env["account.journal"].search( + domain, + limit=1, ) - if not journals: - raise UserError( - _( - "Define a purchase journal for this company: '%(name)s' (id: %(id)s)." - ) - % {"name": company.name, "id": company.id} + if not journal: + exception = self._get_missing_journal_exception(company) + raise exception + return journal + + def _get_missing_journal_exception(self, company): + return UserError( + _( + "Define a purchase journal for this company: '%(name)s' (id: %(id)s).", + name=company.name, + id=company.id, ) - return journals[0] + ) def create_e_invoice_line(self, line): vals = { @@ -1009,7 +1068,7 @@ def get_credit_account(self, product=None): Try to get default credit account for invoice line looking in 1) product (if provided) - 2) purchase journal + 2) journal 3) company default. :param product: Product whose expense account will be used @@ -1024,8 +1083,8 @@ def get_credit_account(self, product=None): credit_account = accounts_dict["expense"] company = self.env.company - # Search in purchase journal - journal = self.get_purchase_journal(company) + # Search in journal + journal = self.get_journal(company) if not credit_account: credit_account = journal.default_account_id @@ -1052,40 +1111,69 @@ def get_credit_account(self, product=None): return credit_account - def invoiceCreate(self, fatt, fatturapa_attachment, FatturaBody, partner_id): - partner_model = self.env["res.partner"] - invoice_model = self.env["account.move"] - currency_model = self.env["res.currency"] - ftpa_doctype_model = self.env["fiscal.document.type"] - rel_docs_model = self.env["fatturapa.related_document_type"] - - company = self.env.company - partner = partner_model.browse(partner_id) - + def _get_currency(self, FatturaBody): # currency 2.1.1.2 - currency = currency_model.search( - [("name", "=", FatturaBody.DatiGenerali.DatiGeneraliDocumento.Divisa)] + currency_code = FatturaBody.DatiGenerali.DatiGeneraliDocumento.Divisa + currency = self.env["res.currency"].search( + [ + ("name", "=", currency_code), + ] ) if not currency: raise UserError( - _("No currency found with code %s.") - % FatturaBody.DatiGenerali.DatiGeneraliDocumento.Divisa + _( + "No currency found with code %s.", + currency_code, + ) ) - purchase_journal = self.get_purchase_journal(company) - credit_account = self.get_credit_account() + return currency + + def _get_fiscal_document_type(self, FatturaBody): + fiscal_document_type_code = ( + FatturaBody.DatiGenerali.DatiGeneraliDocumento.TipoDocumento + ) + if fiscal_document_type_code: + fiscal_document_type = self.env["fiscal.document.type"].search( + [ + ("code", "=", fiscal_document_type_code), + ], + limit=1, + ) + if not fiscal_document_type: + raise UserError( + _( + "Document type %s not handled.", + fiscal_document_type_code, + ) + ) + else: + fiscal_document_type = self.env["fiscal.document.type"].browse() + return fiscal_document_type + + def _get_invoice_type(self, fiscal_document_type): + if fiscal_document_type.code == "TD04": + invoice_type = "in_refund" + else: + invoice_type = "in_invoice" + return invoice_type + + def _get_received_date(self, attachment): + received_date = attachment.e_invoice_received_date + if not received_date: + received_date = attachment.create_date + received_date = received_date.date() + return received_date + + def _prepare_invoice_values(self, fatt, fatturapa_attachment, FatturaBody, partner): + company = self.env.company + currency = self._get_currency(FatturaBody) + purchase_journal = self.get_journal(company) comment = "" + # 2.1.1 - docType_id = False - invtype = "in_invoice" - docType = FatturaBody.DatiGenerali.DatiGeneraliDocumento.TipoDocumento - if docType: - docType_record = ftpa_doctype_model.search([("code", "=", docType)]) - if docType_record: - docType_id = docType_record[0].id - else: - raise UserError(_("Document type %s not handled.") % docType) - if docType == "TD04": - invtype = "in_refund" + fiscal_document_type = self._get_fiscal_document_type(FatturaBody) + invoice_type = self._get_invoice_type(fiscal_document_type) + # 2.1.1.11 causLst = FatturaBody.DatiGenerali.DatiGeneraliDocumento.Causale if causLst: @@ -1094,24 +1182,17 @@ def invoiceCreate(self, fatt, fatturapa_attachment, FatturaBody, partner_id): if comment: comment = "
" + comment + "
" - if fatturapa_attachment.e_invoice_received_date: - e_invoice_received_date = ( - fatturapa_attachment.e_invoice_received_date.date() - ) - else: - e_invoice_received_date = fatturapa_attachment.create_date.date() + e_invoice_received_date = self._get_received_date(fatturapa_attachment) e_invoice_date = datetime.strptime( FatturaBody.DatiGenerali.DatiGeneraliDocumento.Data, "%Y-%m-%d" ).date() delivery_partner_id = partner.address_get(["delivery"])["delivery"] - delivery_partner = partner_model.browse(delivery_partner_id) - fiscal_position_id = ( - self.env["account.fiscal.position"] - ._get_fiscal_position(partner, delivery=delivery_partner) - .id - or False + delivery_partner = self.env["res.partner"].browse(delivery_partner_id) + fiscal_position = self.env["account.fiscal.position"]._get_fiscal_position( + partner, + delivery=delivery_partner, ) invoice_data = { @@ -1119,14 +1200,14 @@ def invoiceCreate(self, fatt, fatturapa_attachment, FatturaBody, partner_id): "date": e_invoice_received_date if company.in_invoice_registration_date == "rec_date" else e_invoice_date, - "fiscal_document_type_id": docType_id, + "fiscal_document_type_id": fiscal_document_type.id, "sender": fatt.FatturaElettronicaHeader.SoggettoEmittente or False, - "move_type": invtype, - "partner_id": partner_id, - "currency_id": currency[0].id, + "move_type": invoice_type, + "partner_id": partner.id, + "currency_id": currency.id, "journal_id": purchase_journal.id, # 'origin': xmlData.datiOrdineAcquisto, - "fiscal_position_id": fiscal_position_id, + "fiscal_position_id": fiscal_position.id, "invoice_payment_term_id": partner.property_supplier_payment_term_id.id, "company_id": company.id, "fatturapa_attachment_in_id": fatturapa_attachment.id, @@ -1136,24 +1217,42 @@ def invoiceCreate(self, fatt, fatturapa_attachment, FatturaBody, partner_id): # 2.1.1.12 self.set_art73(FatturaBody, invoice_data) - # 2.1.1.5 - wt_founds = self.set_withholding_tax(FatturaBody, invoice_data) - self.set_e_invoice_lines(FatturaBody, invoice_data) + return invoice_data - invoice = invoice_model.create(invoice_data) + def invoiceCreate(self, fatt, fatturapa_attachment, FatturaBody, partner_id): + partner_model = self.env["res.partner"] + partner = partner_model.browse(partner_id) + invoice_data = self._prepare_invoice_values( + fatt, + fatturapa_attachment, + FatturaBody, + partner, + ) + + # 2.1.1.5 + found_withholding_taxes = self.set_withholding_tax(FatturaBody, invoice_data) + + invoice = self.env["account.move"].create(invoice_data) + credit_account = self.get_credit_account() invoice_lines = [] # 2.2.1 invoice_lines.extend( self.set_invoice_line_ids( - FatturaBody, credit_account.id, partner, wt_founds, invoice + FatturaBody, + credit_account.id, + partner, + found_withholding_taxes, + invoice, ) ) # 2.1.1.7 invoice_lines.extend( - self.set_welfares_fund(FatturaBody, credit_account.id, invoice, wt_founds) + self.set_welfares_fund( + FatturaBody, credit_account.id, invoice, found_withholding_taxes + ) ) # 2.1.1.10 @@ -1185,7 +1284,7 @@ def invoiceCreate(self, fatt, fatturapa_attachment, FatturaBody, partner_id): doc_datas = self._prepareRelDocsLine(invoice.id, rel_doc, rel_doc_key) for doc_data in doc_datas: # Note for v12: must take advantage of batch creation - rel_docs_model.create(doc_data) + self.env["fatturapa.related_document_type"].create(doc_data) # 2.1.7 self.set_activity_progress(FatturaBody, invoice) @@ -1478,53 +1577,58 @@ def set_withholding_tax(self, FatturaBody, invoice_data): Withholdings = FatturaBody.DatiGenerali.DatiGeneraliDocumento.DatiRitenuta if not Withholdings: return None - invoice_data["ftpa_withholding_ids"] = [] - wt_founds = [] + + withholding_tax_model = self.env["withholding.tax"] + found_withholding_taxes = withholding_tax_model.browse() + e_withholding_taxes_values = [] for Withholding in Withholdings: - wts = self.env["withholding.tax"].search( - [("payment_reason_id.code", "=", Withholding.CausalePagamento)] + payment_reason_code = Withholding.CausalePagamento + withholding_taxes = withholding_tax_model.search( + [("payment_reason_id.code", "=", payment_reason_code)], ) - if not wts: + if not withholding_taxes: raise UserError( _( "The bill contains withholding tax with " "payment reason %s, " "but such a tax is not found in your system. Please " - "set it." + "set it.", + payment_reason_code, ) - % Withholding.CausalePagamento ) - for wt in wts: + withholding_tax_amount = Withholding.AliquotaRitenuta + e_withholding_tax_type = Withholding.TipoRitenuta + withholding_tax_type = WT_CODES_MAPPING[e_withholding_tax_type] + for withholding_tax in withholding_taxes: if ( - wt.tax == float(Withholding.AliquotaRitenuta) - and WT_CODES_MAPPING[Withholding.TipoRitenuta] == wt.wt_types + withholding_tax.tax == float(withholding_tax_amount) + and withholding_tax_type == withholding_tax.wt_types ): - wt_founds.append(wt) + found_withholding_taxes |= withholding_tax break else: raise UserError( _( "No withholding tax found with document payment " - "reason %(reason)s rate %(rate)s and type %(type)s." + "reason %(reason)s rate %(rate)s and type %(type)s.", + reason=payment_reason_code, + rate=withholding_tax_amount, + type=withholding_tax_type, ) - % { - "reason": Withholding.CausalePagamento, - "rate": Withholding.AliquotaRitenuta, - "type": WT_CODES_MAPPING[Withholding.TipoRitenuta], - } ) - invoice_data["ftpa_withholding_ids"].append( - ( - 0, - 0, - { - "name": Withholding.TipoRitenuta, - "amount": Withholding.ImportoRitenuta, - }, - ) - ) - return wt_founds + + e_withholding_tax_values = { + "name": e_withholding_tax_type, + "amount": Withholding.ImportoRitenuta, + } + e_withholding_taxes_values.append(e_withholding_tax_values) + + invoice_data["ftpa_withholding_ids"] = [ + (0, 0, withholding_tax_values) + for withholding_tax_values in e_withholding_taxes_values + ] + return found_withholding_taxes def set_welfares_fund(self, FatturaBody, credit_account_id, invoice, wt_founds): invoice_line_model = self.env["account.move.line"] @@ -1732,7 +1836,7 @@ def check_invoice_amount(self, invoice, FatturaElettronicaBody): ) def get_invoice_obj(self, fatturapa_attachment): - xml_string = fatturapa_attachment.get_xml_string() + xml_string = fatturapa_attachment.ir_attachment_id.get_xml_string() return efattura.CreateFromDocument(xml_string) def create_and_get_line_id(self, invoice_line_ids, invoice_line_model, upd_vals): @@ -1774,8 +1878,6 @@ def _restore_original_precision(self, precision, original_precision): def importFatturaPA(self): self.ensure_one() - fatturapa_attachment_obj = self.env["fatturapa.attachment.in"] - fatturapa_attachment_ids = self.env.context.get("active_ids", False) ( price_precision, @@ -1797,14 +1899,11 @@ def importFatturaPA(self): new_invoices = [] # convert to dict in order to be able to modify context + fatturapa_attachments = self._get_selected_records() self.env.context = dict(self.env.context) - for fatturapa_attachment_id in fatturapa_attachment_ids: + for fatturapa_attachment in fatturapa_attachments: self.reset_inconsistencies() - fatturapa_attachment = fatturapa_attachment_obj.browse( - fatturapa_attachment_id - ) - if fatturapa_attachment.in_invoice_ids: - raise UserError(_("File is linked to bills yet.")) + self._check_attachment(fatturapa_attachment) fatt = self.get_invoice_obj(fatturapa_attachment) cedentePrestatore = fatt.FatturaElettronicaHeader.CedentePrestatore From f68a2531dbd9313042c9017ab5dad323f390c052 Mon Sep 17 00:00:00 2001 From: Borruso Date: Wed, 28 Apr 2021 15:18:54 +0200 Subject: [PATCH 03/10] [ADD] l10n_it_fatturapa_import_zip: aggiunto modulo che permette di importare in massa e-fatture in/out --- l10n_it_fatturapa_import_zip/README.rst | 95 ++++ l10n_it_fatturapa_import_zip/__init__.py | 2 + l10n_it_fatturapa_import_zip/__manifest__.py | 28 + l10n_it_fatturapa_import_zip/i18n/it.po | 532 ++++++++++++++++++ .../models/__init__.py | 2 + .../models/account_invoice.py | 20 + .../models/attachment.py | 206 +++++++ .../readme/CONTRIBUTORS.rst | 2 + .../readme/DESCRIPTION.rst | 7 + l10n_it_fatturapa_import_zip/readme/USAGE.rst | 11 + .../security/ir.model.access.csv | 2 + .../security/rules.xml | 13 + .../static/description/index.html | 443 +++++++++++++++ .../tests/__init__.py | 1 + .../tests/data/xml_import.zip | Bin 0 -> 89808 bytes .../tests/test_import_zip.py | 293 ++++++++++ .../views/account_invoice_views.xml | 23 + .../views/attachment_views.xml | 180 ++++++ 18 files changed, 1860 insertions(+) create mode 100644 l10n_it_fatturapa_import_zip/README.rst create mode 100644 l10n_it_fatturapa_import_zip/__init__.py create mode 100644 l10n_it_fatturapa_import_zip/__manifest__.py create mode 100644 l10n_it_fatturapa_import_zip/i18n/it.po create mode 100644 l10n_it_fatturapa_import_zip/models/__init__.py create mode 100644 l10n_it_fatturapa_import_zip/models/account_invoice.py create mode 100644 l10n_it_fatturapa_import_zip/models/attachment.py create mode 100644 l10n_it_fatturapa_import_zip/readme/CONTRIBUTORS.rst create mode 100644 l10n_it_fatturapa_import_zip/readme/DESCRIPTION.rst create mode 100644 l10n_it_fatturapa_import_zip/readme/USAGE.rst create mode 100644 l10n_it_fatturapa_import_zip/security/ir.model.access.csv create mode 100644 l10n_it_fatturapa_import_zip/security/rules.xml create mode 100644 l10n_it_fatturapa_import_zip/static/description/index.html create mode 100644 l10n_it_fatturapa_import_zip/tests/__init__.py create mode 100644 l10n_it_fatturapa_import_zip/tests/data/xml_import.zip create mode 100644 l10n_it_fatturapa_import_zip/tests/test_import_zip.py create mode 100644 l10n_it_fatturapa_import_zip/views/account_invoice_views.xml create mode 100644 l10n_it_fatturapa_import_zip/views/attachment_views.xml diff --git a/l10n_it_fatturapa_import_zip/README.rst b/l10n_it_fatturapa_import_zip/README.rst new file mode 100644 index 000000000000..64f95b8f7273 --- /dev/null +++ b/l10n_it_fatturapa_import_zip/README.rst @@ -0,0 +1,95 @@ +====================================== +ITA - Fattura elettronica - Import ZIP +====================================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fl10n--italy-lightgray.png?logo=github + :target: https://github.com/OCA/l10n-italy/tree/14.0/l10n_it_fatturapa_import_zip + :alt: OCA/l10n-italy +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/l10n-italy-14-0/l10n-italy-14-0-l10n_it_fatturapa_import_zip + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/122/14.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +**Italiano** + +Questo modulo aggiunge una vista per importare diversi file XML di fatture elettroniche (OUT/IN) tramite file ZIP. + +**English** + +This module adds a view to import several XML e-invoice files (OUT/IN) via ZIP file. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +**Italiano** + + * Andare in Fatturazione -> Configurazione -> Fiscalità italiana -> Import E-bill ZIP Files + * Caricare un file ZIP contenente i file XML di fatture elettroniche (OUT/IN) + * Eseguire "Import Invoices" per creare le fatture in bozza + +**English** + + * Go to Accounting -> Configuration -> Italian Localization -> Import E-bill ZIP Files + * Upload ZIP file including XML e-invoice files (OUT/IN) + * Run "Import Invoices" to create a draft invoices/bills + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* TAKOBI + +Contributors +~~~~~~~~~~~~ + +* TAKOBI +* Giuseppe Borruso - Dinamiche Aziendali srl + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/l10n-italy `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/l10n_it_fatturapa_import_zip/__init__.py b/l10n_it_fatturapa_import_zip/__init__.py new file mode 100644 index 000000000000..0ee8b5073e26 --- /dev/null +++ b/l10n_it_fatturapa_import_zip/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import tests diff --git a/l10n_it_fatturapa_import_zip/__manifest__.py b/l10n_it_fatturapa_import_zip/__manifest__.py new file mode 100644 index 000000000000..c4f6c7b57ec9 --- /dev/null +++ b/l10n_it_fatturapa_import_zip/__manifest__.py @@ -0,0 +1,28 @@ +# Copyright 2020 Lorenzo Battistini +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "ITA - Fattura elettronica - Import ZIP", + "summary": "Permette di importare in uno ZIP diversi file XML di " + "fatture elettroniche", + "version": "14.0.1.0.0", + "category": "Localization/Italy", + "website": "https://github.com/OCA/l10n-italy", + "author": "TAKOBI, Odoo Community Association (OCA)", + "license": "AGPL-3", + "application": False, + "installable": True, + "depends": [ + "l10n_it_account", + "l10n_it_fiscal_document_type", + "l10n_it_fatturapa_out", + "l10n_it_fatturapa_in", + "l10n_it_withholding_tax_reason", + ], + "data": [ + "views/account_invoice_views.xml", + "views/attachment_views.xml", + "security/ir.model.access.csv", + "security/rules.xml", + ], +} diff --git a/l10n_it_fatturapa_import_zip/i18n/it.po b/l10n_it_fatturapa_import_zip/i18n/it.po new file mode 100644 index 000000000000..1a427730b2d7 --- /dev/null +++ b/l10n_it_fatturapa_import_zip/i18n/it.po @@ -0,0 +1,532 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * l10n_it_fatturapa_import_zip +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2021-11-19 16:10+0000\n" +"PO-Revision-Date: 2021-11-19 16:10+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__access_token +msgid "Access Token" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__message_needaction +msgid "Action Needed" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__activity_ids +msgid "Activities" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__activity_exception_decoration +msgid "Activity Exception Decoration" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__activity_state +msgid "Activity State" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__activity_type_icon +msgid "Activity Type Icon" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__ir_attachment_id +msgid "Attachment" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__message_attachment_count +msgid "Attachment Count" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__local_url +msgid "Attachment URL" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__attachment_in_ids +msgid "Attachments In" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__attachment_out_ids +msgid "Attachments Out" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__checksum +msgid "Checksum/SHA1" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__company_id +msgid "Company" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields.selection,name:l10n_it_fatturapa_import_zip.selection__fatturapa_attachment_import_zip__state__done +msgid "Completed" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__create_uid +msgid "Created by" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__create_date +msgid "Created on" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__db_datas +msgid "Database Data" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__description +msgid "Description" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_account_move__display_name +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__display_name +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_in__display_name +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_out__display_name +msgid "Display Name" +msgstr "Nome visualizzato" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields.selection,name:l10n_it_fatturapa_import_zip.selection__fatturapa_attachment_import_zip__state__draft +msgid "Draft" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model,name:l10n_it_fatturapa_import_zip.model_fatturapa_attachment_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_in__attachment_import_zip_id +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_out__attachment_import_zip_id +msgid "E-bill ZIP import" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model,name:l10n_it_fatturapa_import_zip.model_fatturapa_attachment_in +msgid "E-bill import file" +msgstr "File importato e-fattura" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_account_bank_statement_line__attachment_out_import_zip_id +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_account_move__attachment_out_import_zip_id +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_account_payment__attachment_out_import_zip_id +msgid "E-bills ZIP import" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model,name:l10n_it_fatturapa_import_zip.model_fatturapa_attachment_out +msgid "E-invoice Export File" +msgstr "File esportato e-fattura" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_account_bank_statement_line__attachment_in_import_zip_id +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_account_move__attachment_in_import_zip_id +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_account_payment__attachment_in_import_zip_id +msgid "E-invoices ZIP import" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__messages +msgid "Error Messages" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__datas +msgid "File Content (base64)" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__raw +msgid "File Content (raw)" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__file_size +msgid "File Size" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__message_follower_ids +msgid "Followers" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__message_channel_ids +msgid "Followers (Channels)" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__message_partner_ids +msgid "Followers (Partners)" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,help:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__activity_type_icon +msgid "Font awesome icon e.g. fa-tasks" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_account_move__id +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__id +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_in__id +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_out__id +msgid "ID" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__activity_exception_icon +msgid "Icon" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,help:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__activity_exception_icon +msgid "Icon to indicate an exception activity." +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,help:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__message_needaction +#: model:ir.model.fields,help:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__message_unread +msgid "If checked, new messages require your attention." +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,help:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__message_has_error +#: model:ir.model.fields,help:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__message_has_sms_error +msgid "If checked, some messages have a delivery error." +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__image_height +msgid "Image Height" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__image_src +msgid "Image Src" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__image_width +msgid "Image Width" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.actions.act_window,name:l10n_it_fatturapa_import_zip.action_fatturapa_attachment_import_zip +#: model:ir.ui.menu,name:l10n_it_fatturapa_import_zip.menu_action_fatturapa_attachment_import_zip +msgid "Import E-bill ZIP Files" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model_terms:ir.ui.view,arch_db:l10n_it_fatturapa_import_zip.fatturapa_attachment_import_zip_form +msgid "Import e-bill ZIP" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model_terms:ir.ui.view,arch_db:l10n_it_fatturapa_import_zip.fatturapa_attachment_import_zip_form +msgid "Import invoices" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model_terms:ir.ui.view,arch_db:l10n_it_fatturapa_import_zip.fatturapa_attachment_import_zip_form +msgid "In invoices" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__index_content +msgid "Indexed Content" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__invoice_in_ids +msgid "Invoices In" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__invoices_in_count +msgid "Invoices In Count" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__invoice_out_ids +msgid "Invoices Out" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__invoices_out_count +msgid "Invoices Out Count" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__message_is_follower +msgid "Is Follower" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__public +msgid "Is public document" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model,name:l10n_it_fatturapa_import_zip.model_account_move +msgid "Journal Entry" +msgstr "Registrazione contabile" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_account_move____last_update +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip____last_update +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_in____last_update +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_out____last_update +msgid "Last Modified on" +msgstr "Ultima modifica il" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__write_date +msgid "Last Updated on" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__message_main_attachment_id +msgid "Main Attachment" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__message_has_error +msgid "Message Delivery error" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__message_ids +#: model_terms:ir.ui.view,arch_db:l10n_it_fatturapa_import_zip.fatturapa_attachment_import_zip_form +msgid "Messages" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__mimetype +msgid "Mime Type" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__my_activity_date_deadline +msgid "My Activity Deadline" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__name +msgid "Name" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__activity_date_deadline +msgid "Next Activity Deadline" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__activity_summary +msgid "Next Activity Summary" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__activity_type_id +msgid "Next Activity Type" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__message_needaction_counter +msgid "Number of Actions" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__message_has_error_counter +msgid "Number of errors" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,help:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__message_needaction_counter +msgid "Number of messages which requires an action" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,help:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__message_has_error_counter +msgid "Number of messages with delivery error" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,help:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__message_unread_counter +msgid "Number of unread messages" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__original_id +msgid "Original (unoptimized, unresized) attachment" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model_terms:ir.ui.view,arch_db:l10n_it_fatturapa_import_zip.fatturapa_attachment_import_zip_form +msgid "Out invoices" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__ftpa_preview_link +msgid "Preview link" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__res_field +msgid "Resource Field" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__res_id +msgid "Resource ID" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__res_model +msgid "Resource Model" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__res_name +msgid "Resource Name" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__activity_user_id +msgid "Responsible User" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__message_has_sms_error +msgid "SMS Delivery error" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__state +msgid "State" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,help:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__activity_state +msgid "" +"Status based on activities\n" +"Overdue: Due date is already passed\n" +"Today: Activity date is today\n" +"Planned: Future activities." +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__store_fname +msgid "Stored Filename" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,help:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__res_model +msgid "The database object this attachment will be attached to." +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,help:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__res_id +msgid "The record id this is attached to." +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__type +msgid "Type" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,help:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__activity_exception_decoration +msgid "Type of the exception activity on record." +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__message_unread +msgid "Unread Messages" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__message_unread_counter +msgid "Unread Messages Counter" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__url +msgid "Url" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__website_message_ids +msgid "Website Messages" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,help:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__website_message_ids +msgid "Website communication history" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model_terms:ir.ui.view,arch_db:l10n_it_fatturapa_import_zip.fatturapa_attachment_import_zip_form +msgid "XML In" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__xml_in_count +msgid "XML In Count" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model_terms:ir.ui.view,arch_db:l10n_it_fatturapa_import_zip.fatturapa_attachment_import_zip_form +msgid "XML Out" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,field_description:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__xml_out_count +msgid "XML Out Count" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model_terms:ir.ui.view,arch_db:l10n_it_fatturapa_import_zip.fatturapa_attachment_import_zip_tree +msgid "Xml Attachment" +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model:ir.model.fields,help:l10n_it_fatturapa_import_zip.field_fatturapa_attachment_import_zip__type +msgid "" +"You can either upload a file from your computer or copy/paste an internet " +"link to your file." +msgstr "" + +#. module: l10n_it_fatturapa_import_zip +#: model_terms:ir.ui.view,arch_db:l10n_it_fatturapa_import_zip.view_fatturapa_in_attachment_form_zip_import +#: model_terms:ir.ui.view,arch_db:l10n_it_fatturapa_import_zip.view_fatturapa_out_attachment_form_zip_import +msgid "ZIP import" +msgstr "" diff --git a/l10n_it_fatturapa_import_zip/models/__init__.py b/l10n_it_fatturapa_import_zip/models/__init__.py new file mode 100644 index 000000000000..ae9b78008ab1 --- /dev/null +++ b/l10n_it_fatturapa_import_zip/models/__init__.py @@ -0,0 +1,2 @@ +from . import attachment +from . import account_invoice diff --git a/l10n_it_fatturapa_import_zip/models/account_invoice.py b/l10n_it_fatturapa_import_zip/models/account_invoice.py new file mode 100644 index 000000000000..b6113ef195d1 --- /dev/null +++ b/l10n_it_fatturapa_import_zip/models/account_invoice.py @@ -0,0 +1,20 @@ +from odoo import fields, models + + +class Invoice(models.Model): + _inherit = "account.move" + + attachment_out_import_zip_id = fields.Many2one( + "fatturapa.attachment.import.zip", + "E-bills ZIP import", + readonly=True, + related="fatturapa_attachment_out_id.attachment_import_zip_id", + store=True, + ) + attachment_in_import_zip_id = fields.Many2one( + "fatturapa.attachment.import.zip", + "E-invoices ZIP import", + readonly=True, + related="fatturapa_attachment_in_id.attachment_import_zip_id", + store=True, + ) diff --git a/l10n_it_fatturapa_import_zip/models/attachment.py b/l10n_it_fatturapa_import_zip/models/attachment.py new file mode 100644 index 000000000000..70369a6233c2 --- /dev/null +++ b/l10n_it_fatturapa_import_zip/models/attachment.py @@ -0,0 +1,206 @@ +import base64 +import os +import shutil +import zipfile +from datetime import datetime + +from odoo import fields, models + +from odoo.addons.l10n_it_fatturapa_in.wizard import efattura + + +class FatturaPAAttachmentImportZIP(models.Model): + _name = "fatturapa.attachment.import.zip" + _description = "E-bill ZIP import" + _inherits = {"ir.attachment": "ir_attachment_id"} + _inherit = ["mail.thread", "mail.activity.mixin"] + _order = "id desc" + + ir_attachment_id = fields.Many2one( + "ir.attachment", "Attachment", required=True, ondelete="cascade" + ) + state = fields.Selection( + [ + ("draft", "Draft"), + ("done", "Completed"), + ], + string="State", + default="draft", + required=True, + readonly=True, + ) + messages = fields.Text( + "Error Messages", readonly=True, compute="_compute_invoices_data" + ) + xml_out_count = fields.Integer( + string="XML Out Count", compute="_compute_invoices_data", readonly=True + ) + xml_in_count = fields.Integer( + string="XML In Count", compute="_compute_invoices_data", readonly=True + ) + invoices_out_count = fields.Integer( + string="Invoices Out Count", compute="_compute_invoices_data", readonly=True + ) + invoices_in_count = fields.Integer( + string="Invoices In Count", compute="_compute_invoices_data", readonly=True + ) + attachment_out_ids = fields.One2many( + "fatturapa.attachment.out", + "attachment_import_zip_id", + string="Attachments Out", + readonly=True, + ) + attachment_in_ids = fields.One2many( + "fatturapa.attachment.in", + "attachment_import_zip_id", + string="Attachments In", + readonly=True, + ) + invoice_out_ids = fields.One2many( + "account.move", "attachment_out_import_zip_id", string="Invoices Out" + ) + invoice_in_ids = fields.One2many( + "account.move", "attachment_in_import_zip_id", string="Invoices In" + ) + + def action_view_xml(self): + if self.env.context.get("xml_type") == "out_xml": + attachments = self.mapped("attachment_out_ids") + action = self.env.ref( + "l10n_it_fatturapa_out.action_fatturapa_attachment" + ).read()[0] + elif self.env.context.get("xml_type") == "in_xml": + attachments = self.mapped("attachment_in_ids") + action = self.env.ref("l10n_it_fatturapa_in.action_fattura_pa_in").read()[0] + else: + return {"type": "ir.actions.act_window_close"} + action["context"] = "{}" + action["domain"] = [("id", "in", attachments.ids)] + return action + + def action_view_invoices(self): + if self.env.context.get("invoice_type") == "out_invoice": + invoices = self.mapped("invoice_out_ids") + action = self.env.ref("account.action_move_out_invoice_type").read()[0] + context = { + "default_move_type": "out_invoice", + } + elif self.env.context.get("invoice_type") == "in_invoice": + invoices = self.mapped("invoice_in_ids") + action = self.env.ref("account.action_move_in_invoice_type").read()[0] + context = { + "default_move_type": "in_invoice", + } + else: + return {"type": "ir.actions.act_window_close"} + action["context"] = context + action["domain"] = [("id", "in", invoices.ids)] + return action + + def _compute_invoices_data(self): + for import_zip in self: + import_zip.xml_out_count = len(import_zip.attachment_out_ids) + import_zip.xml_in_count = len(import_zip.attachment_in_ids) + import_zip.invoices_out_count = len(import_zip.invoice_out_ids) + import_zip.invoices_in_count = len(import_zip.invoice_in_ids) + import_zip.messages = "{}\n{}".format( + "\n".join( + [ + i + for i in import_zip.invoice_out_ids.mapped("inconsistencies") + if i + ] + ), + "\n".join( + [ + i + for i in import_zip.invoice_in_ids.mapped("inconsistencies") + if i + ] + ), + ) + + def action_import(self): + self.ensure_one() + tmp_dir_name = "/tmp/{}_{}".format( + self.env.cr.dbname, datetime.now().timestamp() + ) + if os.path.isdir(tmp_dir_name): + shutil.rmtree(tmp_dir_name) + os.mkdir(tmp_dir_name) + zip_data = base64.b64decode(self.datas) + zip_file_path = "%s/e_bills_to_import.zip" % tmp_dir_name + with open(zip_file_path, "wb") as writer: + writer.write(zip_data) + tmp_dir_name_xml = tmp_dir_name + "/XML" + with zipfile.ZipFile(zip_file_path, "r") as zip_ref: + zip_ref.extractall(tmp_dir_name_xml) + for xml_filename in os.listdir(tmp_dir_name_xml): + with open("{}/{}".format(tmp_dir_name_xml, xml_filename), "rb") as reader: + content = reader.read() + attach_vals = { + "name": xml_filename, + "datas": base64.encodebytes(content), + } + att_in = self.env["fatturapa.attachment.in"].create(attach_vals) + if att_in.xml_supplier_id.id == self.env.company.partner_id.id: + att_in.unlink() + attach_vals["state"] = "validated" + att_out = self.env["fatturapa.attachment.out"].create(attach_vals) + wizard = ( + self.env["wizard.import.fatturapa"] + .with_context( + active_ids=[att_out.id], active_model="fatturapa.attachment.out" + ) + .create({}) + ) + wizard.importFatturaPA(invoice_type="sale") + att_out.attachment_import_zip_id = self.id + else: + in_invoice_registration_date = ( + self.env.company.in_invoice_registration_date + ) + # we don't have the received date + self.env.company.in_invoice_registration_date = "inv_date" + att_in.attachment_import_zip_id = self.id + wizard = ( + self.env["wizard.import.fatturapa"] + .with_context( + active_ids=[att_in.id], active_model="fatturapa.attachment.in" + ) + .create({}) + ) + wizard.importFatturaPA() + att_in.attachment_import_zip_id = self.id + self.env.company.in_invoice_registration_date = ( + in_invoice_registration_date + ) + if os.path.isdir(tmp_dir_name): + shutil.rmtree(tmp_dir_name) + self.state = "done" + + +class FatturaPAAttachmentIn(models.Model): + _inherit = "fatturapa.attachment.in" + + attachment_import_zip_id = fields.Many2one( + "fatturapa.attachment.import.zip", + "E-bill ZIP import", + readonly=True, + ondelete="restrict", + ) + + +class FatturaPAAttachmentOut(models.Model): + _inherit = "fatturapa.attachment.out" + + attachment_import_zip_id = fields.Many2one( + "fatturapa.attachment.import.zip", + "E-bill ZIP import", + readonly=True, + ondelete="restrict", + ) + + def get_invoice_obj(self, fatturapa_attachment): + xml_string = fatturapa_attachment.get_xml_string() + return efattura.CreateFromDocument(xml_string) diff --git a/l10n_it_fatturapa_import_zip/readme/CONTRIBUTORS.rst b/l10n_it_fatturapa_import_zip/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000000..90b7406cf8f1 --- /dev/null +++ b/l10n_it_fatturapa_import_zip/readme/CONTRIBUTORS.rst @@ -0,0 +1,2 @@ +* TAKOBI +* Giuseppe Borruso - Dinamiche Aziendali srl diff --git a/l10n_it_fatturapa_import_zip/readme/DESCRIPTION.rst b/l10n_it_fatturapa_import_zip/readme/DESCRIPTION.rst new file mode 100644 index 000000000000..9c207b9098f3 --- /dev/null +++ b/l10n_it_fatturapa_import_zip/readme/DESCRIPTION.rst @@ -0,0 +1,7 @@ +**Italiano** + +Questo modulo aggiunge una vista per importare diversi file XML di fatture elettroniche (OUT/IN) tramite file ZIP. + +**English** + +This module adds a view to import several XML e-invoice files (OUT/IN) via ZIP file. diff --git a/l10n_it_fatturapa_import_zip/readme/USAGE.rst b/l10n_it_fatturapa_import_zip/readme/USAGE.rst new file mode 100644 index 000000000000..b5f7ce3e21d3 --- /dev/null +++ b/l10n_it_fatturapa_import_zip/readme/USAGE.rst @@ -0,0 +1,11 @@ +**Italiano** + + * Andare in Fatturazione -> Configurazione -> Fiscalità italiana -> Import E-bill ZIP Files + * Caricare un file ZIP contenente i file XML di fatture elettroniche (OUT/IN) + * Eseguire "Import Invoices" per creare le fatture in bozza + +**English** + + * Go to Accounting -> Configuration -> Italian Localization -> Import E-bill ZIP Files + * Upload ZIP file including XML e-invoice files (OUT/IN) + * Run "Import Invoices" to create a draft invoices/bills diff --git a/l10n_it_fatturapa_import_zip/security/ir.model.access.csv b/l10n_it_fatturapa_import_zip/security/ir.model.access.csv new file mode 100644 index 000000000000..075b3c663036 --- /dev/null +++ b/l10n_it_fatturapa_import_zip/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_fatturapa_attachment_import_zip,access_fatturapa_attachment_import_zip,model_fatturapa_attachment_import_zip,account.group_account_invoice,1,1,1,1 diff --git a/l10n_it_fatturapa_import_zip/security/rules.xml b/l10n_it_fatturapa_import_zip/security/rules.xml new file mode 100644 index 000000000000..2ce069546f59 --- /dev/null +++ b/l10n_it_fatturapa_import_zip/security/rules.xml @@ -0,0 +1,13 @@ + + + + + E-invoice ZIP import multi company rule + + + ['|',('company_id','=',False),('company_id','in',company_ids)] + + + diff --git a/l10n_it_fatturapa_import_zip/static/description/index.html b/l10n_it_fatturapa_import_zip/static/description/index.html new file mode 100644 index 000000000000..a669aeaca5cf --- /dev/null +++ b/l10n_it_fatturapa_import_zip/static/description/index.html @@ -0,0 +1,443 @@ + + + + + + +ITA - Fattura elettronica - Import ZIP + + + +
+

ITA - Fattura elettronica - Import ZIP

+ + +

Beta License: AGPL-3 OCA/l10n-italy Translate me on Weblate Try me on Runbot

+

Italiano

+

Questo modulo aggiunge una vista per importare diversi file XML di fatture elettroniche (OUT/IN) tramite file ZIP.

+

English

+

This module adds a view to import several XML e-invoice files (OUT/IN) via ZIP file.

+

Table of contents

+ +
+

Usage

+

Italiano

+
+
    +
  • Andare in Fatturazione -> Configurazione -> Fiscalità italiana -> Import E-bill ZIP Files
  • +
  • Caricare un file ZIP contenente i file XML di fatture elettroniche (OUT/IN)
  • +
  • Eseguire “Import Invoices” per creare le fatture in bozza
  • +
+
+

English

+
+
    +
  • Go to Accounting -> Configuration -> Italian Localization -> Import E-bill ZIP Files
  • +
  • Upload ZIP file including XML e-invoice files (OUT/IN)
  • +
  • Run “Import Invoices” to create a draft invoices/bills
  • +
+
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • TAKOBI
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/l10n-italy project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/l10n_it_fatturapa_import_zip/tests/__init__.py b/l10n_it_fatturapa_import_zip/tests/__init__.py new file mode 100644 index 000000000000..d6ecc71be352 --- /dev/null +++ b/l10n_it_fatturapa_import_zip/tests/__init__.py @@ -0,0 +1 @@ +from . import test_import_zip diff --git a/l10n_it_fatturapa_import_zip/tests/data/xml_import.zip b/l10n_it_fatturapa_import_zip/tests/data/xml_import.zip new file mode 100644 index 0000000000000000000000000000000000000000..31e589cab60205f50a1d86dfab70fce3c9d72ef6 GIT binary patch literal 89808 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 AQvd(} literal 0 HcmV?d00001 diff --git a/l10n_it_fatturapa_import_zip/tests/test_import_zip.py b/l10n_it_fatturapa_import_zip/tests/test_import_zip.py new file mode 100644 index 000000000000..9969cb8a4f5b --- /dev/null +++ b/l10n_it_fatturapa_import_zip/tests/test_import_zip.py @@ -0,0 +1,293 @@ +import base64 +import tempfile +from datetime import datetime + +from odoo.modules import get_module_resource +from odoo.tests.common import TransactionCase + + +class TestImportZIP(TransactionCase): + def setUp(self): + super(TestImportZIP, self).setUp() + self.attach_model = self.env["fatturapa.attachment.import.zip"] + self.cleanPartners() + arrotondamenti_attivi_account_id = ( + self.env["account.account"] + .search( + [ + ( + "user_type_id", + "=", + self.env.ref("account.data_account_type_other_income").id, + ) + ], + limit=1, + ) + .id + ) + arrotondamenti_passivi_account_id = ( + self.env["account.account"] + .search( + [ + ( + "user_type_id", + "=", + self.env.ref("account.data_account_type_direct_costs").id, + ) + ], + limit=1, + ) + .id + ) + arrotondamenti_tax_id = self.env["account.tax"].search( + [("type_tax_use", "=", "purchase"), ("amount", "=", 0.0)], + order="sequence", + limit=1, + ) + self.env.company.arrotondamenti_attivi_account_id = ( + arrotondamenti_attivi_account_id + ) + self.env.company.arrotondamenti_passivi_account_id = ( + arrotondamenti_passivi_account_id + ) + self.env.company.arrotondamenti_tax_id = arrotondamenti_tax_id + account_payable = self.env["account.account"].create( + { + "name": "Test WH tax", + "code": "whtaxpay2", + "user_type_id": self.env.ref("account.data_account_type_payable").id, + "reconcile": True, + } + ) + account_receivable = self.env["account.account"].create( + { + "name": "Test WH tax", + "code": "whtaxrec2", + "user_type_id": self.env.ref("account.data_account_type_receivable").id, + "reconcile": True, + } + ) + self.env["withholding.tax"].create( + { + "name": "1040", + "code": "1040", + "account_receivable_id": account_receivable.id, + "account_payable_id": account_payable.id, + "payment_term": self.env.ref( + "account.account_payment_term_immediate" + ).id, + "rate_ids": [(0, 0, {"tax": 20.0})], + "payment_reason_id": self.env.ref("l10n_it_payment_reason.a").id, + } + ) + self.env["withholding.tax"].create( + { + "name": "Enasarco", + "code": "TC07", + "account_receivable_id": account_receivable.id, + "account_payable_id": account_payable.id, + "payment_term": self.env.ref("account.account_payment_term_advance").id, + "wt_types": "enasarco", + "payment_reason_id": self.env.ref("l10n_it_payment_reason.r").id, + "rate_ids": [ + ( + 0, + 0, + { + "tax": 1.57, + "base": 1.0, + }, + ) + ], + } + ) + self.env["withholding.tax"].create( + { + "name": "Enasarco 8,50", + "code": "TC07", + "account_receivable_id": account_receivable.id, + "account_payable_id": account_payable.id, + "payment_term": self.env.ref("account.account_payment_term_advance").id, + "wt_types": "enasarco", + "payment_reason_id": self.env.ref("l10n_it_payment_reason.r").id, + "rate_ids": [ + ( + 0, + 0, + { + "tax": 8.5, + "base": 1.0, + }, + ) + ], + } + ) + self.env["withholding.tax"].create( + { + "name": "1040/3", + "code": "1040", + "account_receivable_id": account_receivable.id, + "account_payable_id": account_payable.id, + "payment_term": self.env.ref("account.account_payment_term_advance").id, + "wt_types": "ritenuta", + "payment_reason_id": self.env.ref("l10n_it_payment_reason.a").id, + "rate_ids": [ + ( + 0, + 0, + { + "tax": 11.50, + "base": 1.0, + }, + ) + ], + } + ) + self.env["withholding.tax"].create( + { + "name": "1040 R", + "code": "1040R", + "account_receivable_id": account_receivable.id, + "account_payable_id": account_payable.id, + "payment_term": self.env.ref("account.account_payment_term_advance").id, + "wt_types": "ritenuta", + "payment_reason_id": self.env.ref("l10n_it_payment_reason.r").id, + "rate_ids": [ + ( + 0, + 0, + { + "tax": 11.50, + "base": 1.0, + }, + ) + ], + } + ) + + self.env["withholding.tax"].create( + { + "name": "2320", + "code": "2320", + "account_receivable_id": account_receivable.id, + "account_payable_id": account_payable.id, + "payment_term": self.env.ref( + "account.account_payment_term_immediate" + ).id, + "rate_ids": [(0, 0, {"tax": 23.0, "base": 0.2})], + "payment_reason_id": self.env.ref("l10n_it_payment_reason.a").id, + } + ) + + self.env["withholding.tax"].create( + { + "name": "2320", + "code": "2320", + "account_receivable_id": account_receivable.id, + "account_payable_id": account_payable.id, + "payment_term": self.env.ref( + "account.account_payment_term_immediate" + ).id, + "rate_ids": [(0, 0, {"tax": 23.0, "base": 0.5})], + "payment_reason_id": self.env.ref("l10n_it_payment_reason.a").id, + } + ) + + self.env["withholding.tax"].create( + { + "name": "2620q", + "code": "2620q", + "account_receivable_id": account_receivable.id, + "account_payable_id": account_payable.id, + "payment_term": self.env.ref( + "account.account_payment_term_immediate" + ).id, + "rate_ids": [(0, 0, {"tax": 26.0, "base": 0.2})], + "payment_reason_id": self.env.ref("l10n_it_payment_reason.q").id, + } + ) + + self.env["withholding.tax"].create( + { + "name": "2640q", + "code": "2640q", + "account_receivable_id": account_receivable.id, + "account_payable_id": account_payable.id, + "payment_term": self.env.ref( + "account.account_payment_term_immediate" + ).id, + "rate_ids": [(0, 0, {"tax": 26.0, "base": 0.4})], + "payment_reason_id": self.env.ref("l10n_it_payment_reason.q").id, + } + ) + + self.env["withholding.tax"].create( + { + "name": "2720q", + "code": "2720q", + "account_receivable_id": account_receivable.id, + "account_payable_id": account_payable.id, + "payment_term": self.env.ref( + "account.account_payment_term_immediate" + ).id, + "rate_ids": [(0, 0, {"tax": 27.0, "base": 0.2})], + "payment_reason_id": self.env.ref("l10n_it_payment_reason.q").id, + } + ) + + self.env["withholding.tax"].create( + { + "name": "4q", + "code": "4q", + "wt_types": "enasarco", + "account_receivable_id": account_receivable.id, + "account_payable_id": account_payable.id, + "payment_term": self.env.ref( + "account.account_payment_term_immediate" + ).id, + "rate_ids": [(0, 0, {"tax": 4.0, "base": 1.0})], + "payment_reason_id": self.env.ref("l10n_it_payment_reason.q").id, + } + ) + + AED = self.env.ref("base.AED") + AED.active = True + + def cleanPartners(self): + # VAT number used in tests, assigned to other partners by demo data, probably + main_company = self.env.ref("base.main_company") + partners = self.env["res.partner"].search([("vat", "=", "IT06363391001")]) + for partner in partners: + if partner.id != main_company.partner_id.id: + partner.vat = "" + + def getFile(self, filename): + module_name = "l10n_it_fatturapa_import_zip" + path = get_module_resource(module_name, "tests", "data", filename) + with open(path, "rb") as test_data: + with tempfile.TemporaryFile() as out: + base64.encode(test_data, out) + out.seek(0) + return path, out.read() + + def test_import_zip(self): + attachment = self.attach_model.create( + { + "name": "xml_import.zip", + "datas": self.getFile("xml_import.zip")[1], + } + ) + attachment.action_import() + self.assertEqual(len(attachment.invoice_out_ids), 6) + self.assertEqual(len(attachment.invoice_in_ids), 37) + checked = False + for att in attachment.attachment_out_ids: + if att.name == "IT06363391001_00012.xml": + checked = True + self.assertEqual( + att.out_invoice_ids.invoice_date, datetime(2020, 1, 7).date() + ) + self.assertEqual( + att.out_invoice_ids.invoice_date_due, datetime(2020, 2, 29).date() + ) + self.assertTrue(checked) diff --git a/l10n_it_fatturapa_import_zip/views/account_invoice_views.xml b/l10n_it_fatturapa_import_zip/views/account_invoice_views.xml new file mode 100644 index 000000000000..c8d507514bc4 --- /dev/null +++ b/l10n_it_fatturapa_import_zip/views/account_invoice_views.xml @@ -0,0 +1,23 @@ + + + + view_invoice_form_fatturapa_import_zip + account.move + + + + + + + + + diff --git a/l10n_it_fatturapa_import_zip/views/attachment_views.xml b/l10n_it_fatturapa_import_zip/views/attachment_views.xml new file mode 100644 index 000000000000..393b29ff3811 --- /dev/null +++ b/l10n_it_fatturapa_import_zip/views/attachment_views.xml @@ -0,0 +1,180 @@ + + + + + fatturapa_attachment_import_zip_form + fatturapa.attachment.import.zip + +
+
+
+ +
+ + + + +
+
+ + + + + + + + + +
+
+
+ + +
+
+
+
+ + fatturapa_attachment_import_zip_tree + fatturapa.attachment.import.zip + + + + + + + + + + fatturapa_attachment_import_zip_search + fatturapa.attachment.import.zip + + + + + + + + + Import E-bill ZIP Files + fatturapa.attachment.import.zip + tree,form + + + + + + view_fatturapa_in_attachment_form_zip_import + fatturapa.attachment.in + + + + + + + + + + + + + view_fatturapa_out_attachment_form_zip_import + fatturapa.attachment.out + + + + + + + + +
From faa9f98836b2eec1d45383b5ec7cfa73ce2c1482 Mon Sep 17 00:00:00 2001 From: Simone Rubino Date: Thu, 27 Jul 2023 16:18:39 +0200 Subject: [PATCH 04/10] [IMP] l10n_it_fatturapa_import_zip: FS-Agnostic file management Do not assume there is a `/tmp` directory or that path separator is `/` so that this can also work in other FileSystems than Linux's --- .../models/attachment.py | 112 +++++++++--------- .../readme/CONTRIBUTORS.rst | 4 +- 2 files changed, 59 insertions(+), 57 deletions(-) diff --git a/l10n_it_fatturapa_import_zip/models/attachment.py b/l10n_it_fatturapa_import_zip/models/attachment.py index 70369a6233c2..648b43d37d92 100644 --- a/l10n_it_fatturapa_import_zip/models/attachment.py +++ b/l10n_it_fatturapa_import_zip/models/attachment.py @@ -1,14 +1,25 @@ +# Copyright 2023 Simone Rubino - TAKOBI +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + import base64 -import os -import shutil +import tempfile import zipfile -from datetime import datetime +from pathlib import Path from odoo import fields, models from odoo.addons.l10n_it_fatturapa_in.wizard import efattura +def _extract_zip_file(directory, datas): + """Extract the zip file having content `datas` to `directory`.""" + zip_data = base64.b64decode(datas) + with tempfile.NamedTemporaryFile(mode="wb") as tmp_file: + tmp_file.write(zip_data) + with zipfile.ZipFile(tmp_file.name, mode="r") as zip_ref: + zip_ref.extractall(directory) + + class FatturaPAAttachmentImportZIP(models.Model): _name = "fatturapa.attachment.import.zip" _description = "E-bill ZIP import" @@ -122,61 +133,50 @@ def _compute_invoices_data(self): def action_import(self): self.ensure_one() - tmp_dir_name = "/tmp/{}_{}".format( - self.env.cr.dbname, datetime.now().timestamp() - ) - if os.path.isdir(tmp_dir_name): - shutil.rmtree(tmp_dir_name) - os.mkdir(tmp_dir_name) - zip_data = base64.b64decode(self.datas) - zip_file_path = "%s/e_bills_to_import.zip" % tmp_dir_name - with open(zip_file_path, "wb") as writer: - writer.write(zip_data) - tmp_dir_name_xml = tmp_dir_name + "/XML" - with zipfile.ZipFile(zip_file_path, "r") as zip_ref: - zip_ref.extractall(tmp_dir_name_xml) - for xml_filename in os.listdir(tmp_dir_name_xml): - with open("{}/{}".format(tmp_dir_name_xml, xml_filename), "rb") as reader: - content = reader.read() - attach_vals = { - "name": xml_filename, - "datas": base64.encodebytes(content), - } - att_in = self.env["fatturapa.attachment.in"].create(attach_vals) - if att_in.xml_supplier_id.id == self.env.company.partner_id.id: - att_in.unlink() - attach_vals["state"] = "validated" - att_out = self.env["fatturapa.attachment.out"].create(attach_vals) - wizard = ( - self.env["wizard.import.fatturapa"] - .with_context( - active_ids=[att_out.id], active_model="fatturapa.attachment.out" + with tempfile.TemporaryDirectory() as tmp_dir_path: + tmp_dir = Path(tmp_dir_path) + _extract_zip_file(tmp_dir, self.datas) + for xml_file in tmp_dir.glob("*"): + content = xml_file.read_bytes() + attach_vals = { + "name": xml_file.name, + "datas": base64.encodebytes(content), + } + att_in = self.env["fatturapa.attachment.in"].create(attach_vals) + if att_in.xml_supplier_id.id == self.env.company.partner_id.id: + att_in.unlink() + attach_vals["state"] = "validated" + att_out = self.env["fatturapa.attachment.out"].create(attach_vals) + wizard = ( + self.env["wizard.import.fatturapa"] + .with_context( + active_ids=[att_out.id], + active_model="fatturapa.attachment.out", + ) + .create({}) + ) + wizard.importFatturaPA(invoice_type="sale") + att_out.attachment_import_zip_id = self.id + else: + in_invoice_registration_date = ( + self.env.company.in_invoice_registration_date + ) + # we don't have the received date + self.env.company.in_invoice_registration_date = "inv_date" + att_in.attachment_import_zip_id = self.id + wizard = ( + self.env["wizard.import.fatturapa"] + .with_context( + active_ids=[att_in.id], + active_model="fatturapa.attachment.in", + ) + .create({}) ) - .create({}) - ) - wizard.importFatturaPA(invoice_type="sale") - att_out.attachment_import_zip_id = self.id - else: - in_invoice_registration_date = ( - self.env.company.in_invoice_registration_date - ) - # we don't have the received date - self.env.company.in_invoice_registration_date = "inv_date" - att_in.attachment_import_zip_id = self.id - wizard = ( - self.env["wizard.import.fatturapa"] - .with_context( - active_ids=[att_in.id], active_model="fatturapa.attachment.in" + wizard.importFatturaPA() + att_in.attachment_import_zip_id = self.id + self.env.company.in_invoice_registration_date = ( + in_invoice_registration_date ) - .create({}) - ) - wizard.importFatturaPA() - att_in.attachment_import_zip_id = self.id - self.env.company.in_invoice_registration_date = ( - in_invoice_registration_date - ) - if os.path.isdir(tmp_dir_name): - shutil.rmtree(tmp_dir_name) self.state = "done" diff --git a/l10n_it_fatturapa_import_zip/readme/CONTRIBUTORS.rst b/l10n_it_fatturapa_import_zip/readme/CONTRIBUTORS.rst index 90b7406cf8f1..c59a56ee69c6 100644 --- a/l10n_it_fatturapa_import_zip/readme/CONTRIBUTORS.rst +++ b/l10n_it_fatturapa_import_zip/readme/CONTRIBUTORS.rst @@ -1,2 +1,4 @@ -* TAKOBI +* `TAKOBI `_: + + * Simone Rubino * Giuseppe Borruso - Dinamiche Aziendali srl From 2421f978d2ef72388d24980ca6f78870c3f65059 Mon Sep 17 00:00:00 2001 From: Simone Rubino Date: Fri, 28 Jul 2023 14:32:45 +0200 Subject: [PATCH 05/10] [REF] l10n_it_fatturapa_import_zip: Invoices creation Override exposed methods instead of duplicating Reuse common tests data --- l10n_it_fatturapa_import_zip/__init__.py | 3 + .../models/attachment.py | 57 ++- .../tests/test_import_zip.py | 325 +++--------------- .../wizards/__init__.py | 3 + .../wizards/wizard_import_fatturapa.py | 86 +++++ 5 files changed, 174 insertions(+), 300 deletions(-) create mode 100644 l10n_it_fatturapa_import_zip/wizards/__init__.py create mode 100644 l10n_it_fatturapa_import_zip/wizards/wizard_import_fatturapa.py diff --git a/l10n_it_fatturapa_import_zip/__init__.py b/l10n_it_fatturapa_import_zip/__init__.py index 0ee8b5073e26..b15de888c327 100644 --- a/l10n_it_fatturapa_import_zip/__init__.py +++ b/l10n_it_fatturapa_import_zip/__init__.py @@ -1,2 +1,5 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + from . import models from . import tests +from . import wizards diff --git a/l10n_it_fatturapa_import_zip/models/attachment.py b/l10n_it_fatturapa_import_zip/models/attachment.py index 648b43d37d92..14abadda6216 100644 --- a/l10n_it_fatturapa_import_zip/models/attachment.py +++ b/l10n_it_fatturapa_import_zip/models/attachment.py @@ -133,50 +133,43 @@ def _compute_invoices_data(self): def action_import(self): self.ensure_one() + company_partner = self.env.company.partner_id with tempfile.TemporaryDirectory() as tmp_dir_path: tmp_dir = Path(tmp_dir_path) _extract_zip_file(tmp_dir, self.datas) + original_in_invoice_registration_date = ( + self.env.company.in_invoice_registration_date + ) + # we don't have the received date + self.env.company.in_invoice_registration_date = "inv_date" + for xml_file in tmp_dir.glob("*"): content = xml_file.read_bytes() attach_vals = { "name": xml_file.name, "datas": base64.encodebytes(content), + "attachment_import_zip_id": self.id, } - att_in = self.env["fatturapa.attachment.in"].create(attach_vals) - if att_in.xml_supplier_id.id == self.env.company.partner_id.id: - att_in.unlink() + attachment = self.env["fatturapa.attachment.in"].create(attach_vals) + if attachment.xml_supplier_id == company_partner: + attachment.unlink() attach_vals["state"] = "validated" - att_out = self.env["fatturapa.attachment.out"].create(attach_vals) - wizard = ( - self.env["wizard.import.fatturapa"] - .with_context( - active_ids=[att_out.id], - active_model="fatturapa.attachment.out", - ) - .create({}) - ) - wizard.importFatturaPA(invoice_type="sale") - att_out.attachment_import_zip_id = self.id - else: - in_invoice_registration_date = ( - self.env.company.in_invoice_registration_date + attachment = self.env["fatturapa.attachment.out"].create( + attach_vals ) - # we don't have the received date - self.env.company.in_invoice_registration_date = "inv_date" - att_in.attachment_import_zip_id = self.id - wizard = ( - self.env["wizard.import.fatturapa"] - .with_context( - active_ids=[att_in.id], - active_model="fatturapa.attachment.in", - ) - .create({}) - ) - wizard.importFatturaPA() - att_in.attachment_import_zip_id = self.id - self.env.company.in_invoice_registration_date = ( - in_invoice_registration_date + wizard = ( + self.env["wizard.import.fatturapa"] + .with_context( + active_ids=attachment.ids, + active_model=attachment._name, ) + .create({}) + ) + wizard.importFatturaPA() + self.env.company.in_invoice_registration_date = ( + original_in_invoice_registration_date + ) + self.state = "done" diff --git a/l10n_it_fatturapa_import_zip/tests/test_import_zip.py b/l10n_it_fatturapa_import_zip/tests/test_import_zip.py index 9969cb8a4f5b..91fbdaeaac90 100644 --- a/l10n_it_fatturapa_import_zip/tests/test_import_zip.py +++ b/l10n_it_fatturapa_import_zip/tests/test_import_zip.py @@ -1,254 +1,26 @@ -import base64 -import tempfile -from datetime import datetime +# Copyright 2023 Simone Rubino - TAKOBI +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -from odoo.modules import get_module_resource -from odoo.tests.common import TransactionCase +from datetime import date +from odoo.addons.l10n_it_fatturapa_in.tests.fatturapa_common import FatturapaCommon -class TestImportZIP(TransactionCase): + +class TestImportZIP(FatturapaCommon): def setUp(self): super(TestImportZIP, self).setUp() - self.attach_model = self.env["fatturapa.attachment.import.zip"] + self.attachment_import_model = self.env["fatturapa.attachment.import.zip"] self.cleanPartners() - arrotondamenti_attivi_account_id = ( - self.env["account.account"] - .search( - [ - ( - "user_type_id", - "=", - self.env.ref("account.data_account_type_other_income").id, - ) - ], - limit=1, - ) - .id - ) - arrotondamenti_passivi_account_id = ( - self.env["account.account"] - .search( - [ - ( - "user_type_id", - "=", - self.env.ref("account.data_account_type_direct_costs").id, - ) - ], - limit=1, - ) - .id - ) - arrotondamenti_tax_id = self.env["account.tax"].search( - [("type_tax_use", "=", "purchase"), ("amount", "=", 0.0)], - order="sequence", - limit=1, - ) - self.env.company.arrotondamenti_attivi_account_id = ( - arrotondamenti_attivi_account_id - ) - self.env.company.arrotondamenti_passivi_account_id = ( - arrotondamenti_passivi_account_id - ) - self.env.company.arrotondamenti_tax_id = arrotondamenti_tax_id - account_payable = self.env["account.account"].create( - { - "name": "Test WH tax", - "code": "whtaxpay2", - "user_type_id": self.env.ref("account.data_account_type_payable").id, - "reconcile": True, - } - ) - account_receivable = self.env["account.account"].create( - { - "name": "Test WH tax", - "code": "whtaxrec2", - "user_type_id": self.env.ref("account.data_account_type_receivable").id, - "reconcile": True, - } - ) - self.env["withholding.tax"].create( - { - "name": "1040", - "code": "1040", - "account_receivable_id": account_receivable.id, - "account_payable_id": account_payable.id, - "payment_term": self.env.ref( - "account.account_payment_term_immediate" - ).id, - "rate_ids": [(0, 0, {"tax": 20.0})], - "payment_reason_id": self.env.ref("l10n_it_payment_reason.a").id, - } - ) - self.env["withholding.tax"].create( - { - "name": "Enasarco", - "code": "TC07", - "account_receivable_id": account_receivable.id, - "account_payable_id": account_payable.id, - "payment_term": self.env.ref("account.account_payment_term_advance").id, - "wt_types": "enasarco", - "payment_reason_id": self.env.ref("l10n_it_payment_reason.r").id, - "rate_ids": [ - ( - 0, - 0, - { - "tax": 1.57, - "base": 1.0, - }, - ) - ], - } - ) - self.env["withholding.tax"].create( - { - "name": "Enasarco 8,50", - "code": "TC07", - "account_receivable_id": account_receivable.id, - "account_payable_id": account_payable.id, - "payment_term": self.env.ref("account.account_payment_term_advance").id, - "wt_types": "enasarco", - "payment_reason_id": self.env.ref("l10n_it_payment_reason.r").id, - "rate_ids": [ - ( - 0, - 0, - { - "tax": 8.5, - "base": 1.0, - }, - ) - ], - } - ) - self.env["withholding.tax"].create( - { - "name": "1040/3", - "code": "1040", - "account_receivable_id": account_receivable.id, - "account_payable_id": account_payable.id, - "payment_term": self.env.ref("account.account_payment_term_advance").id, - "wt_types": "ritenuta", - "payment_reason_id": self.env.ref("l10n_it_payment_reason.a").id, - "rate_ids": [ - ( - 0, - 0, - { - "tax": 11.50, - "base": 1.0, - }, - ) - ], - } - ) - self.env["withholding.tax"].create( - { - "name": "1040 R", - "code": "1040R", - "account_receivable_id": account_receivable.id, - "account_payable_id": account_payable.id, - "payment_term": self.env.ref("account.account_payment_term_advance").id, - "wt_types": "ritenuta", - "payment_reason_id": self.env.ref("l10n_it_payment_reason.r").id, - "rate_ids": [ - ( - 0, - 0, - { - "tax": 11.50, - "base": 1.0, - }, - ) - ], - } - ) - - self.env["withholding.tax"].create( - { - "name": "2320", - "code": "2320", - "account_receivable_id": account_receivable.id, - "account_payable_id": account_payable.id, - "payment_term": self.env.ref( - "account.account_payment_term_immediate" - ).id, - "rate_ids": [(0, 0, {"tax": 23.0, "base": 0.2})], - "payment_reason_id": self.env.ref("l10n_it_payment_reason.a").id, - } - ) - - self.env["withholding.tax"].create( - { - "name": "2320", - "code": "2320", - "account_receivable_id": account_receivable.id, - "account_payable_id": account_payable.id, - "payment_term": self.env.ref( - "account.account_payment_term_immediate" - ).id, - "rate_ids": [(0, 0, {"tax": 23.0, "base": 0.5})], - "payment_reason_id": self.env.ref("l10n_it_payment_reason.a").id, - } - ) - - self.env["withholding.tax"].create( - { - "name": "2620q", - "code": "2620q", - "account_receivable_id": account_receivable.id, - "account_payable_id": account_payable.id, - "payment_term": self.env.ref( - "account.account_payment_term_immediate" - ).id, - "rate_ids": [(0, 0, {"tax": 26.0, "base": 0.2})], - "payment_reason_id": self.env.ref("l10n_it_payment_reason.q").id, - } - ) - - self.env["withholding.tax"].create( - { - "name": "2640q", - "code": "2640q", - "account_receivable_id": account_receivable.id, - "account_payable_id": account_payable.id, - "payment_term": self.env.ref( - "account.account_payment_term_immediate" - ).id, - "rate_ids": [(0, 0, {"tax": 26.0, "base": 0.4})], - "payment_reason_id": self.env.ref("l10n_it_payment_reason.q").id, - } - ) - - self.env["withholding.tax"].create( - { - "name": "2720q", - "code": "2720q", - "account_receivable_id": account_receivable.id, - "account_payable_id": account_payable.id, - "payment_term": self.env.ref( - "account.account_payment_term_immediate" - ).id, - "rate_ids": [(0, 0, {"tax": 27.0, "base": 0.2})], - "payment_reason_id": self.env.ref("l10n_it_payment_reason.q").id, - } - ) - - self.env["withholding.tax"].create( - { - "name": "4q", - "code": "4q", - "wt_types": "enasarco", - "account_receivable_id": account_receivable.id, - "account_payable_id": account_payable.id, - "payment_term": self.env.ref( - "account.account_payment_term_immediate" - ).id, - "rate_ids": [(0, 0, {"tax": 4.0, "base": 1.0})], - "payment_reason_id": self.env.ref("l10n_it_payment_reason.q").id, - } - ) + self.create_wt() + self.create_wt_enasarco_157_r() + self.create_wt_enasarco_85_r() + self.create_wt_enasarco_115_a() + self.create_wt_115_r() + self.create_wt_23_20() + self.create_wt_26_20q() + self.create_wt_26_40q() + self.create_wt_27_20q() + self.create_wt_4q() AED = self.env.ref("base.AED") AED.active = True @@ -261,33 +33,50 @@ def cleanPartners(self): if partner.id != main_company.partner_id.id: partner.vat = "" - def getFile(self, filename): - module_name = "l10n_it_fatturapa_import_zip" - path = get_module_resource(module_name, "tests", "data", filename) - with open(path, "rb") as test_data: - with tempfile.TemporaryFile() as out: - base64.encode(test_data, out) - out.seek(0) - return path, out.read() + def getFile(self, filename, module_name="l10n_it_fatturapa_import_zip"): + return super().getFile( + filename, + module_name=module_name, + ) def test_import_zip(self): - attachment = self.attach_model.create( + wizard_attachment_import = self.attachment_import_model.create( { "name": "xml_import.zip", "datas": self.getFile("xml_import.zip")[1], } ) - attachment.action_import() - self.assertEqual(len(attachment.invoice_out_ids), 6) - self.assertEqual(len(attachment.invoice_in_ids), 37) - checked = False - for att in attachment.attachment_out_ids: - if att.name == "IT06363391001_00012.xml": - checked = True - self.assertEqual( - att.out_invoice_ids.invoice_date, datetime(2020, 1, 7).date() - ) - self.assertEqual( - att.out_invoice_ids.invoice_date_due, datetime(2020, 2, 29).date() - ) - self.assertTrue(checked) + wizard_attachment_import.action_import() + attachments_out = wizard_attachment_import.attachment_out_ids + attachments_in = wizard_attachment_import.invoice_in_ids + self.assertEqual(len(attachments_out), 6) + self.assertEqual(len(attachments_in), 37) + + check_invoices_values = { + "IT06363391001_00012.xml": [ + { + "invoice_date": date( + 2020, + month=1, + day=7, + ), + "invoice_date_due": date( + 2020, + month=2, + day=29, + ), + }, + ], + } + + for attachment in attachments_out: + expected_invoices_values = check_invoices_values.get(attachment.name) + if expected_invoices_values is not None: + invoices = attachment.out_invoice_ids + for invoice, expected_values in zip(invoices, expected_invoices_values): + for field, expected_value in expected_values.items(): + self.assertEqual( + getattr(invoice, field), + expected_value, + f"Field {field} of invoice {invoice.display_name} does not match", + ) diff --git a/l10n_it_fatturapa_import_zip/wizards/__init__.py b/l10n_it_fatturapa_import_zip/wizards/__init__.py new file mode 100644 index 000000000000..9ba144222cf0 --- /dev/null +++ b/l10n_it_fatturapa_import_zip/wizards/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import wizard_import_fatturapa diff --git a/l10n_it_fatturapa_import_zip/wizards/wizard_import_fatturapa.py b/l10n_it_fatturapa_import_zip/wizards/wizard_import_fatturapa.py new file mode 100644 index 000000000000..6fe9beba6362 --- /dev/null +++ b/l10n_it_fatturapa_import_zip/wizards/wizard_import_fatturapa.py @@ -0,0 +1,86 @@ +# Copyright 2023 Simone Rubino - TAKOBI +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import _, models +from odoo.exceptions import UserError + +ATTACHMENT_OUT_MODEL_NAME = "fatturapa.attachment.out" + + +class WizardImportFatturapa(models.TransientModel): + _inherit = "wizard.import.fatturapa" + + def _is_import_attachment_out(self): + model = self._get_selected_model() + return model == ATTACHMENT_OUT_MODEL_NAME + + def _check_attachment(self, attachment): + if self._is_import_attachment_out(): + if attachment.out_invoice_ids: + raise UserError( + _("File %s is linked to invoices yet.", attachment.name) + ) + result = True + else: + result = super()._check_attachment(attachment) + return result + + def _extract_supplier(self, attachment): + if self._is_import_attachment_out(): + partner = self.env.company.partner_id + else: + partner = super()._extract_supplier(attachment) + return partner + + def _get_received_date(self, attachment): + if self._is_import_attachment_out(): + received_date = None + else: + received_date = super()._get_received_date(attachment) + return received_date + + def _get_journal_domain(self, company): + if self._is_import_attachment_out(): + domain = [ + ("type", "=", "sale"), + ("company_id", "=", company.id), + ] + else: + domain = super()._get_journal_domain(company) + return domain + + def _get_missing_journal_exception(self, company): + if self._is_import_attachment_out(): + exception = UserError( + _( + "Define a sale journal for this company: '%s' (id: %d).", + company.name, + company.id, + ) + ) + else: + exception = super()._get_missing_journal_exception(company) + return exception + + def _prepare_invoice_values(self, fatt, fatturapa_attachment, FatturaBody, partner): + invoice_values = super()._prepare_invoice_values( + fatt, + fatturapa_attachment, + FatturaBody, + partner, + ) + if self._is_import_attachment_out(): + invoice_values["fatturapa_attachment_out_id"] = invoice_values.pop( + "fatturapa_attachment_in_id" + ) + return invoice_values + + def _get_invoice_type(self, fiscal_document_type): + if self._is_import_attachment_out(): + if fiscal_document_type.code == "TD04": + invoice_type = "out_refund" + else: + invoice_type = "out_invoice" + else: + invoice_type = super()._get_invoice_type(fiscal_document_type) + return invoice_type From 74cdf961d03e5c24f71090760333aafefc28fd19 Mon Sep 17 00:00:00 2001 From: Simone Rubino Date: Mon, 31 Jul 2023 10:09:20 +0200 Subject: [PATCH 06/10] [IMP] l10n_it_fatturapa_import_zip: pre-commit execution --- .../odoo/addons/l10n_it_fatturapa_import_zip | 1 + setup/l10n_it_fatturapa_import_zip/setup.py | 6 ++++++ 2 files changed, 7 insertions(+) create mode 120000 setup/l10n_it_fatturapa_import_zip/odoo/addons/l10n_it_fatturapa_import_zip create mode 100644 setup/l10n_it_fatturapa_import_zip/setup.py diff --git a/setup/l10n_it_fatturapa_import_zip/odoo/addons/l10n_it_fatturapa_import_zip b/setup/l10n_it_fatturapa_import_zip/odoo/addons/l10n_it_fatturapa_import_zip new file mode 120000 index 000000000000..b0cdcd149e1b --- /dev/null +++ b/setup/l10n_it_fatturapa_import_zip/odoo/addons/l10n_it_fatturapa_import_zip @@ -0,0 +1 @@ +../../../../l10n_it_fatturapa_import_zip \ No newline at end of file diff --git a/setup/l10n_it_fatturapa_import_zip/setup.py b/setup/l10n_it_fatturapa_import_zip/setup.py new file mode 100644 index 000000000000..28c57bb64031 --- /dev/null +++ b/setup/l10n_it_fatturapa_import_zip/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) From a8e4a39eacfd9e3b2c821c0db1d7a3e7530cc8ab Mon Sep 17 00:00:00 2001 From: Simone Rubino Date: Mon, 31 Jul 2023 10:09:21 +0200 Subject: [PATCH 07/10] [MIG] l10n_it_fatturapa_import_zip: Migration to 16.0 --- l10n_it_fatturapa_import_zip/__init__.py | 1 - l10n_it_fatturapa_import_zip/__manifest__.py | 2 +- l10n_it_fatturapa_import_zip/models/attachment.py | 9 ++------- l10n_it_fatturapa_import_zip/tests/test_import_zip.py | 7 ++++++- l10n_it_fatturapa_import_zip/views/attachment_views.xml | 6 +----- .../wizards/wizard_import_fatturapa.py | 7 ++++--- 6 files changed, 14 insertions(+), 18 deletions(-) diff --git a/l10n_it_fatturapa_import_zip/__init__.py b/l10n_it_fatturapa_import_zip/__init__.py index b15de888c327..d6ade57604db 100644 --- a/l10n_it_fatturapa_import_zip/__init__.py +++ b/l10n_it_fatturapa_import_zip/__init__.py @@ -1,5 +1,4 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). from . import models -from . import tests from . import wizards diff --git a/l10n_it_fatturapa_import_zip/__manifest__.py b/l10n_it_fatturapa_import_zip/__manifest__.py index c4f6c7b57ec9..68bef9b4b920 100644 --- a/l10n_it_fatturapa_import_zip/__manifest__.py +++ b/l10n_it_fatturapa_import_zip/__manifest__.py @@ -5,7 +5,7 @@ "name": "ITA - Fattura elettronica - Import ZIP", "summary": "Permette di importare in uno ZIP diversi file XML di " "fatture elettroniche", - "version": "14.0.1.0.0", + "version": "16.0.1.0.0", "category": "Localization/Italy", "website": "https://github.com/OCA/l10n-italy", "author": "TAKOBI, Odoo Community Association (OCA)", diff --git a/l10n_it_fatturapa_import_zip/models/attachment.py b/l10n_it_fatturapa_import_zip/models/attachment.py index 14abadda6216..72aa338c7bba 100644 --- a/l10n_it_fatturapa_import_zip/models/attachment.py +++ b/l10n_it_fatturapa_import_zip/models/attachment.py @@ -35,7 +35,6 @@ class FatturaPAAttachmentImportZIP(models.Model): ("draft", "Draft"), ("done", "Completed"), ], - string="State", default="draft", required=True, readonly=True, @@ -49,12 +48,8 @@ class FatturaPAAttachmentImportZIP(models.Model): xml_in_count = fields.Integer( string="XML In Count", compute="_compute_invoices_data", readonly=True ) - invoices_out_count = fields.Integer( - string="Invoices Out Count", compute="_compute_invoices_data", readonly=True - ) - invoices_in_count = fields.Integer( - string="Invoices In Count", compute="_compute_invoices_data", readonly=True - ) + invoices_out_count = fields.Integer(compute="_compute_invoices_data", readonly=True) + invoices_in_count = fields.Integer(compute="_compute_invoices_data", readonly=True) attachment_out_ids = fields.One2many( "fatturapa.attachment.out", "attachment_import_zip_id", diff --git a/l10n_it_fatturapa_import_zip/tests/test_import_zip.py b/l10n_it_fatturapa_import_zip/tests/test_import_zip.py index 91fbdaeaac90..693d3f29b7d8 100644 --- a/l10n_it_fatturapa_import_zip/tests/test_import_zip.py +++ b/l10n_it_fatturapa_import_zip/tests/test_import_zip.py @@ -7,6 +7,11 @@ class TestImportZIP(FatturapaCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env.company.vat = "IT06363391001" + def setUp(self): super(TestImportZIP, self).setUp() self.attachment_import_model = self.env["fatturapa.attachment.import.zip"] @@ -27,7 +32,7 @@ def setUp(self): def cleanPartners(self): # VAT number used in tests, assigned to other partners by demo data, probably - main_company = self.env.ref("base.main_company") + main_company = self.env.company partners = self.env["res.partner"].search([("vat", "=", "IT06363391001")]) for partner in partners: if partner.id != main_company.partner_id.id: diff --git a/l10n_it_fatturapa_import_zip/views/attachment_views.xml b/l10n_it_fatturapa_import_zip/views/attachment_views.xml index 393b29ff3811..a811872a81ec 100644 --- a/l10n_it_fatturapa_import_zip/views/attachment_views.xml +++ b/l10n_it_fatturapa_import_zip/views/attachment_views.xml @@ -107,11 +107,7 @@ fatturapa_attachment_import_zip_tree fatturapa.attachment.import.zip - + diff --git a/l10n_it_fatturapa_import_zip/wizards/wizard_import_fatturapa.py b/l10n_it_fatturapa_import_zip/wizards/wizard_import_fatturapa.py index 6fe9beba6362..61db22795d1f 100644 --- a/l10n_it_fatturapa_import_zip/wizards/wizard_import_fatturapa.py +++ b/l10n_it_fatturapa_import_zip/wizards/wizard_import_fatturapa.py @@ -53,9 +53,10 @@ def _get_missing_journal_exception(self, company): if self._is_import_attachment_out(): exception = UserError( _( - "Define a sale journal for this company: '%s' (id: %d).", - company.name, - company.id, + "Define a sale journal for this company: " + "'%(company)s' (id: %(company_id)d).", + company=company.name, + company_id=company.id, ) ) else: From 280ecc56435e9f3929950153b831bda8d8f6bb34 Mon Sep 17 00:00:00 2001 From: Simone Rubino Date: Mon, 18 Sep 2023 16:25:32 +0200 Subject: [PATCH 08/10] [FIX] l10n_it_fatturapa_import_zip: Prevent bad zip file Sometimes the file that is being read still hasn't been written completely so it is not recognized as a zip file during parsing and raises exception "BadZipfile: File is not a zip file". --- l10n_it_fatturapa_import_zip/models/attachment.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/l10n_it_fatturapa_import_zip/models/attachment.py b/l10n_it_fatturapa_import_zip/models/attachment.py index 72aa338c7bba..10e6d8da8d2d 100644 --- a/l10n_it_fatturapa_import_zip/models/attachment.py +++ b/l10n_it_fatturapa_import_zip/models/attachment.py @@ -4,6 +4,7 @@ import base64 import tempfile import zipfile +from io import BytesIO from pathlib import Path from odoo import fields, models @@ -14,10 +15,8 @@ def _extract_zip_file(directory, datas): """Extract the zip file having content `datas` to `directory`.""" zip_data = base64.b64decode(datas) - with tempfile.NamedTemporaryFile(mode="wb") as tmp_file: - tmp_file.write(zip_data) - with zipfile.ZipFile(tmp_file.name, mode="r") as zip_ref: - zip_ref.extractall(directory) + with zipfile.ZipFile(BytesIO(zip_data)) as zip_ref: + zip_ref.extractall(directory) class FatturaPAAttachmentImportZIP(models.Model): From 35d63754fbb1bd184bbb76c9efc44bbb1c5aac04 Mon Sep 17 00:00:00 2001 From: Simone Rubino Date: Mon, 18 Sep 2023 16:36:04 +0200 Subject: [PATCH 09/10] [REF] l10n_it_fatturapa_in: Allow override for invoice partner --- l10n_it_fatturapa_in/wizard/wizard_import_fatturapa.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/l10n_it_fatturapa_in/wizard/wizard_import_fatturapa.py b/l10n_it_fatturapa_in/wizard/wizard_import_fatturapa.py index 61b3db74d8ce..2c964f455554 100644 --- a/l10n_it_fatturapa_in/wizard/wizard_import_fatturapa.py +++ b/l10n_it_fatturapa_in/wizard/wizard_import_fatturapa.py @@ -1876,6 +1876,11 @@ def _restore_original_precision(self, precision, original_precision): new_price_precision.sudo().write({"digits": original_precision}) new_cr.commit() + def _get_invoice_partner_id(self, fatt): + cedentePrestatore = fatt.FatturaElettronicaHeader.CedentePrestatore + partner_id = self.getCedPrest(cedentePrestatore) + return partner_id + def importFatturaPA(self): self.ensure_one() @@ -1908,7 +1913,7 @@ def importFatturaPA(self): fatt = self.get_invoice_obj(fatturapa_attachment) cedentePrestatore = fatt.FatturaElettronicaHeader.CedentePrestatore # 1.2 - partner_id = self.getCedPrest(cedentePrestatore) + partner_id = self._get_invoice_partner_id(fatt) # 1.3 TaxRappresentative = fatt.FatturaElettronicaHeader.RappresentanteFiscale # 1.5 From f3de844dac6aaf6f7ad0db8e7e010910acce52ff Mon Sep 17 00:00:00 2001 From: Simone Rubino Date: Mon, 18 Sep 2023 16:37:01 +0200 Subject: [PATCH 10/10] [FIX] l10n_it_fatturapa_import_zip: Customer is CessionarioCommittente --- l10n_it_fatturapa_import_zip/tests/test_import_zip.py | 10 ++++++++++ .../wizards/wizard_import_fatturapa.py | 9 +++++++++ 2 files changed, 19 insertions(+) diff --git a/l10n_it_fatturapa_import_zip/tests/test_import_zip.py b/l10n_it_fatturapa_import_zip/tests/test_import_zip.py index 693d3f29b7d8..3e7b44a7e11c 100644 --- a/l10n_it_fatturapa_import_zip/tests/test_import_zip.py +++ b/l10n_it_fatturapa_import_zip/tests/test_import_zip.py @@ -72,6 +72,16 @@ def test_import_zip(self): ), }, ], + "IT06363391001_00009.xml": [ + { + "partner_id": self.env["res.partner"].search( + [ + ("name", "=", "Foreign Customer"), + ], + limit=1, + ), + } + ], } for attachment in attachments_out: diff --git a/l10n_it_fatturapa_import_zip/wizards/wizard_import_fatturapa.py b/l10n_it_fatturapa_import_zip/wizards/wizard_import_fatturapa.py index 61db22795d1f..3fa93e140f27 100644 --- a/l10n_it_fatturapa_import_zip/wizards/wizard_import_fatturapa.py +++ b/l10n_it_fatturapa_import_zip/wizards/wizard_import_fatturapa.py @@ -25,6 +25,15 @@ def _check_attachment(self, attachment): result = super()._check_attachment(attachment) return result + def _get_invoice_partner_id(self, fatt): + if self._is_import_attachment_out(): + partner_id = self.getPartnerBase( + fatt.FatturaElettronicaHeader.CessionarioCommittente.DatiAnagrafici + ) + else: + partner_id = super()._get_invoice_partner_id(fatt) + return partner_id + def _extract_supplier(self, attachment): if self._is_import_attachment_out(): partner = self.env.company.partner_id