l10n_it_fatturapa_out/wizard/wizard_export_fatturapa.py
# -*- coding: utf-8 -*-
# Copyright 2014 Davide Corio
# Copyright 2015-2016 Lorenzo Battistini - Agile Business Group
# Copyright 2018 Gianmarco Conte, Marco Calcagni - Dinamiche Aziendali srl
# Copyright 2019 Alex Comba - Agile Business Group
# Copyright 2018-2019 Sergio Corato
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
import base64
import logging
import os
import string
import random
import openerp
from openerp import fields, models, api, _
from openerp.exceptions import Warning as UserError
from openerp.addons.l10n_it_account.tools.account_tools import encode_for_export
from openerp.tools.float_utils import float_round
from openerp.tools.safe_eval import safe_eval
import time
from openerp.addons.l10n_it_fatturapa.bindings.fatturapa import (
FatturaElettronica,
FatturaElettronicaHeaderType,
DatiTrasmissioneType,
IdFiscaleType,
ContattiTrasmittenteType,
CedentePrestatoreType,
AnagraficaType,
IndirizzoType,
IscrizioneREAType,
CessionarioCommittenteType,
RappresentanteFiscaleType,
DatiAnagraficiCedenteType,
DatiAnagraficiCessionarioType,
DatiAnagraficiRappresentanteType,
TerzoIntermediarioSoggettoEmittenteType,
DatiAnagraficiTerzoIntermediarioType,
FatturaElettronicaBodyType,
DatiGeneraliType,
DettaglioLineeType,
DatiBeniServiziType,
DatiRiepilogoType,
DatiGeneraliDocumentoType,
DatiDocumentiCorrelatiType,
ContattiType,
DatiPagamentoType,
DettaglioPagamentoType,
AllegatiType,
ScontoMaggiorazioneType,
CodiceArticoloType,
AltriDatiGestionaliType,
)
from openerp.addons.l10n_it_fatturapa.models.account import (
RELATED_DOCUMENT_TYPES)
_logger = logging.getLogger(__name__)
try:
from pyxb.utils import domutils
from pyxb.binding.datatypes import decimal as pyxb_decimal
from unidecode import unidecode
from pyxb.exceptions_ import SimpleFacetValueError, SimpleTypeValueError
except ImportError as err:
_logger.debug(err)
def id_generator(
size=5, chars=string.ascii_uppercase + string.digits +
string.ascii_lowercase
):
return ''.join(random.choice(chars) for dummy in range(size))
class FatturapaBDS(domutils.BindingDOMSupport):
def valueAsText(self, value, enable_default_namespace=True):
if isinstance(value, pyxb_decimal) and hasattr(value, '_CF_pattern'):
# PyXB changes the text representation of decimals
# so that it breaks pattern matching.
# We have to use directly the string value
# instead of letting PyXB edit it
return str(value)
return super(FatturapaBDS, self) \
.valueAsText(value, enable_default_namespace)
fatturapaBDS = FatturapaBDS()
class WizardExportFatturapa(models.TransientModel):
_name = "wizard.export.fatturapa"
_description = "Export E-invoice"
@api.model
def _domain_ir_values(self):
"""Get all print actions for current model"""
return [('model', '=', self.env.context.get('active_model', False)),
('key2', '=', 'client_print_multi')]
report_print_menu = fields.Many2one(
comodel_name='ir.values',
domain=_domain_ir_values,
help='This report will be automatically included in the created XML')
def saveAttachment(self, fatturapa, number):
attach_obj = self.env['fatturapa.attachment.out']
vat = attach_obj.get_file_vat()
attach_str = fatturapa.toxml(
encoding="UTF-8",
bds=fatturapaBDS,
)
fatturapaBDS.reset()
attach_vals = {
'name': '%s_%s.xml' % (vat, number),
'datas_fname': '%s_%s.xml' % (vat, number),
'datas': base64.encodestring(attach_str),
}
return attach_obj.create(attach_vals)
def setProgressivoInvio(self, fatturapa):
file_id = id_generator()
while self.env['fatturapa.attachment.out'].file_name_exists(file_id):
file_id = id_generator()
try:
fatturapa.FatturaElettronicaHeader.DatiTrasmissione. \
ProgressivoInvio = file_id
except (SimpleFacetValueError, SimpleTypeValueError) as e:
msg = _(
'FatturaElettronicaHeader.DatiTrasmissione.'
'ProgressivoInvio:\n%s'
) % unicode(e)
raise UserError(msg)
return file_id
def _setIdTrasmittente(self, company, fatturapa):
if not company.country_id:
raise UserError(
_('Company %s, Country not set.') % company.display_name)
IdPaese = company.country_id.code
IdCodice = company.partner_id.fiscalcode
if not IdCodice:
if company.vat:
IdCodice = company.vat[2:]
if not IdCodice:
raise UserError(
_('Company %s does not have fiscal code or VAT number.')
% company.display_name)
fatturapa.FatturaElettronicaHeader.DatiTrasmissione. \
IdTrasmittente = IdFiscaleType(
IdPaese=IdPaese, IdCodice=IdCodice)
return True
def _setFormatoTrasmissione(self, partner, fatturapa):
if partner.is_pa:
fatturapa.FatturaElettronicaHeader.DatiTrasmissione. \
FormatoTrasmissione = 'FPA12'
else:
fatturapa.FatturaElettronicaHeader.DatiTrasmissione. \
FormatoTrasmissione = 'FPR12'
return True
def _setCodiceDestinatario(self, partner, fatturapa):
pec_destinatario = None
if partner.is_pa:
if not partner.ipa_code:
raise UserError(_(
"Partner %s is PA but does not have IPA code."
) % partner.name)
code = partner.ipa_code
else:
if not partner.codice_destinatario:
raise UserError(_(
"Partner %s is not PA but does not have Addressee "
"Code."
) % partner.name)
code = partner.codice_destinatario
if code == '0000000':
pec_destinatario = partner.pec_destinatario
fatturapa.FatturaElettronicaHeader.DatiTrasmissione. \
CodiceDestinatario = code.upper()
if pec_destinatario:
fatturapa.FatturaElettronicaHeader.DatiTrasmissione. \
PECDestinatario = pec_destinatario
return True
def _setContattiTrasmittente(self, company, fatturapa):
Telefono = company.phone
Email = company.email
fatturapa.FatturaElettronicaHeader.DatiTrasmissione. \
ContattiTrasmittente = ContattiTrasmittenteType(
Telefono=Telefono or None, Email=Email or None)
return True
def setDatiTrasmissione(self, company, partner, fatturapa):
fatturapa.FatturaElettronicaHeader.DatiTrasmissione = (
DatiTrasmissioneType())
self._setIdTrasmittente(company, fatturapa)
self._setFormatoTrasmissione(partner.commercial_partner_id, fatturapa)
self._setCodiceDestinatario(partner.commercial_partner_id, fatturapa)
self._setContattiTrasmittente(company, fatturapa)
def _setDatiAnagraficiCedente(self, CedentePrestatore, company):
if not company.vat:
raise UserError(
_('TIN not set.'))
CedentePrestatore.DatiAnagrafici = DatiAnagraficiCedenteType()
fatturapa_fp = company.fatturapa_fiscal_position_id
if not fatturapa_fp:
raise UserError(_(
'Fiscal position for electronic invoice not set '
'for company %s. '
'(Go to Accounting / Configuration / Settings / '
'Electronic Invoice)' % company.display_name
))
CedentePrestatore.DatiAnagrafici.IdFiscaleIVA = IdFiscaleType(
IdPaese=company.country_id.code, IdCodice=company.vat[2:])
CedentePrestatore.DatiAnagrafici.Anagrafica = AnagraficaType(
Denominazione=company.name)
if company.partner_id.fiscalcode:
CedentePrestatore.DatiAnagrafici.CodiceFiscale = (
company.partner_id.fiscalcode)
CedentePrestatore.DatiAnagrafici.RegimeFiscale = fatturapa_fp.code
return True
def _setAlboProfessionaleCedente(self, CedentePrestatore, company):
# TODO Albo professionale, for now the main company is considered
# to be a legal entity and not a single person
# 1.2.1.4 <AlboProfessionale>
# 1.2.1.5 <ProvinciaAlbo>
# 1.2.1.6 <NumeroIscrizioneAlbo>
# 1.2.1.7 <DataIscrizioneAlbo>
return True
def _setSedeCedente(self, CedentePrestatore, company):
if not company.street:
raise UserError(
_('Company %s, Street is not set.') % company.display_name)
if not company.zip:
raise UserError(
_('Company %s, ZIP is not set.') % company.display_name)
if not company.city:
raise UserError(
_('Company %s, City is not set.') % company.display_name)
if not company.country_id:
raise UserError(
_('Company %s, Country is not set.') % company.display_name)
# TODO: manage address number in <NumeroCivico>
# see https://github.com/OCA/partner-contact/pull/96
CedentePrestatore.Sede = IndirizzoType(
Indirizzo=encode_for_export(company.street, 60),
CAP=company.zip,
Comune=encode_for_export(company.city, 60),
Nazione=company.country_id.code)
if company.partner_id.state_id:
CedentePrestatore.Sede.Provincia = company.partner_id.state_id.code
return True
def _setStabileOrganizzazione(self, CedentePrestatore, company):
if company.fatturapa_stabile_organizzazione:
stabile_organizzazione = company.fatturapa_stabile_organizzazione
if not stabile_organizzazione.street:
raise UserError(
_('Street is not set for %s.') %
stabile_organizzazione.name)
if not stabile_organizzazione.zip:
raise UserError(
_('ZIP is not set for %s.') %
stabile_organizzazione.name)
if not stabile_organizzazione.city:
raise UserError(
_('City is not set for %s.') %
stabile_organizzazione.name)
if not stabile_organizzazione.country_id:
raise UserError(
_('Country is not set for %s.') %
stabile_organizzazione.name)
CedentePrestatore.StabileOrganizzazione = IndirizzoType(
Indirizzo=stabile_organizzazione.street,
CAP=stabile_organizzazione.zip,
Comune=stabile_organizzazione.city,
Nazione=stabile_organizzazione.country_id.code)
if stabile_organizzazione.state_id:
CedentePrestatore.StabileOrganizzazione.Provincia = (
stabile_organizzazione.state_id.code)
return True
def _setRea(self, CedentePrestatore, company):
if company.fatturapa_rea_office and company.fatturapa_rea_number:
CedentePrestatore.IscrizioneREA = IscrizioneREAType(
Ufficio=(
company.fatturapa_rea_office and
company.fatturapa_rea_office.code or None),
NumeroREA=company.fatturapa_rea_number or None,
CapitaleSociale=(
company.fatturapa_rea_capital and
'%.2f' % float_round(company.fatturapa_rea_capital, 2) or None),
SocioUnico=(company.fatturapa_rea_partner or None),
StatoLiquidazione=company.fatturapa_rea_liquidation or None
)
def _setContatti(self, CedentePrestatore, company):
CedentePrestatore.Contatti = ContattiType(
Telefono=company.partner_id.phone or None,
Fax=company.partner_id.fax or None,
Email=company.partner_id.email or None
)
def _setPubAdministrationRef(self, CedentePrestatore, company):
if company.fatturapa_pub_administration_ref:
CedentePrestatore.RiferimentoAmministrazione = (
company.fatturapa_pub_administration_ref)
def setCedentePrestatore(self, company, fatturapa):
fatturapa.FatturaElettronicaHeader.CedentePrestatore = (
CedentePrestatoreType())
self._setDatiAnagraficiCedente(
fatturapa.FatturaElettronicaHeader.CedentePrestatore,
company)
self._setSedeCedente(
fatturapa.FatturaElettronicaHeader.CedentePrestatore,
company)
self._setAlboProfessionaleCedente(
fatturapa.FatturaElettronicaHeader.CedentePrestatore,
company)
self._setStabileOrganizzazione(
fatturapa.FatturaElettronicaHeader.CedentePrestatore,
company)
# TODO: add Contacts
self._setRea(
fatturapa.FatturaElettronicaHeader.CedentePrestatore,
company)
self._setContatti(
fatturapa.FatturaElettronicaHeader.CedentePrestatore,
company)
self._setPubAdministrationRef(
fatturapa.FatturaElettronicaHeader.CedentePrestatore,
company)
def _setDatiAnagraficiCessionario(self, partner, fatturapa):
fatturapa.FatturaElettronicaHeader.CessionarioCommittente.\
DatiAnagrafici = DatiAnagraficiCessionarioType()
if not partner.vat and not partner.fiscalcode:
if (
partner.codice_destinatario == 'XXXXXXX'
and partner.country_id.code
and partner.country_id.code != 'IT'
):
# SDI accepts missing VAT# for foreign customers by setting a
# fake IdCodice and a valid IdPaese
# Otherwise raise error if we have no VAT# and no Fiscal code
fatturapa.FatturaElettronicaHeader.CessionarioCommittente.\
DatiAnagrafici.IdFiscaleIVA = IdFiscaleType(
IdPaese=partner.country_id.code,
IdCodice='99999999999')
else:
raise UserError(
_('VAT number and fiscal code are not set for %s.') %
partner.name)
if partner.fiscalcode:
fatturapa.FatturaElettronicaHeader.CessionarioCommittente. \
DatiAnagrafici.CodiceFiscale = partner.fiscalcode
if partner.vat:
fatturapa.FatturaElettronicaHeader.CessionarioCommittente. \
DatiAnagrafici.IdFiscaleIVA = IdFiscaleType(
IdPaese=partner.vat[0:2].upper(), IdCodice=partner.vat[2:])
if partner.is_company:
fatturapa.FatturaElettronicaHeader.CessionarioCommittente. \
DatiAnagrafici.Anagrafica = AnagraficaType(
Denominazione=encode_for_export(partner.name, 80))
else:
if not partner.lastname or not partner.firstname:
raise UserError(
_("Partner %s must have name and surname.") %
partner.name)
fatturapa.FatturaElettronicaHeader.CessionarioCommittente. \
DatiAnagrafici.Anagrafica = AnagraficaType(
Cognome=encode_for_export(partner.lastname, 60),
Nome=encode_for_export(partner.firstname, 60))
if partner.eori_code:
fatturapa.FatturaElettronicaHeader.CessionarioCommittente. \
DatiAnagrafici.Anagrafica.CodEORI = partner.eori_code
return True
def _setDatiAnagraficiRappresentanteFiscale(self, partner, fatturapa):
fatturapa.FatturaElettronicaHeader.RappresentanteFiscale = (
RappresentanteFiscaleType())
fatturapa.FatturaElettronicaHeader.RappresentanteFiscale. \
DatiAnagrafici = DatiAnagraficiRappresentanteType()
if not partner.vat and not partner.fiscalcode:
raise UserError(
_('VAT number and fiscal code are not set for %s.') %
partner.name)
if partner.fiscalcode:
fatturapa.FatturaElettronicaHeader.RappresentanteFiscale. \
DatiAnagrafici.CodiceFiscale = partner.fiscalcode
if partner.vat:
fatturapa.FatturaElettronicaHeader.RappresentanteFiscale. \
DatiAnagrafici.IdFiscaleIVA = IdFiscaleType(
IdPaese=partner.vat[0:2].upper(), IdCodice=partner.vat[2:])
fatturapa.FatturaElettronicaHeader.RappresentanteFiscale. \
DatiAnagrafici.Anagrafica = AnagraficaType(
Denominazione=encode_for_export(partner.name, 80))
if partner.eori_code:
fatturapa.FatturaElettronicaHeader.RappresentanteFiscale. \
DatiAnagrafici.Anagrafica.CodEORI = partner.eori_code
return True
def _setTerzoIntermediarioOSoggettoEmittente(self, partner, fatturapa):
fatturapa.FatturaElettronicaHeader. \
TerzoIntermediarioOSoggettoEmittente = \
(TerzoIntermediarioSoggettoEmittenteType())
fatturapa.FatturaElettronicaHeader. \
TerzoIntermediarioOSoggettoEmittente. \
DatiAnagrafici = DatiAnagraficiTerzoIntermediarioType()
if not partner.vat and not partner.fiscalcode:
raise UserError(
_('Partner VAT number and fiscal code are not set.'))
if partner.fiscalcode:
fatturapa.FatturaElettronicaHeader. \
TerzoIntermediarioOSoggettoEmittente. \
DatiAnagrafici.CodiceFiscale = partner.fiscalcode
if partner.vat:
fatturapa.FatturaElettronicaHeader. \
TerzoIntermediarioOSoggettoEmittente. \
DatiAnagrafici.IdFiscaleIVA = IdFiscaleType(
IdPaese=partner.vat[0:2].upper(),
IdCodice=partner.vat[2:])
fatturapa.FatturaElettronicaHeader. \
TerzoIntermediarioOSoggettoEmittente. \
DatiAnagrafici.Anagrafica = \
AnagraficaType(Denominazione=partner.name)
if partner.eori_code:
fatturapa.FatturaElettronicaHeader. \
TerzoIntermediarioOSoggettoEmittente. \
DatiAnagrafici.Anagrafica.CodEORI = partner.eori_code
fatturapa.FatturaElettronicaHeader.SoggettoEmittente = 'TZ'
return True
def _setSedeCessionario(self, partner, fatturapa):
if not partner.street:
raise UserError(
_('Customer street is not set.'))
if not partner.city:
raise UserError(
_('Customer city is not set.'))
if not partner.country_id:
raise UserError(
_('Customer country is not set.'))
# TODO: manage address number in <NumeroCivico>
if partner.codice_destinatario == 'XXXXXXX':
fatturapa.FatturaElettronicaHeader.CessionarioCommittente.Sede = (
IndirizzoType(
Indirizzo=encode_for_export(partner.street, 60),
CAP='00000',
Comune=encode_for_export(partner.city, 60),
Provincia='EE',
Nazione=partner.country_id.code))
else:
if not partner.zip:
raise UserError(
_('Customer ZIP not set for %s.' % partner.name))
fatturapa.FatturaElettronicaHeader.CessionarioCommittente.Sede = (
IndirizzoType(
Indirizzo=encode_for_export(partner.street, 60),
CAP=partner.zip,
Comune=encode_for_export(partner.city, 60),
Nazione=partner.country_id.code))
if partner.state_id:
fatturapa.FatturaElettronicaHeader.CessionarioCommittente.\
Sede.Provincia = partner.state_id.code
return True
def setRappresentanteFiscale(self, company, fatturapa):
if company.fatturapa_tax_representative:
self._setDatiAnagraficiRappresentanteFiscale(
company.fatturapa_tax_representative, fatturapa)
return True
def setCessionarioCommittente(self, partner, fatturapa):
fatturapa.FatturaElettronicaHeader.CessionarioCommittente = (
CessionarioCommittenteType())
self._setDatiAnagraficiCessionario(
partner.commercial_partner_id, fatturapa)
self._setSedeCessionario(partner, fatturapa)
def setTerzoIntermediarioOSoggettoEmittente(self, company, fatturapa):
if company.fatturapa_sender_partner:
self._setTerzoIntermediarioOSoggettoEmittente(
company.fatturapa_sender_partner, fatturapa)
return True
def setDatiGeneraliDocumento(self, invoice, body):
# TODO DatiSAL
body.DatiGenerali = DatiGeneraliType()
if not invoice.number:
raise UserError(
_('Invoice %s does not have a number.' % invoice.display_name))
TipoDocumento = invoice.fiscal_document_type_id.code
if not TipoDocumento:
TipoDocumento = 'TD01'
if invoice.type == 'out_refund':
TipoDocumento = 'TD04'
ImportoTotaleDocumento = invoice.amount_total
if invoice.split_payment:
ImportoTotaleDocumento += invoice.amount_sp
body.DatiGenerali.DatiGeneraliDocumento = DatiGeneraliDocumentoType(
TipoDocumento=TipoDocumento,
Divisa=invoice.currency_id.name,
Data=invoice.date_invoice,
Numero=invoice.number,
ImportoTotaleDocumento='%.2f' % float_round(ImportoTotaleDocumento, 2))
# TODO: DatiRitenuta, DatiBollo, DatiCassaPrevidenziale,
# ScontoMaggiorazione, Arrotondamento,
if invoice.comment:
# max length of Causale is 200
caus_list = invoice.comment.split('\n')
for causale in caus_list:
if not causale:
continue
causale_list_200 = \
[causale[i:i+200] for i in range(0, len(causale), 200)]
for causale200 in causale_list_200:
# Remove non latin chars, but go back to unicode string,
# as expected by String200LatinType
causale = encode_for_export(causale200, 200)
body.DatiGenerali.DatiGeneraliDocumento.Causale\
.append(causale)
if invoice.company_id.fatturapa_art73:
body.DatiGenerali.DatiGeneraliDocumento.Art73 = 'SI'
return True
def setRelatedDocumentTypes(self, invoice, body):
for line in invoice.invoice_line:
for related_document in line.related_documents:
doc_type = RELATED_DOCUMENT_TYPES[related_document.type]
documento = DatiDocumentiCorrelatiType()
if related_document.name:
documento.IdDocumento = related_document.name
if related_document.lineRef:
documento.RiferimentoNumeroLinea.append(
line.ftpa_line_number)
if related_document.date:
documento.Data = related_document.date
if related_document.numitem:
documento.NumItem = related_document.numitem
if related_document.code:
documento.CodiceCommessaConvenzione = related_document.code
if related_document.cup:
documento.CodiceCUP = related_document.cup
if related_document.cig:
documento.CodiceCIG = related_document.cig
getattr(body.DatiGenerali, doc_type).append(documento)
for related_document in invoice.related_documents:
doc_type = RELATED_DOCUMENT_TYPES[related_document.type]
documento = DatiDocumentiCorrelatiType()
if related_document.name:
documento.IdDocumento = related_document.name
if related_document.date:
documento.Data = related_document.date
if related_document.numitem:
documento.NumItem = related_document.numitem
if related_document.code:
documento.CodiceCommessaConvenzione = related_document.code
if related_document.cup:
documento.CodiceCUP = related_document.cup
if related_document.cig:
documento.CodiceCIG = related_document.cig
getattr(body.DatiGenerali, doc_type).append(documento)
return True
def setDatiTrasporto(self, invoice, body):
return True
def setDatiDDT(self, invoice, body):
return True
def _get_prezzo_unitario(self, line):
res = line.price_unit
if (line.invoice_line_tax_id and
line.invoice_line_tax_id[0].price_include):
res = line.price_unit / (
1 + line.invoice_line_tax_id[0].amount)
return res
def setDettaglioLinee(self, invoice, body):
body.DatiBeniServizi = DatiBeniServiziType()
# TipoCessionePrestazione not handled
line_no = 1
price_precision = self.env['decimal.precision'].precision_get(
'Product Price for XML e-invoices')
if price_precision < 2:
# XML wants at least 2 decimals always
price_precision = 2
uom_precision = self.env['decimal.precision'].precision_get(
'Product Unit of Measure')
if uom_precision < 2:
uom_precision = 2
for line in invoice.invoice_line:
self.setDettaglioLinea(
line_no, line, body, price_precision, uom_precision)
line_no += 1
generic_mngt_line = False
generic_mngt_lines = invoice.related_mngt_data_ids.filtered(
lambda x: not x.lineRef)
if generic_mngt_lines:
generic_mngt_line = generic_mngt_lines[0]
for DettaglioLinea in body.DatiBeniServizi.DettaglioLinee:
# get related mgnt_line if exists
mngt_lines = filter(
lambda x: x.lineRef == DettaglioLinea.NumeroLinea,
invoice.related_mngt_data_ids)
if mngt_lines:
for mngt_line in mngt_lines:
dati_gestionali = AltriDatiGestionaliType()
if mngt_line.name:
dati_gestionali.TipoDato = mngt_line.name
if mngt_line.text_ref:
dati_gestionali.RiferimentoTesto = mngt_line.text_ref
if mngt_line.number_ref:
dati_gestionali.RiferimentoNumero = '%.2f' % \
mngt_line.number_ref
if mngt_line.date_ref:
dati_gestionali.RiferimentoData = mngt_line.date_ref
DettaglioLinea.AltriDatiGestionali.append(
dati_gestionali
)
else:
if generic_mngt_line:
dati_gestionali = AltriDatiGestionaliType()
# if fatturapa line is not referred, and exist a
# generic_mngt_line, add this generic mngt data to line
if generic_mngt_line.name:
dati_gestionali.TipoDato = generic_mngt_line.name
if generic_mngt_line.text_ref:
dati_gestionali.RiferimentoTesto = generic_mngt_line.\
text_ref
if generic_mngt_line.number_ref:
dati_gestionali.RiferimentoNumero = '%.2f' %\
generic_mngt_line.number_ref
if generic_mngt_line.date_ref:
dati_gestionali.RiferimentoData = generic_mngt_line.\
date_ref
DettaglioLinea.AltriDatiGestionali.append(
dati_gestionali
)
return True
def setDettaglioLinea(
self, line_no, line, body, price_precision, uom_precision):
if not line.invoice_line_tax_id:
raise UserError(
_("Invoice line %s does not have tax.") % line.name)
if len(line.invoice_line_tax_id) > 1:
raise UserError(
_("Too many taxes for invoice line %s.") % line.name)
aliquota = line.invoice_line_tax_id[0].amount
AliquotaIVA = '%.2f' % float_round(aliquota * 100, 2)
line.ftpa_line_number = line_no
prezzo_unitario = self._get_prezzo_unitario(line)
DettaglioLinea = DettaglioLineeType(
NumeroLinea=str(line_no),
# can't insert newline with pyxb
# see https://tinyurl.com/ycem923t
# and ' ' would not be correctly visualized anyway
# (for example firefox replaces ' ' with space
Descrizione=encode_for_export(line.name.replace('\n', ' '), 1000),
PrezzoUnitario=('%.' + str(price_precision) + 'f') % float_round(
prezzo_unitario, price_precision),
Quantita=('%.' + str(uom_precision) + 'f') % line.quantity,
UnitaMisura=line.uos_id and (unidecode(line.uos_id.name)) or None,
PrezzoTotale='%.2f' % float_round(line.price_subtotal, 2),
AliquotaIVA=AliquotaIVA)
DettaglioLinea.ScontoMaggiorazione.extend(
self.setScontoMaggiorazione(line))
if aliquota == 0.0:
if not line.invoice_line_tax_id[0].kind_id:
raise UserError(
_("No 'nature' field for tax %s.") %
line.invoice_line_tax_id[0].name)
DettaglioLinea.Natura = line.invoice_line_tax_id[
0
].kind_id.code
if line.admin_ref:
DettaglioLinea.RiferimentoAmministrazione = line.admin_ref
if line.product_id:
product_code = line.product_id.default_code
if product_code:
CodiceArticolo = CodiceArticoloType(
CodiceTipo=self.env['ir.config_parameter'].sudo(
).get_param('fatturapa.codicetipo.odoo', 'ODOO'),
CodiceValore=product_code[:35],
)
DettaglioLinea.CodiceArticolo.append(CodiceArticolo)
product_barcode = line.product_id.ean13
if product_barcode:
CodiceArticolo = CodiceArticoloType(
CodiceTipo='EAN',
CodiceValore=product_barcode[:35],
)
DettaglioLinea.CodiceArticolo.append(CodiceArticolo)
body.DatiBeniServizi.DettaglioLinee.append(DettaglioLinea)
return DettaglioLinea
def setScontoMaggiorazione(self, line):
res = []
if line.discount:
res.append(ScontoMaggiorazioneType(
Tipo='SC',
Percentuale='%.2f' % float_round(line.discount, 2)
))
return res
def setDatiRiepilogo(self, invoice, body):
model_tax = self.env['account.tax']
if not invoice.tax_line:
raise UserError(
_("Invoice {invoice} has no tax lines")
.format(invoice=invoice.display_name))
for tax_line in invoice.tax_line:
tax = model_tax.get_tax_by_invoice_tax(tax_line.name)
riepilogo = DatiRiepilogoType(
AliquotaIVA='%.2f' % float_round(tax.amount * 100, 2),
ImponibileImporto='%.2f' % float_round(tax_line.base, 2),
Imposta='%.2f' % float_round(tax_line.amount, 2)
)
if tax.amount == 0.0:
if not tax.kind_id:
raise UserError(
_("No 'nature' field for tax %s.") % tax.name)
riepilogo.Natura = tax.kind_id.code
if not tax.law_reference:
raise UserError(
_("No 'law reference' field for tax %s.") % tax.name)
riepilogo.RiferimentoNormativo = encode_for_export(
tax.law_reference, 100)
if tax.payability:
riepilogo.EsigibilitaIVA = tax.payability
# TODO
# el.remove(el.find('SpeseAccessorie'))
# el.remove(el.find('Arrotondamento'))
body.DatiBeniServizi.DatiRiepilogo.append(riepilogo)
return True
def setDatiPagamento(self, invoice, body):
if invoice.payment_term:
payment_line_ids = invoice.move_line_id_payment_get()
if not payment_line_ids:
return True
DatiPagamento = DatiPagamentoType()
if not invoice.payment_term.fatturapa_pt_id:
raise UserError(
_('Payment term %s does not have a linked e-invoice '
'payment term') % invoice.payment_term.name)
if not invoice.payment_term.fatturapa_pm_id:
raise UserError(
_('Payment term %s does not have a linked e-invoice '
'payment method') % invoice.payment_term.name)
DatiPagamento.CondizioniPagamento = (
invoice.payment_term.fatturapa_pt_id.code)
move_line_pool = self.env['account.move.line']
for move_line_id in payment_line_ids:
move_line = move_line_pool.browse(move_line_id)
ImportoPagamento = '%.2f' % float_round(
move_line.amount_currency or move_line.debit, 2)
DettaglioPagamento = DettaglioPagamentoType(
ModalitaPagamento=(
invoice.payment_term.fatturapa_pm_id.code),
DataScadenzaPagamento=move_line.date_maturity,
ImportoPagamento=ImportoPagamento
)
if invoice.partner_bank_id:
DettaglioPagamento.IstitutoFinanziario = (
invoice.partner_bank_id.bank_name)
if invoice.partner_bank_id.acc_number:
DettaglioPagamento.IBAN = (
''.join(invoice.partner_bank_id.acc_number.split())
)
if invoice.partner_bank_id.bank_bic:
DettaglioPagamento.BIC = (
invoice.partner_bank_id.bank_bic)
DatiPagamento.DettaglioPagamento.append(DettaglioPagamento)
body.DatiPagamento.append(DatiPagamento)
return True
def setAttachments(self, invoice, body):
if invoice.fatturapa_doc_attachments:
for doc_id in invoice.fatturapa_doc_attachments:
file_name, file_extension = os.path.splitext(doc_id.name)
attachment_name = doc_id.datas_fname if len(
doc_id.datas_fname) <= 60 else ''.join([
file_name[:(60-len(file_extension))], file_extension])
AttachDoc = AllegatiType(
NomeAttachment=encode_for_export(attachment_name, 60),
Attachment=base64.decodestring(doc_id.datas)
)
body.Allegati.append(AttachDoc)
return True
def setFatturaElettronicaHeader(self, company, partner, fatturapa):
fatturapa.FatturaElettronicaHeader = (
FatturaElettronicaHeaderType())
self.setDatiTrasmissione(company, partner, fatturapa)
self.setCedentePrestatore(company, fatturapa)
self.setRappresentanteFiscale(company, fatturapa)
self.setCessionarioCommittente(partner, fatturapa)
self.setTerzoIntermediarioOSoggettoEmittente(company, fatturapa)
def setFatturaElettronicaBody(self, inv, FatturaElettronicaBody):
self.setDatiGeneraliDocumento(inv, FatturaElettronicaBody)
self.setDettaglioLinee(inv, FatturaElettronicaBody)
self.setDatiDDT(inv, FatturaElettronicaBody)
self.setDatiTrasporto(inv, FatturaElettronicaBody)
self.setRelatedDocumentTypes(inv, FatturaElettronicaBody)
self.setDatiRiepilogo(inv, FatturaElettronicaBody)
self.setDatiPagamento(inv, FatturaElettronicaBody)
self.setAttachments(inv, FatturaElettronicaBody)
def getPartnerId(self, invoice_ids):
invoice_model = self.env['account.invoice']
partner = False
invoices = invoice_model.browse(invoice_ids)
for invoice in invoices:
if not partner:
partner = invoice.partner_id
if invoice.partner_id != partner:
raise UserError(
_('Invoices %s must belong to the same partner') %
invoices.mapped('number'))
return partner
def group_invoices_by_partner(self):
invoice_ids = self.env.context.get('active_ids', False)
res = {}
for invoice in self.env['account.invoice'].browse(invoice_ids):
if invoice.partner_id.id not in res:
res[invoice.partner_id.id] = []
res[invoice.partner_id.id].append(invoice.id)
return res
@api.multi
def exportFatturaPA(self):
invoice_obj = self.env['account.invoice']
invoices_by_partner = self.group_invoices_by_partner()
attachments = self.env['fatturapa.attachment.out']
for partner_id in invoices_by_partner:
invoice_ids = invoices_by_partner[partner_id]
partner = self.getPartnerId(invoice_ids)
if partner.is_pa:
fatturapa = FatturaElettronica(versione='FPA12')
else:
fatturapa = FatturaElettronica(versione='FPR12')
company = self.env.user.company_id
context_partner = self.env.context.copy()
context_partner.update({'lang': partner.lang})
try:
self.with_context(context_partner).setFatturaElettronicaHeader(
company, partner, fatturapa)
for invoice_id in invoice_ids:
inv = invoice_obj.with_context(context_partner).browse(
invoice_id)
if inv.fatturapa_attachment_out_id:
raise UserError(
_("Invoice %s has e-invoice export file yet.") % (
inv.number))
if self.report_print_menu:
self.generate_attach_report(inv)
invoice_body = FatturaElettronicaBodyType()
inv.preventive_checks()
self.with_context(
context_partner
).setFatturaElettronicaBody(
inv, invoice_body)
fatturapa.FatturaElettronicaBody.append(invoice_body)
# TODO DatiVeicoli
number = self.setProgressivoInvio(fatturapa)
except (SimpleFacetValueError, SimpleTypeValueError) as e:
raise UserError(unicode(e))
attach = self.saveAttachment(fatturapa, number)
attachments |= attach
for invoice_id in invoice_ids:
inv = invoice_obj.browse(invoice_id)
inv.write({'fatturapa_attachment_out_id': attach.id})
action = {
'view_type': 'form',
'name': "Export Electronic Invoice",
'res_model': 'fatturapa.attachment.out',
'type': 'ir.actions.act_window',
}
if len(attachments) == 1:
action['view_mode'] = 'form'
action['res_id'] = attachments[0].id
else:
action['view_mode'] = 'tree,form'
action['domain'] = [('id', 'in', attachments.ids)]
return action
@api.v8
def generate_attach_report(self, inv):
action_report_model, action_report_id = (
self.report_print_menu.value.split(',')[0],
int(self.report_print_menu.value.split(',')[1]))
action_report = self.env[action_report_model] \
.browse(action_report_id)
report_model = self.env['report']
attachment_model = self.env['ir.attachment']
# Generate the PDF: if report_action.attachment is set
# they will be automatically attached to the invoice,
# otherwise use res to build a new attachment
(res, report_format) = openerp.report.render_report(
self._cr, self._uid, [inv.id],
action_report.report_name, {'model': inv._name},
self._context)
if action_report.attachment:
# If the report is configured to be attached
# to the current invoice, just get that from the attachments.
# Note that in this case the attachment in
# fatturapa_doc_attachments is exactly the same
# that is attached to the invoice.
attachment = report_model._attachment_stored(
inv, action_report)[inv.id]
else:
# Otherwise, create a new attachment to be stored in
# fatturapa_doc_attachments.
filename = inv.number
data_attach = {
'name': filename,
'datas': base64.b64encode(res),
'datas_fname': filename,
'type': 'binary'
}
attachment = attachment_model.create(data_attach)
inv.write({
'fatturapa_doc_attachments': [(0, 0, {
'is_pdf_invoice_print': True,
'ir_attachment_id': attachment.id,
'description': _("Attachment generated by "
"electronic invoice export")})]
})
class Report(models.Model):
_inherit = "report"
@api.model
def _attachment_filename(self, records, report):
return dict((record.id, safe_eval(report.attachment,
{'object': record,
'time': time})) for
record in records)
@api.model
def _attachment_stored(self, records, report, filenames=None):
if not filenames:
filenames = self._attachment_filename(records, report)
return dict((record.id, self.env['ir.attachment'].search([
('datas_fname', '=', filenames[record.id]),
('res_model', '=', report.model),
('res_id', '=', record.id)
], limit=1)) for record in records)