diff --git a/addons/account/static/src/js/tours/account.js b/addons/account/static/src/js/tours/account.js
index 3b30f0ee869..51d1ba5ab97 100644
--- a/addons/account/static/src/js/tours/account.js
+++ b/addons/account/static/src/js/tours/account.js
@@ -82,8 +82,8 @@ registry.category("web_tour.tours").add('account_tour', {
content: _t("Send the invoice to the customer and check what he'll receive."),
position: "bottom",
}, {
- trigger: "button[name=action_open_partners_without_email]",
- extra_trigger: "[name=move_type] [raw-value=out_invoice], [name=move_type][raw-value=out_invoice]",
+ trigger: "div[name=partner_missing_email] a",
+ extra_trigger: "[name=move_type] [raw-value=out_invoice]",
content: _t("Complete the partner data with email"),
}, {
trigger: ".o_field_widget[name=email] input, input[name=email]",
diff --git a/addons/account/wizard/account_move_send.py b/addons/account/wizard/account_move_send.py
index 4cd18956480..0661de1d9a8 100644
--- a/addons/account/wizard/account_move_send.py
+++ b/addons/account/wizard/account_move_send.py
@@ -41,7 +41,7 @@ class AccountMoveSend(models.TransientModel):
readonly=False,
)
display_mail_composer = fields.Boolean(compute='_compute_send_mail_extra_fields')
- send_mail_warning_message = fields.Boolean(compute='_compute_send_mail_extra_fields')
+ send_mail_warning_message = fields.Json(compute='_compute_send_mail_extra_fields')
send_mail_readonly = fields.Boolean(compute='_compute_send_mail_extra_fields')
mail_template_id = fields.Many2one(
comodel_name='mail.template',
@@ -254,7 +254,17 @@ class AccountMoveSend(models.TransientModel):
wizard.display_mail_composer = wizard.mode == 'invoice_single'
invoices_without_mail_data = wizard.move_ids.filtered(lambda x: not x.partner_id.email)
wizard.send_mail_readonly = invoices_without_mail_data == wizard.move_ids
- wizard.send_mail_warning_message = bool(invoices_without_mail_data) and (wizard.checkbox_send_mail or wizard.send_mail_readonly)
+ if not (invoices_without_mail_data and wizard.checkbox_send_mail or wizard.send_mail_readonly):
+ wizard.send_mail_warning_message = False
+ else:
+ partners = invoices_without_mail_data.partner_id
+ wizard.send_mail_warning_message = {
+ **(wizard.send_mail_warning_message or {}),
+ 'partner_missing_email': {
+ 'message': _("Partner(s) should have an email address."),
+ 'action_text': _("View Partner(s)"),
+ 'action': partners._get_records_action(name=_("Check Partner(s)"))
+ }}
@api.depends('mail_template_id')
def _compute_mail_lang(self):
@@ -326,25 +336,8 @@ class AccountMoveSend(models.TransientModel):
# -------------------------------------------------------------------------
def action_open_partners_without_email(self, res_ids=None):
- partners = self.move_ids.mapped("partner_id").filtered(lambda x: not x.email)
- if len(partners) == 1:
- return {
- 'type': 'ir.actions.act_window',
- 'res_model': 'res.partner',
- 'view_mode': 'form',
- 'target': 'current',
- 'res_id': partners.id,
- }
- else:
- return {
- 'type': 'ir.actions.act_window',
- 'res_model': 'res.partner',
- 'view_mode': 'tree,form',
- 'target': 'current',
- 'name': _('Partners without email'),
- 'context': {'create': False, 'delete': False},
- 'domain': [('id', 'in', partners.ids)],
- }
+ # TODO: remove this method in master
+ return self.move_ids.mapped("partner_id").filtered(lambda x: not x.email)._get_records_action(name=_("Partners without email"))
@api.model
def _need_invoice_document(self, invoice):
diff --git a/addons/account/wizard/account_move_send_views.xml b/addons/account/wizard/account_move_send_views.xml
index 77f536d1be0..051f2462727 100644
--- a/addons/account/wizard/account_move_send_views.xml
+++ b/addons/account/wizard/account_move_send_views.xml
@@ -18,16 +18,8 @@
-
-
-
- Please specify an email address on the
-
-
+
+
diff --git a/addons/l10n_it/models/account_tax.py b/addons/l10n_it/models/account_tax.py
index 08b22ee1952..ea8d47b5413 100644
--- a/addons/l10n_it/models/account_tax.py
+++ b/addons/l10n_it/models/account_tax.py
@@ -51,6 +51,11 @@ class AccountTax(models.Model):
if tax.l10n_it_exempt_reason == 'N6' and tax._l10n_it_is_split_payment():
raise UserError(_("Split Payment is not compatible with exoneration of kind 'N6'"))
+ def _l10n_it_get_tax_kind(self):
+ if self.amount_type == 'percent' and self.amount >= 0:
+ return 'vat'
+ return None
+
def _l10n_it_filter_kind(self, kind):
""" This can be overridden by l10n_it_edi_withholding for different kind of taxes (withholding, pension_fund)."""
return self if kind == 'vat' else self.env['account.tax']
diff --git a/addons/l10n_it_edi/__manifest__.py b/addons/l10n_it_edi/__manifest__.py
index d80191d59ae..44e9abeaf2f 100644
--- a/addons/l10n_it_edi/__manifest__.py
+++ b/addons/l10n_it_edi/__manifest__.py
@@ -4,7 +4,7 @@
{
'name': 'Italy - E-invoicing',
'countries': ['it'],
- 'version': '0.3',
+ 'version': '0.4',
'depends': [
'l10n_it',
'account_edi_proxy_client',
diff --git a/addons/l10n_it_edi/models/account_move.py b/addons/l10n_it_edi/models/account_move.py
index 58dae2581b0..96676b7a0bd 100644
--- a/addons/l10n_it_edi/models/account_move.py
+++ b/addons/l10n_it_edi/models/account_move.py
@@ -115,8 +115,8 @@ class AccountMove(models.Model):
@api.depends('commercial_partner_id.l10n_it_pa_index', 'company_id')
def _compute_l10n_it_partner_pa(self):
for move in self:
- move.l10n_it_partner_pa = (move.country_code == 'IT' and move.commercial_partner_id.l10n_it_pa_index and
- len(move.commercial_partner_id.l10n_it_pa_index) == 6)
+ partner = move.commercial_partner_id
+ move.l10n_it_partner_pa = partner and partner._l10n_it_edi_is_public_administration()
@api.depends('move_type', 'line_ids.tax_tag_ids')
def _compute_l10n_it_edi_is_self_invoice(self):
@@ -184,10 +184,8 @@ class AccountMove(models.Model):
"""
self.ensure_one()
- if (errors := self._l10n_it_edi_export_data_check()):
- message = _("Errors occured while creating the e-invoice file.")
- message += "\n- " + "\n- ".join(errors)
- raise UserError(message)
+ if self._l10n_it_edi_export_data_check():
+ raise UserError(_("The invoices you're trying to send have incomplete or incorrect data, please verify before sending."))
attachment_vals = self._l10n_it_edi_get_attachment_values(pdf_values=None)
self.env['ir.attachment'].create(attachment_vals)
@@ -460,10 +458,11 @@ class AccountMove(models.Model):
self.ensure_one()
template_reference = self.env.ref('l10n_it_edi.account_invoice_it_simplified_FatturaPA_export', raise_if_not_found=False)
buyer = self.commercial_partner_id
+ checks = ['partner_address_missing', 'partner_vat_codice_fiscale_missing']
return bool(
template_reference
and not self.l10n_it_edi_is_self_invoice
- and self._l10n_it_edi_export_buyer_data_check()
+ and list(buyer._l10n_it_edi_export_check(checks).keys()) == ['partner_address_missing']
and (not buyer.country_id or buyer.country_id.code == 'IT')
and (buyer.l10n_it_codice_fiscale or (buyer.vat and (buyer.vat[:2].upper() == 'IT' or buyer.vat[:2].isdecimal())))
and self.amount_total <= 400
@@ -1031,117 +1030,67 @@ class AccountMove(models.Model):
# -------------------------------------------------------------------------
def _l10n_it_edi_export_data_check(self):
- errors = self._l10n_it_edi_base_export_data_check()
- if not self._l10n_it_edi_is_simplified():
- errors += self._l10n_it_edi_export_buyer_data_check()
+ """ This function checks the Settings, Company, Partners, Moves involved in the
+ sending activity and returns an errors dictionary ready for the
+ actionable_errors widget to display. """
+
+ companies = self.mapped("company_id")
+ companies_partners = companies.mapped("partner_id")
+ moves_full = self.filtered(lambda m: not m._l10n_it_edi_is_simplified())
+ moves_simplified = self.filtered(lambda m: m._l10n_it_edi_is_simplified())
+
+ full = moves_full.mapped("commercial_partner_id").filtered(lambda p: p not in companies_partners)
+ simplified = moves_simplified.mapped("commercial_partner_id").filtered(lambda p: p not in companies_partners | full)
+ representatives = companies.mapped("l10n_it_tax_representative_partner_id").filtered(lambda p: p not in companies_partners | simplified | full)
+
+ return {
+ **companies._l10n_it_edi_export_check(),
+ **full._l10n_it_edi_export_check(['partner_address_missing']),
+ **simplified._l10n_it_edi_export_check(['partner_country_missing']),
+ **(simplified | full)._l10n_it_edi_export_check(['partner_vat_codice_fiscale_missing']),
+ **representatives._l10n_it_edi_export_check(['partner_vat_missing']),
+ **self._l10n_it_edi_base_export_check(),
+ **self._l10n_it_edi_export_taxes_check(),
+ }
+
+ def _l10n_it_edi_base_export_check(self):
+ def build_error(message, records):
+ return {
+ 'message': message,
+ **({
+ 'action_text': _("View invoice(s)"),
+ 'action': records._get_records_action(name=_("Invoice(s) to check")),
+ } if len(self) > 1 else {})
+ }
+
+ errors = {}
+ if moves := self.filtered(lambda move: move.l10n_it_edi_is_self_invoice and move._l10n_it_edi_services_or_goods() == 'both'):
+ errors['move_reverse_charge_with_mixed_services_and_goods'] = build_error(
+ message=_("Cannot apply Reverse Charge to bills which contains both services and goods."),
+ records=moves)
+ if pa_moves := self.filtered(lambda move: move.company_id.partner_id._l10n_it_edi_is_public_administration()):
+ if moves := pa_moves.filtered(lambda move: move.l10n_it_origin_document_type):
+ message = _("Your company belongs to the Public Administration, please fill out Origin Document Type field in the Electronic Invoicing tab.")
+ errors['move_missing_origin_document'] = build_error(message=message, records=moves)
+ if moves := pa_moves.filtered(lambda move: move.l10n_it_origin_document_date and move.l10n_it_origin_document_date > fields.Date.today()):
+ message = _("The Origin Document Date cannot be in the future.")
+ errors['move_future_origin_document_date'] = build_error(message=message, records=moves)
return errors
- def _l10n_it_edi_format_export_data_errors(self):
- messages = (
- self._l10n_it_edi_format_errors(move.name + ":" if len(self) > 1 else False, move_warnings)
- for move in self if (move_warnings := move._l10n_it_edi_export_data_check())
- )
- return Markup(" ").join(messages) or False
-
- def _l10n_it_edi_base_export_data_check(self):
- errors = []
- seller = self.company_id
- buyer = self.commercial_partner_id
- is_self_invoice = self.l10n_it_edi_is_self_invoice
- if is_self_invoice:
- seller, buyer = buyer, seller
-
- # <1.1.1.1>
- if not seller.country_id:
- errors.append(_("%s must have a country", seller.display_name))
-
- # <1.1.1.2>
- if not self.company_id.vat:
- errors.append(_("%s must have a VAT number", seller.display_name))
- if seller.vat and len(seller.vat) > 30:
- errors.append(_("The maximum length for VAT number is 30. %s have a VAT number too long: %s.", seller.display_name, seller.vat))
-
- # <1.2.1.2>
- if not is_self_invoice and not seller.l10n_it_codice_fiscale:
- errors.append(_("%s must have a codice fiscale number", seller.display_name))
-
- # <1.2.1.8>
- if not is_self_invoice and not seller.l10n_it_tax_system:
- errors.append(_("The seller's company must have a tax system."))
-
- # <1.2.2>
- if not seller.street and not seller.street2:
- errors.append(_("%s must have a street.", seller.display_name))
- if not seller.zip:
- errors.append(_("%s must have a post code.", seller.display_name))
- elif len(seller.zip) != 5 and seller.country_id.code == 'IT':
- errors.append(_("%s must have a post code of length 5.", seller.display_name))
- if not seller.city:
- errors.append(_("%s must have a city.", seller.display_name))
- if not seller.country_id:
- errors.append(_("%s must have a country.", seller.display_name))
-
- if not is_self_invoice and seller.l10n_it_has_tax_representative and not seller.l10n_it_tax_representative_partner_id.vat:
- errors.append(_("Tax representative partner %s of %s must have a tax number.", seller.l10n_it_tax_representative_partner_id.display_name, seller.display_name))
-
- # <1.4.1>
- if not buyer.vat and not buyer.l10n_it_codice_fiscale and buyer.country_id.code == 'IT':
- errors.append(_("The buyer, %s, or his company must have a VAT number and/or a tax code (Codice Fiscale).", buyer.display_name))
-
- if is_self_invoice and self._l10n_it_edi_services_or_goods() == 'both':
- errors.append(_("Cannot apply Reverse Charge to a bill which contains both services and goods."))
-
- if is_self_invoice and not buyer.partner_id.l10n_it_pa_index:
- errors.append(_("Vendor bills sent as self-invoices to the SdI require a valid PA Index (Codice Destinatario) on the company's contact."))
-
- for tax_line in self.line_ids.filtered(lambda line: line.tax_line_id):
- if not tax_line.tax_line_id.l10n_it_exempt_reason and tax_line.tax_line_id.amount == 0:
- errors.append(_("%s has an amount of 0.0, you must indicate the kind of exoneration.", tax_line.name))
-
- if self.l10n_it_partner_pa:
- if not self.l10n_it_origin_document_type:
- errors.append(_("This invoice targets the Public Administration, please fill out"
- " Origin Document Type field in the Electronic Invoicing tab."))
- if self.l10n_it_origin_document_date and self.l10n_it_origin_document_date > fields.Date.today():
- errors.append(_("The Origin Document Date cannot be in the future."))
-
- errors += self._l10n_it_edi_export_taxes_data_check()
-
- return errors
-
- def _l10n_it_edi_export_taxes_data_check(self):
- """
- Can be overridden by submodules like l10n_it_edi_withholding, which also allows for withholding and pension_fund taxes.
- """
- errors = []
- for invoice_line in self.invoice_line_ids.filtered(lambda x: x.display_type == 'product'):
- all_taxes = invoice_line.tax_ids.flatten_taxes_hierarchy()
- vat_taxes = all_taxes.filtered(lambda t: t.amount_type == 'percent' and t.amount >= 0)
- if len(vat_taxes) != 1:
- errors.append(_("In line %s, you must select one and only one VAT tax.", invoice_line.name))
- return errors
-
- def _l10n_it_edi_export_buyer_data_check(self):
- errors = []
- buyer = self.commercial_partner_id
-
- # <1.4.2>
- if not buyer.street and not buyer.street2:
- errors.append(_("%s must have a street.", buyer.display_name))
- if not buyer.country_id:
- errors.append(_("%s must have a country.", buyer.display_name))
- if not buyer.zip:
- errors.append(_("%s must have a post code.", buyer.display_name))
- elif len(buyer.zip) != 5 and buyer.country_id.code == 'IT':
- errors.append(_("%s must have a post code of length 5.", buyer.display_name))
- if not buyer.city:
- errors.append(_("%s must have a city.", buyer.display_name))
-
- for tax_line in self.line_ids.filtered(lambda line: line.tax_line_id):
- if not tax_line.tax_line_id.l10n_it_exempt_reason and tax_line.tax_line_id.amount == 0:
- errors.append(_("%s has an amount of 0.0, you must indicate the kind of exoneration.", tax_line.name))
-
- return errors
+ def _l10n_it_edi_export_taxes_check(self):
+ if move_lines := self.mapped("invoice_line_ids").filtered(lambda line:
+ line.display_type == 'product'
+ and len(line.tax_ids.flatten_taxes_hierarchy()._l10n_it_filter_kind('vat')) != 1
+ ):
+ return {
+ 'move_only_one_vat_tax_per_line': {
+ 'message': _("Invoices must have exactly one VAT tax set per line."),
+ **({
+ 'action_text': _("View invoice(s)"),
+ 'action': move_lines.mapped("move_id")._get_records_action(name=_("Check taxes on invoice lines")),
+ } if len(self) > 1 else {})
+ }}
+ return {}
def _l10n_it_edi_get_formatters(self):
def format_alphanumeric(text, maxlen=None):
diff --git a/addons/l10n_it_edi/models/res_company.py b/addons/l10n_it_edi/models/res_company.py
index e7503cf2623..24b06655036 100644
--- a/addons/l10n_it_edi/models/res_company.py
+++ b/addons/l10n_it_edi/models/res_company.py
@@ -115,3 +115,41 @@ class ResCompany(models.Model):
def _compute_l10n_it_edi_proxy_user_id(self):
for company in self:
company.l10n_it_edi_proxy_user_id = company.account_edi_proxy_client_ids.filtered(lambda x: x.proxy_type == 'l10n_it_edi')
+
+ def _l10n_it_edi_export_check(self):
+ checks = {
+ 'company_vat_codice_fiscale_missing': {
+ 'fields': [('vat', 'l10n_it_codice_fiscale')],
+ 'message': _("Company/ies should have a VAT number or Codice Fiscale."),
+ },
+ 'company_address_missing': {
+ 'fields': [('street', 'street2'), ('zip',), ('city',), ('country_id',)],
+ 'message': _("Company/ies should have a complete address, verify their Street, City, Zipcode and Country."),
+ },
+ 'company_l10n_it_tax_system_missing': {
+ 'fields': [('l10n_it_tax_system',)],
+ 'message': _("Company/ies should have a Tax System"),
+ },
+ }
+ errors = {}
+ for key, check in checks.items():
+ for fields_tuple in check.pop('fields'):
+ if invalid_records := self.filtered(lambda record: not any(record[field] for field in fields_tuple)):
+ errors[key] = {
+ 'message': check['message'],
+ 'action_text': _("View Company/ies"),
+ 'action': invalid_records._get_records_action(name=_("Check Company Data")),
+ }
+ if self.filtered(lambda x: not x.l10n_it_edi_proxy_user_id):
+ new_context = {
+ **self.env.context,
+ 'module': 'account',
+ 'default_search_setting': _("Italian Electronic Invoicing"),
+ 'bin_size': False,
+ }
+ errors['settings_l10n_it_edi_proxy_user_id'] = {
+ 'message': _("You must accept the terms and conditions in the Settings to use the IT EDI."),
+ 'action_text': _("View Settings"),
+ 'action': self.env['res.config.settings']._get_records_action(name=_("Settings"), context=new_context),
+ }
+ return errors
diff --git a/addons/l10n_it_edi/models/res_partner.py b/addons/l10n_it_edi/models/res_partner.py
index f2f7f3073a5..9d5089aa1e8 100644
--- a/addons/l10n_it_edi/models/res_partner.py
+++ b/addons/l10n_it_edi/models/res_partner.py
@@ -36,8 +36,7 @@ class ResPartner(models.Model):
def _l10n_it_edi_is_public_administration(self):
""" Returns True if the destination of the FatturaPA belongs to the Public Administration. """
self.ensure_one()
- return len(self.l10n_it_pa_index or '') == 6
-
+ return self.country_id.code == 'IT' and len(self.l10n_it_pa_index or '') == 6
def _l10n_it_edi_get_values(self):
""" Generates all partner values needed by l10n_it_edi XML export.
@@ -148,3 +147,39 @@ class ResPartner(models.Model):
for record in self:
if record.l10n_it_codice_fiscale and (not codicefiscale.is_valid(record.l10n_it_codice_fiscale) and not iva.is_valid(record.l10n_it_codice_fiscale)):
raise UserError(_("Invalid Codice Fiscale '%s': should be like 'MRTMTT91D08F205J' for physical person and '12345670546' for businesses.", record.l10n_it_codice_fiscale))
+
+ def _l10n_it_edi_export_check(self, checks=None):
+ checks = checks or ['partner_vat_codice_fiscale_missing', 'partner_address_missing']
+ fields_to_check = {
+ 'partner_vat_missing': {
+ 'fields': [('vat',)],
+ 'message': _("Partner(s) should have a VAT number."),
+ },
+ 'partner_vat_codice_fiscale_missing': {
+ 'fields': [('vat', 'l10n_it_codice_fiscale')],
+ 'message': _("Partner(s) should have a VAT number or Codice Fiscale."),
+ },
+ 'partner_country_missing': {
+ 'fields': [('country_id',)],
+ 'message': _("Partner(s) should have a Country when used for simplified invoices."),
+ },
+ 'partner_address_missing': {
+ 'fields': [('street', 'street2'), ('zip',), ('city',), ('country_id',)],
+ 'message': _("Partner(s) should have a complete address, verify their Street, City, Zipcode and Country."),
+ },
+ }
+ selected_checks = {k: v for k, v in fields_to_check.items() if k in checks}
+ single_views = [(False, 'form')]
+ list_view = (self.env.ref('l10n_it_edi.res_partner_tree_l10n_it', raise_if_not_found=False))
+ multi_views = [(list_view.id if list_view else False, 'list'), (False, 'form')]
+ errors = {}
+ for key, check in selected_checks.items():
+ for fields_tuple in check['fields']:
+ if invalid_records := self.filtered(lambda record: not any(record[field] for field in fields_tuple)):
+ views = single_views if len(invalid_records) == 1 else multi_views
+ errors[key] = {
+ 'message': check['message'],
+ 'action_text': _("View Partner(s)"),
+ 'action': invalid_records._get_records_action(name=_("Check Partner(s)"), views=views),
+ }
+ return errors
diff --git a/addons/l10n_it_edi/tests/test_edi_export.py b/addons/l10n_it_edi/tests/test_edi_export.py
index 2254dc36e4d..71e67f94b3e 100644
--- a/addons/l10n_it_edi/tests/test_edi_export.py
+++ b/addons/l10n_it_edi/tests/test_edi_export.py
@@ -230,10 +230,7 @@ class TestItEdiExport(TestItEdi):
}),
],
})
-
- expected = ['Alessi must have a street.', 'Alessi must have a country.', 'Alessi must have a post code.', 'Alessi must have a city.']
- actual = invoice._l10n_it_edi_export_data_check()
- self.assertEqual(expected, actual)
+ self.assertEqual(['partner_address_missing'], list(invoice._l10n_it_edi_export_data_check().keys()))
def test_invoice_non_domestic_simplified(self):
invoice = self.env['account.move'].with_company(self.company).create({
@@ -249,9 +246,7 @@ class TestItEdiExport(TestItEdi):
}),
],
})
- expected = ['Alessi must have a street.', 'Alessi must have a post code.', 'Alessi must have a city.']
- actual = invoice._l10n_it_edi_export_data_check()
- self.assertEqual(expected, actual)
+ self.assertEqual(['partner_address_missing'], list(invoice._l10n_it_edi_export_data_check().keys()))
def test_invoice_zero_percent_taxes(self):
tax_zero_percent_hundred_percent_repartition = self.env['account.tax'].with_company(self.company).create({
diff --git a/addons/l10n_it_edi/views/l10n_it_view.xml b/addons/l10n_it_edi/views/l10n_it_view.xml
index b6d5e0a67a5..ff889c7e103 100644
--- a/addons/l10n_it_edi/views/l10n_it_view.xml
+++ b/addons/l10n_it_edi/views/l10n_it_view.xml
@@ -1,6 +1,19 @@
+
+ res.partner.tree.l10n.it
+ primary
+ res.partner
+
+
+
+
+
+
+
+
+
res.partner.form.l10n.itres.partner
@@ -109,7 +122,7 @@
+ or l10n_it_edi_state in ('forwarded', 'accepted_by_pa_partner', 'accepted_by_pa_partner_after_expiry', 'forward_failed')">
diff --git a/addons/l10n_it_edi/wizard/account_move_send.py b/addons/l10n_it_edi/wizard/account_move_send.py
index 9bb86d8525f..acca4a88a48 100644
--- a/addons/l10n_it_edi/wizard/account_move_send.py
+++ b/addons/l10n_it_edi/wizard/account_move_send.py
@@ -1,22 +1,22 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from markupsafe import Markup, escape
+
from odoo import _, api, fields, models
+from odoo.addons.base.models.ir_qweb_fields import nl2br
class AccountMoveSend(models.TransientModel):
_inherit = 'account.move.send'
- l10n_it_edi_warning_message = fields.Html(compute='_compute_l10n_it_edi_xml_export')
-
+ l10n_it_edi_warning_message = fields.Html(compute='_compute_l10n_it_edi_warning_message')
+ l10n_it_edi_actionable_errors = fields.Json(compute='_compute_l10n_it_edi_xml_export')
l10n_it_edi_enable_xml_export = fields.Boolean(compute='_compute_l10n_it_edi_xml_export')
l10n_it_edi_readonly_xml_export = fields.Boolean(compute='_compute_l10n_it_edi_xml_export')
l10n_it_edi_checkbox_xml_export = fields.Boolean('E-invoice XML',
compute='_compute_l10n_it_edi_checkbox_xml_export',
store=True,
- readonly=False,
- help="Create the e-invoice XML ready to be sent to the Italian Tax Agency.\n"
- "It is set as readonly if a report has already been created, to avoid inconsistencies.\n"
- "To re-enable it, delete the PDF attachment.")
+ readonly=False)
l10n_it_edi_enable_send = fields.Boolean(compute='_compute_l10n_it_edi_enable_readonly_send')
l10n_it_edi_readonly_send = fields.Boolean(compute='_compute_l10n_it_edi_enable_readonly_send')
@@ -37,30 +37,54 @@ class AccountMoveSend(models.TransientModel):
# COMPUTE/CONSTRAINS METHODS
# -------------------------------------------------------------------------
+ @api.depends('l10n_it_edi_actionable_errors')
+ def _compute_l10n_it_edi_warning_message(self):
+ # To be removed -- Proxy feature to be replaced with actionable_errors as soon as the user updates the module
+ for wizard in self:
+ messages = []
+ if wizard.l10n_it_edi_actionable_errors:
+ messages.append(_("Please upgrade the Italian EDI module to update this widget."))
+ messages.append(_("Go to Applications page and update the 'Italia - Fatturazione Elettronica' module."))
+ messages.append("")
+ for error_key, error_data in wizard.l10n_it_edi_actionable_errors.items():
+ message = error_data['message']
+ split = error_key.split("_")
+ if len(split) > 1 and (model_id := {
+ 'partner': 'res.partner',
+ 'move': 'account.move',
+ 'company': 'res.company'
+ }.get(split[0], None)):
+ action = error_data['action']
+ if 'res_id' in action:
+ record_ids = [action['res_id']]
+ else:
+ record_ids = action['domain'][0][2]
+ records = self.env[model_id].browse(record_ids)
+ message = f"{message} - {', '.join(records.mapped('display_name'))}"
+ messages.append(nl2br(escape(message)))
+ wizard.l10n_it_edi_warning_message = Markup(" ").join(messages)
+
@api.depends('move_ids')
def _compute_l10n_it_edi_xml_export(self):
for wizard in self:
if wizard.company_id.account_fiscal_country_id.code == 'IT':
- if not wizard.company_id.l10n_it_edi_proxy_user_id:
- wizard.l10n_it_edi_warning_message = _("You must accept the terms and conditions in the Settings to use the IT EDI.")
- else:
- wizard.l10n_it_edi_warning_message = wizard.move_ids._l10n_it_edi_format_export_data_errors()
has_pdf_but_no_xml = any(move.invoice_pdf_report_id and not move.l10n_it_edi_attachment_id for move in wizard.move_ids)
all_have_xml = all(move.l10n_it_edi_attachment_id for move in wizard.move_ids)
+ wizard.l10n_it_edi_actionable_errors = self.move_ids._l10n_it_edi_export_data_check()
wizard.l10n_it_edi_enable_xml_export = any(m._l10n_it_edi_ready_for_xml_export() for m in wizard.move_ids)
- wizard.l10n_it_edi_readonly_xml_export = bool(wizard.l10n_it_edi_warning_message) or has_pdf_but_no_xml or all_have_xml
+ wizard.l10n_it_edi_readonly_xml_export = wizard.l10n_it_edi_actionable_errors or has_pdf_but_no_xml or all_have_xml
else:
- wizard.l10n_it_edi_warning_message = False
+ wizard.l10n_it_edi_actionable_errors = False
wizard.l10n_it_edi_enable_xml_export = False
wizard.l10n_it_edi_readonly_xml_export = False
- @api.depends('move_ids', 'l10n_it_edi_checkbox_xml_export', 'l10n_it_edi_warning_message')
+ @api.depends('move_ids', 'l10n_it_edi_checkbox_xml_export', 'l10n_it_edi_actionable_errors')
def _compute_l10n_it_edi_enable_readonly_send(self):
for wizard in self:
if wizard.company_id.account_fiscal_country_id.code == 'IT':
xml_already_sent = all(m.l10n_it_edi_state not in (False, 'rejected') for m in wizard.move_ids)
wizard.l10n_it_edi_enable_send = wizard.l10n_it_edi_checkbox_xml_export
- wizard.l10n_it_edi_readonly_send = bool(wizard.l10n_it_edi_warning_message) or xml_already_sent
+ wizard.l10n_it_edi_readonly_send = bool(wizard.l10n_it_edi_actionable_errors or xml_already_sent)
else:
wizard.l10n_it_edi_enable_send = False
wizard.l10n_it_edi_readonly_send = False
diff --git a/addons/l10n_it_edi/wizard/account_move_send_views.xml b/addons/l10n_it_edi/wizard/account_move_send_views.xml
index da8aad0425a..6d1bca4edf6 100644
--- a/addons/l10n_it_edi/wizard/account_move_send_views.xml
+++ b/addons/l10n_it_edi/wizard/account_move_send_views.xml
@@ -10,15 +10,18 @@
-
-
-
+
+
diff --git a/addons/l10n_it_edi_withholding/models/account_move.py b/addons/l10n_it_edi_withholding/models/account_move.py
index 5beadd517c2..18489f1238f 100644
--- a/addons/l10n_it_edi_withholding/models/account_move.py
+++ b/addons/l10n_it_edi_withholding/models/account_move.py
@@ -19,10 +19,11 @@ class AccountMove(models.Model):
@api.depends('amount_total_signed')
def _compute_amount_extended(self):
for move in self:
- totals = dict(vat=0.0, withholding=0.0, pension_fund=0.0)
+ totals = {None: 0.0, 'vat':0.0, 'withholding': 0.0, 'pension_fund': 0.0}
if move.is_invoice(True):
for line in [line for line in move.line_ids if line.tax_line_id]:
- totals[line.tax_line_id._l10n_it_get_tax_kind()] -= line.balance
+ kind = line.tax_line_id._l10n_it_get_tax_kind()
+ totals[kind] -= line.balance
move.l10n_it_amount_vat_signed = totals['vat']
move.l10n_it_amount_withholding_signed = totals['withholding']
move.l10n_it_amount_pension_fund_signed = totals['pension_fund']
diff --git a/addons/l10n_it_edi_withholding/models/account_tax.py b/addons/l10n_it_edi_withholding/models/account_tax.py
index 0e3b3ba1657..324b27e9de3 100644
--- a/addons/l10n_it_edi_withholding/models/account_tax.py
+++ b/addons/l10n_it_edi_withholding/models/account_tax.py
@@ -89,7 +89,7 @@ class AccountTax(models.Model):
def _l10n_it_get_tax_kind(self):
return ((self.l10n_it_withholding_type and 'withholding')
or (self.l10n_it_pension_fund_type and 'pension_fund')
- or 'vat')
+ or super()._l10n_it_get_tax_kind())
def _l10n_it_filter_kind(self, kind):
""" Filters taxes depending on _l10n_it_get_tax_kind. """