487 lines
24 KiB
Python
487 lines
24 KiB
Python
|
# -*- coding:utf-8 -*-
|
|||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|||
|
|
|||
|
import base64
|
|||
|
from functools import reduce
|
|||
|
import logging
|
|||
|
import re
|
|||
|
|
|||
|
from datetime import datetime
|
|||
|
|
|||
|
from odoo import api, fields, models, _
|
|||
|
from odoo.tools import float_repr, float_compare
|
|||
|
from odoo.exceptions import UserError, ValidationError
|
|||
|
|
|||
|
|
|||
|
_logger = logging.getLogger(__name__)
|
|||
|
|
|||
|
DEFAULT_FACTUR_ITALIAN_DATE_FORMAT = '%Y-%m-%d'
|
|||
|
|
|||
|
|
|||
|
class AccountMove(models.Model):
|
|||
|
_inherit = 'account.move'
|
|||
|
|
|||
|
l10n_it_edi_transaction = fields.Char(copy=False, string="FatturaPA Transaction")
|
|||
|
l10n_it_edi_attachment_id = fields.Many2one('ir.attachment', copy=False, string="FatturaPA Attachment", ondelete="restrict")
|
|||
|
|
|||
|
l10n_it_stamp_duty = fields.Float(string="Dati Bollo", readonly=True, states={'draft': [('readonly', False)]})
|
|||
|
|
|||
|
l10n_it_ddt_id = fields.Many2one('l10n_it.ddt', string='DDT', readonly=True, states={'draft': [('readonly', False)]}, copy=False)
|
|||
|
|
|||
|
l10n_it_einvoice_name = fields.Char(compute='_compute_l10n_it_einvoice')
|
|||
|
|
|||
|
l10n_it_einvoice_id = fields.Many2one('ir.attachment', string="Electronic invoice", compute='_compute_l10n_it_einvoice')
|
|||
|
|
|||
|
def _get_l10n_it_amount_split_payment(self):
|
|||
|
self.ensure_one()
|
|||
|
amount = 0.0
|
|||
|
if self.is_sale_document(False):
|
|||
|
for line in self.line_ids:
|
|||
|
if line.tax_line_id and line.tax_line_id._l10n_it_is_split_payment():
|
|||
|
if self.move_type == 'out_invoice':
|
|||
|
amount += line.credit
|
|||
|
else:
|
|||
|
amount += line.debit
|
|||
|
return amount
|
|||
|
|
|||
|
@api.depends('edi_document_ids', 'edi_document_ids.attachment_id')
|
|||
|
def _compute_l10n_it_einvoice(self):
|
|||
|
fattura_pa = self.env.ref('l10n_it_edi.edi_fatturaPA')
|
|||
|
for invoice in self:
|
|||
|
einvoice = invoice.edi_document_ids.filtered(lambda d: d.edi_format_id == fattura_pa).sudo()
|
|||
|
invoice.l10n_it_einvoice_id = einvoice.attachment_id
|
|||
|
invoice.l10n_it_einvoice_name = einvoice.attachment_id.name
|
|||
|
|
|||
|
@api.depends('l10n_it_edi_transaction')
|
|||
|
def _compute_show_reset_to_draft_button(self):
|
|||
|
super(AccountMove, self)._compute_show_reset_to_draft_button()
|
|||
|
for move in self.filtered(lambda m: m.l10n_it_edi_transaction):
|
|||
|
move.show_reset_to_draft_button = False
|
|||
|
|
|||
|
def invoice_generate_xml(self):
|
|||
|
self.ensure_one()
|
|||
|
report_name = self.env['account.edi.format']._l10n_it_edi_generate_electronic_invoice_filename(self)
|
|||
|
|
|||
|
data = "<?xml version='1.0' encoding='UTF-8'?>" + str(self._l10n_it_edi_export_invoice_as_xml())
|
|||
|
description = _('Italian invoice: %s', self.move_type)
|
|||
|
attachment = self.env['ir.attachment'].create({
|
|||
|
'name': report_name,
|
|||
|
'res_id': self.id,
|
|||
|
'res_model': self._name,
|
|||
|
'raw': data.encode(),
|
|||
|
'description': description,
|
|||
|
'type': 'binary',
|
|||
|
})
|
|||
|
|
|||
|
self.message_post(
|
|||
|
body=(_("E-Invoice is generated on %s by %s") % (fields.Datetime.now(), self.env.user.display_name))
|
|||
|
)
|
|||
|
return {'attachment': attachment}
|
|||
|
|
|||
|
def _is_commercial_partner_pa(self):
|
|||
|
"""
|
|||
|
Returns True if the destination of the FatturaPA belongs to the Public Administration.
|
|||
|
"""
|
|||
|
return len(self.commercial_partner_id.l10n_it_pa_index or '') == 6
|
|||
|
|
|||
|
def _l10n_it_edi_prepare_fatturapa_line_details(self, reverse_charge_refund=False, is_downpayment=False, convert_to_euros=True):
|
|||
|
""" Returns a list of dictionaries passed to the template for the invoice lines (DettaglioLinee)
|
|||
|
"""
|
|||
|
invoice_lines = []
|
|||
|
lines = self.invoice_line_ids.filtered(lambda l: not l.display_type in ('line_note', 'line_section'))
|
|||
|
for num, line in enumerate(lines):
|
|||
|
sign = -1 if line.move_id.is_inbound() else 1
|
|||
|
price_subtotal = (line.balance * sign) if convert_to_euros else line.price_subtotal
|
|||
|
# The price_subtotal should be inverted when the line is a reverse charge refund.
|
|||
|
if reverse_charge_refund:
|
|||
|
price_subtotal = -price_subtotal
|
|||
|
|
|||
|
# Unit price
|
|||
|
price_unit = 0
|
|||
|
if line.quantity and line.discount != 100.0:
|
|||
|
price_unit = price_subtotal / ((1 - (line.discount or 0.0) / 100.0) * abs(line.quantity))
|
|||
|
else:
|
|||
|
price_unit = line.price_unit
|
|||
|
|
|||
|
description = line.name
|
|||
|
|
|||
|
# Down payment lines:
|
|||
|
# If there was a down paid amount that has been deducted from this move,
|
|||
|
# we need to put a reference to the down payment invoice in the DatiFattureCollegate tag
|
|||
|
downpayment_moves = self.env['account.move']
|
|||
|
if not is_downpayment and line.price_subtotal < 0:
|
|||
|
downpayment_moves = line._get_downpayment_lines().mapped("move_id")
|
|||
|
if downpayment_moves:
|
|||
|
downpayment_moves_description = ', '.join([m.name for m in downpayment_moves])
|
|||
|
sep = ', ' if description else ''
|
|||
|
description = f"{description}{sep}{downpayment_moves_description}"
|
|||
|
|
|||
|
vat_tax = line.tax_ids.flatten_taxes_hierarchy().filtered(lambda t: t._l10n_it_filter_kind('vat') and t.amount >= 0)
|
|||
|
invoice_lines.append({
|
|||
|
'line': line,
|
|||
|
'line_number': num + 1,
|
|||
|
'description': description or 'NO NAME',
|
|||
|
'unit_price': price_unit,
|
|||
|
'subtotal_price': price_subtotal,
|
|||
|
'vat_tax': vat_tax,
|
|||
|
'downpayment_moves': downpayment_moves,
|
|||
|
})
|
|||
|
return invoice_lines
|
|||
|
|
|||
|
def _l10n_it_edi_prepare_fatturapa_tax_details(self, tax_details, reverse_charge_refund=False):
|
|||
|
""" Returns a list of dictionaries passed to the template for the invoice lines (DatiRiepilogo)
|
|||
|
"""
|
|||
|
tax_lines = []
|
|||
|
for _tax_name, tax_dict in tax_details['tax_details'].items():
|
|||
|
# The assumption is that the company currency is EUR.
|
|||
|
tax = tax_dict['tax']
|
|||
|
base_amount = tax_dict['base_amount']
|
|||
|
tax_amount = tax_dict['tax_amount']
|
|||
|
tax_rate = tax.amount
|
|||
|
tax_exigibility_code = (
|
|||
|
'S' if tax._l10n_it_is_split_payment()
|
|||
|
else 'D' if tax.tax_exigibility == 'on_payment'
|
|||
|
else 'I' if tax.tax_exigibility == 'on_invoice'
|
|||
|
else False
|
|||
|
)
|
|||
|
expected_base_amount = tax_amount * 100 / tax_rate if tax_rate else False
|
|||
|
# Constraints within the edi make local rounding on price included taxes a problem.
|
|||
|
# To solve this there is a <Arrotondamento> or 'rounding' field, such that:
|
|||
|
# taxable base = sum(taxable base for each unit) + Arrotondamento
|
|||
|
if tax.price_include and tax.amount_type == 'percent':
|
|||
|
if expected_base_amount and float_compare(base_amount, expected_base_amount, 2):
|
|||
|
tax_dict['rounding'] = base_amount - (tax_amount * 100 / tax_rate)
|
|||
|
tax_dict['base_amount'] = base_amount - tax_dict['rounding']
|
|||
|
|
|||
|
tax_line_dict = {
|
|||
|
'tax': tax,
|
|||
|
'rounding': tax_dict.get('rounding', False),
|
|||
|
'base_amount': tax_dict['base_amount'],
|
|||
|
'tax_amount': tax_dict['tax_amount'],
|
|||
|
'exigibility_code': tax_exigibility_code,
|
|||
|
}
|
|||
|
tax_lines.append(tax_line_dict)
|
|||
|
return tax_lines
|
|||
|
|
|||
|
def _l10n_it_edi_filter_fatturapa_tax_details(self, line, tax_values):
|
|||
|
"""Filters tax details to only include the positive amounted lines regarding VAT taxes."""
|
|||
|
repartition_line = tax_values['tax_repartition_line']
|
|||
|
return (repartition_line.factor_percent >= 0 and repartition_line.tax_id.amount >= 0)
|
|||
|
|
|||
|
def _prepare_fatturapa_export_values(self):
|
|||
|
self.ensure_one()
|
|||
|
|
|||
|
def format_date(dt):
|
|||
|
# Format the date in the italian standard.
|
|||
|
dt = dt or datetime.now()
|
|||
|
return dt.strftime(DEFAULT_FACTUR_ITALIAN_DATE_FORMAT)
|
|||
|
|
|||
|
def format_monetary(number, currency):
|
|||
|
# Format the monetary values to avoid trailing decimals (e.g. 90.85000000000001).
|
|||
|
return float_repr(number, min(2, currency.decimal_places))
|
|||
|
|
|||
|
def format_numbers(number):
|
|||
|
#format number to str with between 2 and 8 decimals (event if it's .00)
|
|||
|
number_splited = str(number).split('.')
|
|||
|
if len(number_splited) == 1:
|
|||
|
return "%.02f" % number
|
|||
|
|
|||
|
cents = number_splited[1]
|
|||
|
if len(cents) > 8:
|
|||
|
return "%.08f" % number
|
|||
|
return float_repr(number, max(2, len(cents)))
|
|||
|
|
|||
|
def format_numbers_two(number):
|
|||
|
#format number to str with 2 (event if it's .00)
|
|||
|
return "%.02f" % number
|
|||
|
|
|||
|
def discount_type(discount):
|
|||
|
return 'SC' if discount > 0 else 'MG'
|
|||
|
|
|||
|
def format_phone(number):
|
|||
|
if not number:
|
|||
|
return False
|
|||
|
number = number.replace(' ', '').replace('/', '').replace('.', '')
|
|||
|
if len(number) > 4 and len(number) < 13:
|
|||
|
return number
|
|||
|
return False
|
|||
|
|
|||
|
def get_vat_number(vat):
|
|||
|
if vat[:2].isdecimal():
|
|||
|
return vat.replace(' ', '')
|
|||
|
return vat[2:].replace(' ', '')
|
|||
|
|
|||
|
def get_vat_country(vat):
|
|||
|
if vat[:2].isdecimal():
|
|||
|
return 'IT'
|
|||
|
return vat[:2].upper()
|
|||
|
|
|||
|
def format_alphanumeric(text_to_convert):
|
|||
|
return text_to_convert.encode('latin-1', 'replace').decode('latin-1') if text_to_convert else False
|
|||
|
|
|||
|
def get_vat_values(partner):
|
|||
|
""" Generate the VAT and country code needed by l10n_it_edi XML export.
|
|||
|
|
|||
|
VAT number:
|
|||
|
If there is a VAT number and the partner is not in EU and San Marino, then the exported value is 'OO99999999999'
|
|||
|
If there is a VAT number and the partner is in EU or San Marino, then remove the country prefix
|
|||
|
If there is no VAT and the partner is not in Italy, then the exported value is '0000000'
|
|||
|
If there is no VAT and the partner is in Italy, the VAT is not set and Codice Fiscale will be relevant in the XML.
|
|||
|
If there is no VAT and no Codice Fiscale, the invoice is not even exported, so this case is not handled.
|
|||
|
|
|||
|
Country:
|
|||
|
First, take the country configured on the partner.
|
|||
|
If there's a codice fiscale and no country, the country is 'IT'.
|
|||
|
"""
|
|||
|
europe = self.env.ref('base.europe', raise_if_not_found=False)
|
|||
|
in_eu = europe and partner.country_id and partner.country_id in europe.country_ids
|
|||
|
is_sm = partner.country_code == 'SM'
|
|||
|
|
|||
|
normalized_vat = partner.vat
|
|||
|
normalized_country = partner.country_code
|
|||
|
has_vat = partner.vat and not partner.vat in ['/', 'NA']
|
|||
|
if has_vat:
|
|||
|
normalized_vat = partner.vat.replace(' ', '')
|
|||
|
if in_eu:
|
|||
|
# If the partner is from the EU, the country-code prefix of the VAT must be taken away
|
|||
|
if not normalized_vat[:2].isdecimal():
|
|||
|
normalized_vat = normalized_vat[2:]
|
|||
|
# If customer is from San Marino
|
|||
|
elif is_sm:
|
|||
|
normalized_vat = normalized_vat if normalized_vat[:2].isdecimal() else normalized_vat[2:]
|
|||
|
# The Tax Agency arbitrarily decided that non-EU VAT are not interesting,
|
|||
|
# so this default code is used instead
|
|||
|
# Detect the country code from the partner country instead
|
|||
|
else:
|
|||
|
normalized_vat = 'OO99999999999'
|
|||
|
|
|||
|
# If it has a codice fiscale (and no country), it's an Italian partner
|
|||
|
if not normalized_country and partner.l10n_it_codice_fiscale:
|
|||
|
normalized_country = 'IT'
|
|||
|
# If customer has not VAT
|
|||
|
elif not has_vat and partner.country_id and partner.country_id.code != 'IT':
|
|||
|
normalized_vat = '0000000'
|
|||
|
|
|||
|
return {
|
|||
|
'vat': normalized_vat,
|
|||
|
'country_code': normalized_country,
|
|||
|
}
|
|||
|
|
|||
|
formato_trasmissione = "FPA12" if self._is_commercial_partner_pa() else "FPR12"
|
|||
|
|
|||
|
# Flags
|
|||
|
in_eu = self.env['account.edi.format']._l10n_it_edi_partner_in_eu
|
|||
|
is_self_invoice = self.env['account.edi.format']._l10n_it_edi_is_self_invoice(self)
|
|||
|
document_type = self.env['account.edi.format']._l10n_it_get_document_type(self)
|
|||
|
if self.env['account.edi.format']._l10n_it_is_simplified_document_type(document_type):
|
|||
|
formato_trasmissione = "FSM10"
|
|||
|
|
|||
|
# Represent if the document is a reverse charge refund in a single variable
|
|||
|
reverse_charge = document_type in ['TD17', 'TD18', 'TD19']
|
|||
|
is_downpayment = document_type in ['TD02']
|
|||
|
reverse_charge_refund = self.move_type == 'in_refund' and reverse_charge
|
|||
|
convert_to_euros = self.currency_id.name != 'EUR'
|
|||
|
|
|||
|
# b64encode returns a bytestring, the template tries to turn it to string,
|
|||
|
# but only gets the repr(pdf) --> "b'<base64_data>'"
|
|||
|
pdf = self.env['ir.actions.report']._render_qweb_pdf("account.account_invoices", self.id)[0]
|
|||
|
pdf = base64.b64encode(pdf).decode()
|
|||
|
pdf_name = re.sub(r'\W+', '', self.name) + '.pdf'
|
|||
|
|
|||
|
tax_details = self._prepare_edi_tax_details(filter_to_apply=self._l10n_it_edi_filter_fatturapa_tax_details)
|
|||
|
|
|||
|
company = self.company_id
|
|||
|
partner = self.commercial_partner_id
|
|||
|
buyer = partner if not is_self_invoice else company
|
|||
|
seller = company if not is_self_invoice else partner
|
|||
|
codice_destinatario = (
|
|||
|
(is_self_invoice and company.partner_id.l10n_it_pa_index)
|
|||
|
# San Marino is externally integrated with the SdI.
|
|||
|
# The country as a whole has a single fixed Destination Code (i.e. "2R4GTO8").
|
|||
|
# https://www.agenziaentrate.gov.it/portale/documents/20143/3788702/Modifiche+ProvvedimentonSanMarino+0248717-2021.pdf/429b5571-17b9-0cce-7f62-f79cf53086d7
|
|||
|
or (partner.country_code == 'SM' and '2R4GTO8')
|
|||
|
or partner.l10n_it_pa_index
|
|||
|
or (partner.country_id.code == 'IT' and '0000000')
|
|||
|
or 'XXXXXXX')
|
|||
|
|
|||
|
# Self-invoices are technically -100%/+100% repartitioned
|
|||
|
# but functionally need to be exported as 100%
|
|||
|
document_total = self.amount_total
|
|||
|
if is_self_invoice:
|
|||
|
document_total += sum([abs(v['tax_amount_currency']) for k, v in tax_details['tax_details'].items()])
|
|||
|
if reverse_charge_refund:
|
|||
|
document_total = -abs(document_total)
|
|||
|
split_payment_amount = self._get_l10n_it_amount_split_payment()
|
|||
|
if split_payment_amount:
|
|||
|
document_total += split_payment_amount
|
|||
|
|
|||
|
# Reference line for finding the conversion rate used in the document
|
|||
|
conversion_line = self.invoice_line_ids.sorted(lambda l: abs(l.balance), reverse=True)[0] if self.invoice_line_ids else None
|
|||
|
conversion_rate = float_repr(
|
|||
|
abs(conversion_line.balance / conversion_line.amount_currency), precision_digits=5,
|
|||
|
) if convert_to_euros and conversion_line else None
|
|||
|
|
|||
|
invoice_lines = self._l10n_it_edi_prepare_fatturapa_line_details(reverse_charge_refund, is_downpayment, convert_to_euros)
|
|||
|
tax_lines = self._l10n_it_edi_prepare_fatturapa_tax_details(tax_details, reverse_charge_refund)
|
|||
|
|
|||
|
# Reduce downpayment views to a single recordset
|
|||
|
downpayment_moves = [l.get('downpayment_moves', self.env['account.move']) for l in invoice_lines]
|
|||
|
downpayment_moves = self.browse(move.id for moves in downpayment_moves for move in moves)
|
|||
|
|
|||
|
# Create file content.
|
|||
|
template_values = {
|
|||
|
'record': self,
|
|||
|
'balance_multiplicator': -1 if self.is_inbound() else 1,
|
|||
|
'company': company,
|
|||
|
'sender': company,
|
|||
|
'sender_partner': company.partner_id,
|
|||
|
'partner': partner,
|
|||
|
'buyer': buyer,
|
|||
|
'buyer_partner': partner if not is_self_invoice else company.partner_id,
|
|||
|
'buyer_is_company': is_self_invoice or partner.is_company,
|
|||
|
'seller': seller,
|
|||
|
'seller_partner': company.partner_id if not is_self_invoice else partner,
|
|||
|
'origin_document_type': False, # see module l10n_it_edi_origin_document, will be merged in master
|
|||
|
'currency': self.currency_id or self.company_currency_id if not convert_to_euros else self.env.ref('base.EUR'),
|
|||
|
'document_total': document_total,
|
|||
|
'representative': company.l10n_it_tax_representative_partner_id,
|
|||
|
'codice_destinatario': codice_destinatario,
|
|||
|
'regime_fiscale': company.l10n_it_tax_system if not is_self_invoice else 'RF18',
|
|||
|
'is_self_invoice': is_self_invoice,
|
|||
|
'partner_bank': self.partner_bank_id,
|
|||
|
'format_date': format_date,
|
|||
|
'format_monetary': format_monetary,
|
|||
|
'format_numbers': format_numbers,
|
|||
|
'format_numbers_two': format_numbers_two,
|
|||
|
'format_phone': format_phone,
|
|||
|
'format_alphanumeric': format_alphanumeric,
|
|||
|
'discount_type': discount_type,
|
|||
|
'formato_trasmissione': formato_trasmissione,
|
|||
|
'document_type': document_type,
|
|||
|
'pdf': pdf,
|
|||
|
'pdf_name': pdf_name,
|
|||
|
'tax_details': tax_details,
|
|||
|
'downpayment_moves': downpayment_moves,
|
|||
|
'abs': abs,
|
|||
|
'normalize_codice_fiscale': partner._l10n_it_normalize_codice_fiscale,
|
|||
|
'get_vat_number': get_vat_number,
|
|||
|
'get_vat_country': get_vat_country,
|
|||
|
'in_eu': in_eu,
|
|||
|
'rc_refund': reverse_charge_refund,
|
|||
|
'invoice_lines': invoice_lines,
|
|||
|
'tax_lines': tax_lines,
|
|||
|
'conversion_rate': conversion_rate,
|
|||
|
'buyer_info': get_vat_values(buyer),
|
|||
|
'seller_info': get_vat_values(seller),
|
|||
|
}
|
|||
|
return template_values
|
|||
|
|
|||
|
def _post(self, soft=True):
|
|||
|
# OVERRIDE
|
|||
|
posted = super()._post(soft=soft)
|
|||
|
return posted
|
|||
|
|
|||
|
def _compose_info_message(self, tree, element_tags):
|
|||
|
output_str = ""
|
|||
|
elements = tree.xpath(element_tags)
|
|||
|
for element in elements:
|
|||
|
output_str += "<ul>"
|
|||
|
for line in element.iter():
|
|||
|
if line.text:
|
|||
|
text = " ".join(line.text.split())
|
|||
|
if text:
|
|||
|
output_str += "<li>%s: %s</li>" % (line.tag, text)
|
|||
|
output_str += "</ul>"
|
|||
|
return output_str
|
|||
|
|
|||
|
def _compose_multi_info_message(self, tree, element_tags):
|
|||
|
output_str = "<ul>"
|
|||
|
|
|||
|
for element_tag in element_tags:
|
|||
|
elements = tree.xpath(element_tag)
|
|||
|
if not elements:
|
|||
|
continue
|
|||
|
for element in elements:
|
|||
|
text = " ".join(element.text.split())
|
|||
|
if text:
|
|||
|
output_str += "<li>%s: %s</li>" % (element.tag, text)
|
|||
|
return output_str + "</ul>"
|
|||
|
|
|||
|
class AccountTax(models.Model):
|
|||
|
_name = "account.tax"
|
|||
|
_inherit = "account.tax"
|
|||
|
|
|||
|
l10n_it_vat_due_date = fields.Selection([
|
|||
|
("I", "[I] IVA ad esigibilità immediata"),
|
|||
|
("D", "[D] IVA ad esigibilità differita"),
|
|||
|
("S", "[S] Scissione dei pagamenti")], default="I", string="VAT due date")
|
|||
|
|
|||
|
l10n_it_has_exoneration = fields.Boolean(string="Has exoneration of tax (Italy)", help="Tax has a tax exoneration.")
|
|||
|
l10n_it_kind_exoneration = fields.Selection(selection=[
|
|||
|
("N1", "[N1] Escluse ex art. 15"),
|
|||
|
("N2", "[N2] Non soggette"),
|
|||
|
("N2.1", "[N2.1] Non soggette ad IVA ai sensi degli artt. Da 7 a 7-septies del DPR 633/72"),
|
|||
|
("N2.2", "[N2.2] Non soggette – altri casi"),
|
|||
|
("N3", "[N3] Non imponibili"),
|
|||
|
("N3.1", "[N3.1] Non imponibili – esportazioni"),
|
|||
|
("N3.2", "[N3.2] Non imponibili – cessioni intracomunitarie"),
|
|||
|
("N3.3", "[N3.3] Non imponibili – cessioni verso San Marino"),
|
|||
|
("N3.4", "[N3.4] Non imponibili – operazioni assimilate alle cessioni all’esportazione"),
|
|||
|
("N3.5", "[N3.5] Non imponibili – a seguito di dichiarazioni d’intento"),
|
|||
|
("N3.6", "[N3.6] Non imponibili – altre operazioni che non concorrono alla formazione del plafond"),
|
|||
|
("N4", "[N4] Esenti"),
|
|||
|
("N5", "[N5] Regime del margine / IVA non esposta in fattura"),
|
|||
|
("N6", "[N6] Inversione contabile (per le operazioni in reverse charge ovvero nei casi di autofatturazione per acquisti extra UE di servizi ovvero per importazioni di beni nei soli casi previsti)"),
|
|||
|
("N6.1", "[N6.1] Inversione contabile – cessione di rottami e altri materiali di recupero"),
|
|||
|
("N6.2", "[N6.2] Inversione contabile – cessione di oro e argento puro"),
|
|||
|
("N6.3", "[N6.3] Inversione contabile – subappalto nel settore edile"),
|
|||
|
("N6.4", "[N6.4] Inversione contabile – cessione di fabbricati"),
|
|||
|
("N6.5", "[N6.5] Inversione contabile – cessione di telefoni cellulari"),
|
|||
|
("N6.6", "[N6.6] Inversione contabile – cessione di prodotti elettronici"),
|
|||
|
("N6.7", "[N6.7] Inversione contabile – prestazioni comparto edile esettori connessi"),
|
|||
|
("N6.8", "[N6.8] Inversione contabile – operazioni settore energetico"),
|
|||
|
("N6.9", "[N6.9] Inversione contabile – altri casi"),
|
|||
|
("N7", "[N7] IVA assolta in altro stato UE (prestazione di servizi di telecomunicazioni, tele-radiodiffusione ed elettronici ex art. 7-octies, comma 1 lett. a, b, art. 74-sexies DPR 633/72)")],
|
|||
|
string="Exoneration",
|
|||
|
help="Exoneration type",
|
|||
|
default="N1")
|
|||
|
l10n_it_law_reference = fields.Char(string="Law Reference", size=100)
|
|||
|
|
|||
|
@api.constrains('l10n_it_has_exoneration',
|
|||
|
'l10n_it_kind_exoneration',
|
|||
|
'l10n_it_law_reference',
|
|||
|
'amount',
|
|||
|
'invoice_repartition_line_ids',
|
|||
|
'refund_repartition_line_ids')
|
|||
|
def _check_exoneration_with_no_tax(self):
|
|||
|
for tax in self:
|
|||
|
if tax.l10n_it_has_exoneration:
|
|||
|
if not tax.l10n_it_kind_exoneration or not tax.l10n_it_law_reference or tax.amount != 0:
|
|||
|
raise ValidationError(_("If the tax has exoneration, you must enter a kind of exoneration, a law reference and the amount of the tax must be 0.0."))
|
|||
|
if tax.l10n_it_kind_exoneration == 'N6' and tax._l10n_it_is_split_payment():
|
|||
|
raise UserError(_("Split Payment is not compatible with exoneration of kind 'N6'"))
|
|||
|
|
|||
|
def _l10n_it_filter_kind(self, kind):
|
|||
|
""" This can be overridden by l10n_it_edi_withholding for different kind of taxes (withholding, pension_fund)."""
|
|||
|
return self if kind == 'vat' else self.env['account.tax']
|
|||
|
|
|||
|
def _l10n_it_is_split_payment(self):
|
|||
|
""" Split payment means that the Public Administration buyer will pay VAT
|
|||
|
to the tax agency instead of the vendor
|
|||
|
"""
|
|||
|
self.ensure_one()
|
|||
|
|
|||
|
tax_tags = self.get_tax_tags(is_refund=False, repartition_type='base') | self.get_tax_tags(is_refund=False, repartition_type='tax')
|
|||
|
if not tax_tags:
|
|||
|
return False
|
|||
|
|
|||
|
it_tax_report_ve38_lines = self.env['account.report.line'].search([
|
|||
|
('report_id.country_id.code', '=', 'IT'),
|
|||
|
('code', '=', 'VE38'),
|
|||
|
])
|
|||
|
if not it_tax_report_ve38_lines:
|
|||
|
return False
|
|||
|
|
|||
|
ve38_lines_tags = it_tax_report_ve38_lines.expression_ids._get_matching_tags()
|
|||
|
return bool(tax_tags & ve38_lines_tags)
|