diff --git a/.travis.yml b/.travis.yml index 66a9cc8..7377797 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,8 @@ virtual_env: install: - pip install --upgrade pip - pip install -r requirements.txt -script: coverage run --source=pytrustnfe setup.py nosetests +script: + pytest --cov=pytrustnfe before_install: - sudo apt-get update -qq - sudo apt-get install -qq python-dev libffi-dev libxml2-dev libxslt1-dev libssl-dev diff --git a/pytrustnfe/Servidores.py b/pytrustnfe/Servidores.py index e942df9..239053e 100644 --- a/pytrustnfe/Servidores.py +++ b/pytrustnfe/Servidores.py @@ -37,8 +37,8 @@ NFE_AMBIENTE_HOMOLOGACAO = 2 NFCE_AMBIENTE_PRODUCAO = 1 NFCE_AMBIENTE_HOMOLOGACAO = 2 -NFE_MODELO = u'55' -NFCE_MODELO = u'65' +NFE_MODELO = '55' +NFCE_MODELO = '65' SIGLA_ESTADO = { '12': 'AC', diff --git a/pytrustnfe/__init__.py b/pytrustnfe/__init__.py index 252e121..093c5c3 100644 --- a/pytrustnfe/__init__.py +++ b/pytrustnfe/__init__.py @@ -12,10 +12,10 @@ class HttpClient(object): def _headers(self, action): return { - u'Content-type': - u'text/xml; charset=utf-8;', - u'Accept': u'application/soap+xml; charset=utf-8', - u'SOAPAction': action + 'Content-type': + 'text/xml; charset=utf-8;', + 'Accept': 'application/soap+xml; charset=utf-8', + 'SOAPAction': action } def post_soap(self, xml_soap, action): diff --git a/pytrustnfe/certificado.py b/pytrustnfe/certificado.py index 4e44ae9..b96df91 100644 --- a/pytrustnfe/certificado.py +++ b/pytrustnfe/certificado.py @@ -14,7 +14,7 @@ class Certificado(object): def save_pfx(self): pfx_temp = '/tmp/' + uuid4().hex - arq_temp = open(pfx_temp, 'w') + arq_temp = open(pfx_temp, 'wb') arq_temp.write(self.pfx) arq_temp.close() return pfx_temp @@ -28,7 +28,7 @@ def extract_cert_and_key_from_pfx(pfx, password): # PEM formatted certificate cert = crypto.dump_certificate(crypto.FILETYPE_PEM, pfx.get_certificate()) - return cert, key + return cert.decode(), key.decode() def save_cert_key(cert, key): diff --git a/pytrustnfe/client.py b/pytrustnfe/client.py index bf76c7b..30047d1 100644 --- a/pytrustnfe/client.py +++ b/pytrustnfe/client.py @@ -44,8 +44,8 @@ class HttpClient(object): def _headers(self, action): return { - u'Content-type': u'application/soap+xml; charset=utf-8; action="http://www.portalfiscal.inf.br/nfe/wsdl/%s"' % action, - u'Accept': u'application/soap+xml; charset=utf-8', + 'Content-type': 'application/soap+xml; charset=utf-8; action="http://www.portalfiscal.inf.br/nfe/wsdl/%s"' % action, + 'Accept': 'application/soap+xml; charset=utf-8', } def post_soap(self, xml_soap, cabecalho): diff --git a/pytrustnfe/nfe/__init__.py b/pytrustnfe/nfe/__init__.py index f00ee9f..b2daec2 100644 --- a/pytrustnfe/nfe/__init__.py +++ b/pytrustnfe/nfe/__init__.py @@ -5,6 +5,7 @@ import os import hashlib +import binascii from lxml import etree from .comunicacao import executar_consulta from .assinatura import Assinatura @@ -80,7 +81,7 @@ def _add_qrCode(xml, **kwargs): infnfesupl = etree.Element('infNFeSupl') qrcode = etree.Element('qrCode') chave_nfe = inf_nfe['Id'][3:] - dh_emissao = inf_nfe['ide']['dhEmi'].encode('hex') + dh_emissao = binascii.hexlify(inf_nfe['ide']['dhEmi'].encode()).decode() versao = '100' ambiente = kwargs['ambiente'] valor_total = inf_nfe['total']['vNF'] @@ -98,9 +99,8 @@ def _add_qrCode(xml, **kwargs): dest.append(cpf) dest_parent.append(dest) icms_total = inf_nfe['total']['vICMS'] - dig_val = xml.find( - ".//{http://www.w3.org/2000/09/xmldsig#}DigestValue")\ - .text.encode('hex') + dig_val = binascii.hexlify(xml.find( + ".//{http://www.w3.org/2000/09/xmldsig#}DigestValue").text.encode()).decode() cid_token = kwargs['NFes'][0]['infNFe']['codigo_seguranca']['cid_token'] csc = kwargs['NFes'][0]['infNFe']['codigo_seguranca']['csc'] @@ -108,7 +108,7 @@ def _add_qrCode(xml, **kwargs): ={5}&vICMS={6}&digVal={7}&cIdToken={8}{9}".\ format(chave_nfe, versao, ambiente, dest_cpf, dh_emissao, valor_total, icms_total, dig_val, cid_token, csc) - c_hash_QR_code = hashlib.sha1(c_hash_QR_code).hexdigest() + c_hash_QR_code = hashlib.sha1(c_hash_QR_code.encode()).hexdigest() QR_code_url = "?chNFe={0}&nVersao={1}&tpAmb={2}&{3}dhEmi={4}&vNF={5}&vICMS\ ={6}&digVal={7}&cIdToken={8}&cHashQRCode={9}".\ @@ -121,7 +121,7 @@ def _add_qrCode(xml, **kwargs): qrcode.text = etree.CDATA(qrcode_text) infnfesupl.append(qrcode) nfe.insert(1, infnfesupl) - return etree.tostring(xml) + return etree.tostring(xml, encoding=str) def _send(certificado, method, sign, **kwargs): @@ -175,7 +175,7 @@ def _send(certificado, method, sign, **kwargs): xml_send = _add_qrCode(xml_send, **kwargs) else: - xml_send = etree.tostring(xmlElem_send) + xml_send = etree.tostring(xmlElem_send, encoding=str) url = localizar_url(method, kwargs['estado'], modelo, kwargs['ambiente']) diff --git a/pytrustnfe/nfe/assinatura.py b/pytrustnfe/nfe/assinatura.py index e14ec5d..34df54a 100644 --- a/pytrustnfe/nfe/assinatura.py +++ b/pytrustnfe/nfe/assinatura.py @@ -32,7 +32,7 @@ class Assinatura(object): ref_uri = ('#%s' % reference) if reference else None signed_root = signer.sign( - xml_element, key=key, cert=cert, + xml_element, key=key.encode(), cert=cert.encode(), reference_uri=ref_uri) if reference: element_signed = signed_root.find(".//*[@Id='%s']" % reference) @@ -42,4 +42,4 @@ class Assinatura(object): if element_signed is not None and signature is not None: parent = element_signed.getparent() parent.append(signature) - return etree.tostring(signed_root) + return etree.tostring(signed_root, encoding=str) diff --git a/pytrustnfe/nfe/comunicacao.py b/pytrustnfe/nfe/comunicacao.py index 2e3f4d9..df15ddd 100644 --- a/pytrustnfe/nfe/comunicacao.py +++ b/pytrustnfe/nfe/comunicacao.py @@ -10,6 +10,7 @@ from ..xml import sanitize_response def _soap_xml(body, cabecalho): + print(type(body)) xml = '' xml += '' xml += '' diff --git a/pytrustnfe/nfe/danfe.py b/pytrustnfe/nfe/danfe.py index 799150c..d0fedcd 100644 --- a/pytrustnfe/nfe/danfe.py +++ b/pytrustnfe/nfe/danfe.py @@ -1,829 +1,829 @@ -# -*- coding: utf-8 -*- -# © 2017 Edson Bernardino, ITK Soft -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -# Classe para geração de PDF da DANFE a partir de xml etree.fromstring - - -from cStringIO import StringIO as IO -from textwrap import wrap - -from reportlab.lib import utils -from reportlab.pdfgen import canvas -from reportlab.lib.units import mm, cm -from reportlab.lib.pagesizes import A4 -from reportlab.lib.colors import black, gray -from reportlab.graphics.barcode import code128 -from reportlab.lib.styles import getSampleStyleSheet -from reportlab.lib.enums import TA_CENTER -from reportlab.platypus import Paragraph, Image - - -def chunks(cString, nLen): - for start in range(0, len(cString), nLen): - yield cString[start:start+nLen] - - -def format_cnpj_cpf(value): - if len(value) < 12: # CPF - cValue = '%s.%s.%s-%s' % (value[:-8], value[-8:-5], - value[-5:-2], value[-2:]) - else: - cValue = '%s.%s.%s/%s-%s' % (value[:-12], value[-12:-9], - value[-9:-6], value[-6:-2], value[-2:]) - return cValue - - -def getdateUTC(cDateUTC): - cDt = cDateUTC[0:10].split('-') - cDt.reverse() - return '/'.join(cDt), cDateUTC[11:16] - - -def format_number(cNumber, precision=0, group_sep='.', decimal_sep=','): - if cNumber: - number = float(cNumber) - return ("{:,." + str(precision) + "f}").format(number).\ - replace(",", "X").replace(".", ",").replace("X", ".") - return "" - - -def tagtext(oNode=None, cTag=None): - try: - xpath = ".//{http://www.portalfiscal.inf.br/nfe}%s" % (cTag) - cText = oNode.find(xpath).text - except: - cText = '' - return cText - -REGIME_TRIBUTACAO = { - '1': 'Simples Nacional', - '2': 'Simples Nacional, excesso sublimite de receita bruta', - '3': 'Regime Normal' -} - - -def get_image(path, width=1*cm): - img = utils.ImageReader(path) - iw, ih = img.getSize() - aspect = ih / float(iw) - return Image(path, width=width, height=(width * aspect)) - - -class danfe(object): - def __init__(self, sizepage=A4, list_xml=None, recibo=True, - orientation='portrait', logo=None): - self.width = 210 # 21 x 29,7cm - self.height = 297 - self.nLeft = 10 - self.nRight = 10 - self.nTop = 7 - self.nBottom = 8 - self.nlin = self.nTop - self.logo = logo - self.oFrete = {'0': '0 - Emitente', - '1': '1 - Dest/Remet', - '2': '2 - Terceiros', - '9': '9 - Sem Frete'} - - self.oPDF_IO = IO() - if orientation == 'landscape': - raise NameError('Rotina não implementada') - else: - size = sizepage - - self.canvas = canvas.Canvas(self.oPDF_IO, pagesize=size) - self.canvas.setTitle('DANFE') - self.canvas.setStrokeColor(black) - - for oXML in list_xml: - oXML_cobr = oXML.find( - ".//{http://www.portalfiscal.inf.br/nfe}cobr") - - self.NrPages = 1 - self.Page = 1 - - # Calculando total linhas usadas para descrições dos itens - # Com bloco fatura, apenas 29 linhas para itens na primeira folha - nNr_Lin_Pg_1 = 34 if oXML_cobr is None else 30 - # [ rec_ini , rec_fim , lines , limit_lines ] - oPaginator = [[0, 0, 0, nNr_Lin_Pg_1]] - el_det = oXML.findall(".//{http://www.portalfiscal.inf.br/nfe}det") - if el_det is not None: - list_desc = [] - list_cod_prod = [] - nPg = 0 - for nId, item in enumerate(el_det): - el_prod = item.find( - ".//{http://www.portalfiscal.inf.br/nfe}prod") - infAdProd = item.find( - ".//{http://www.portalfiscal.inf.br/nfe}infAdProd") - - list_ = wrap(tagtext(oNode=el_prod, cTag='xProd'), 56) - if infAdProd is not None: - list_.extend(wrap(infAdProd.text, 56)) - list_desc.append(list_) - - list_cProd = wrap(tagtext(oNode=el_prod, cTag='cProd'), 14) - list_cod_prod.append(list_cProd) - - # Nr linhas necessárias p/ descrição item - nLin_Itens = len(list_) - - if (oPaginator[nPg][2] + nLin_Itens) >= oPaginator[nPg][3]: - oPaginator.append([0, 0, 0, 77]) - nPg += 1 - oPaginator[nPg][0] = nId - oPaginator[nPg][1] = nId + 1 - oPaginator[nPg][2] = nLin_Itens - else: - # adiciona-se 1 pelo funcionamento de xrange - oPaginator[nPg][1] = nId + 1 - oPaginator[nPg][2] += nLin_Itens - - self.NrPages = len(oPaginator) # Calculando nr. páginas - - if recibo: - self.recibo_entrega(oXML=oXML) - - self.ide_emit(oXML=oXML) - self.destinatario(oXML=oXML) - - if oXML_cobr is not None: - self.faturas(oXML=oXML_cobr) - - self.impostos(oXML=oXML) - self.transportes(oXML=oXML) - self.produtos(oXML=oXML, el_det=el_det, oPaginator=oPaginator[0], - list_desc=list_desc, list_cod_prod=list_cod_prod) - - self.adicionais(oXML=oXML) - - # Gera o restante das páginas do XML - for oPag in oPaginator[1:]: - self.newpage() - self.ide_emit(oXML=oXML) - self.produtos(oXML=oXML, el_det=el_det, oPaginator=oPag, - list_desc=list_desc, nHeight=77, - list_cod_prod=list_cod_prod) - - self.newpage() - - self.canvas.save() - - def ide_emit(self, oXML=None): - elem_infNFe = oXML.find( - ".//{http://www.portalfiscal.inf.br/nfe}infNFe") - elem_protNFe = oXML.find( - ".//{http://www.portalfiscal.inf.br/nfe}protNFe") - elem_emit = oXML.find(".//{http://www.portalfiscal.inf.br/nfe}emit") - elem_ide = oXML.find(".//{http://www.portalfiscal.inf.br/nfe}ide") - - cChave = elem_infNFe.attrib.get('Id')[3:] - barcode128 = code128.Code128(cChave, barHeight=10*mm, barWidth=0.25*mm) - - self.canvas.setLineWidth(.5) - self.rect(self.nLeft, self.nlin+1, self.nLeft+75, 32) - self.rect(self.nLeft+115, self.nlin+1, - self.width-self.nLeft-self.nRight-115, 39) - - self.hline(self.nLeft+85, self.nlin+1, 125) - - self.rect(self.nLeft+116, self.nlin+15, - self.width-self.nLeft-self.nRight-117, 6) - - self.rect(self.nLeft, self.nlin+33, - self.width-self.nLeft-self.nRight, 14) - self.hline(self.nLeft, self.nlin+40, self.width-self.nRight) - self.vline(self.nLeft+60, self.nlin+40, 7) - self.vline(self.nLeft+100, self.nlin+40, 7) - - # Labels - self.canvas.setFont('NimbusSanL-Bold', 12) - self.stringcenter(self.nLeft+98, self.nlin+5, 'DANFE') - self.stringcenter(self.nLeft+109, self.nlin+19.5, - tagtext(oNode=elem_ide, cTag='tpNF')) - self.canvas.setFont('NimbusSanL-Bold', 8) - cNF = tagtext(oNode=elem_ide, cTag='nNF') - cNF = '{0:011,}'.format(int(cNF)).replace(",", ".") - self.stringcenter(self.nLeft+100, self.nlin+25, "Nº %s" % (cNF)) - - self.stringcenter(self.nLeft+100, self.nlin+29, u"SÉRIE %s" % ( - tagtext(oNode=elem_ide, cTag='serie'))) - cPag = "Página %s de %s" % (str(self.Page), str(self.NrPages)) - self.stringcenter(self.nLeft+100, self.nlin+32, cPag) - self.canvas.setFont('NimbusSanL-Regu', 6) - self.string(self.nLeft+86, self.nlin+8, 'Documento Auxiliar da') - self.string(self.nLeft+86, self.nlin+10.5, 'Nota Fiscal Eletrônica') - self.string(self.nLeft+86, self.nlin+16, '0 - Entrada') - self.string(self.nLeft+86, self.nlin+19, '1 - Saída') - self.rect(self.nLeft+105, self.nlin+15, 8, 6) - - self.stringcenter( - self.nLeft+152, self.nlin+25, - 'Consulta de autenticidade no portal nacional da NF-e') - self.stringcenter( - self.nLeft+152, self.nlin+28, - 'www.nfe.fazenda.gov.br/portal ou no site da SEFAZ Autorizadora') - self.canvas.setFont('NimbusSanL-Regu', 5) - self.string(self.nLeft+117, self.nlin+16.7, 'CHAVE DE ACESSO') - self.string(self.nLeft+116, self.nlin+2.7, 'CONTROLE DO FISCO') - - self.string(self.nLeft+1, self.nlin+34.7, 'NATUREZA DA OPERAÇÃO') - self.string(self.nLeft+116, self.nlin+34.7, - 'PROTOCOLO DE AUTORIZAÇÃO DE USO') - self.string(self.nLeft+1, self.nlin+41.7, 'INSCRIÇÃO ESTADUAL') - self.string(self.nLeft+61, self.nlin+41.7, - 'INSCRIÇÃO ESTADUAL DO SUBST. TRIB.') - self.string(self.nLeft+101, self.nlin+41.7, 'CNPJ') - - # Conteúdo campos - barcode128.drawOn(self.canvas, (self.nLeft+111.5)*mm, - (self.height-self.nlin-14)*mm) - self.canvas.setFont('NimbusSanL-Bold', 6) - nW_Rect = (self.width-self.nLeft-self.nRight-117) / 2 - self.stringcenter(self.nLeft+116.5+nW_Rect, self.nlin+19.5, - ' '.join(chunks(cChave, 4))) # Chave - self.canvas.setFont('NimbusSanL-Regu', 8) - cDt, cHr = getdateUTC(tagtext(oNode=elem_protNFe, cTag='dhRecbto')) - cProtocolo = tagtext(oNode=elem_protNFe, cTag='nProt') - cDt = cProtocolo + ' - ' + cDt + ' ' + cHr - nW_Rect = (self.width-self.nLeft-self.nRight-110) / 2 - self.stringcenter(self.nLeft+115+nW_Rect, self.nlin+38.7, cDt) - self.canvas.setFont('NimbusSanL-Regu', 8) - self.string(self.nLeft+1, self.nlin+38.7, - tagtext(oNode=elem_ide, cTag='natOp')) - self.string(self.nLeft+1, self.nlin+46, - tagtext(oNode=elem_emit, cTag='IE')) - self.string(self.nLeft+101, self.nlin+46, - format_cnpj_cpf(tagtext(oNode=elem_emit, cTag='CNPJ'))) - - styles = getSampleStyleSheet() - styleN = styles['Normal'] - styleN.fontSize = 10 - styleN.fontName = 'NimbusSanL-Bold' - styleN.alignment = TA_CENTER - - # Razão Social emitente - P = Paragraph(tagtext(oNode=elem_emit, cTag='xNome'), styleN) - w, h = P.wrap(55*mm, 50*mm) - P.drawOn(self.canvas, (self.nLeft+30)*mm, - (self.height-self.nlin-12)*mm) - - if self.logo: - img = get_image(self.logo, width=2*cm) - img.drawOn(self.canvas, (self.nLeft+5)*mm, - (self.height-self.nlin-22)*mm) - - cEnd = tagtext(oNode=elem_emit, cTag='xLgr') + ', ' + tagtext( - oNode=elem_emit, cTag='nro') + ' - ' - cEnd += tagtext(oNode=elem_emit, cTag='xBairro') + '
' + tagtext( - oNode=elem_emit, cTag='xMun') + ' - ' - cEnd += 'Fone: ' + tagtext(oNode=elem_emit, cTag='fone') + '
' - cEnd += tagtext(oNode=elem_emit, cTag='UF') + ' - ' + tagtext( - oNode=elem_emit, cTag='CEP') - - regime = tagtext(oNode=elem_emit, cTag='CRT') - cEnd += u'
Regime Tributário: %s' % (REGIME_TRIBUTACAO[regime]) - - styleN.fontName = 'NimbusSanL-Regu' - styleN.fontSize = 7 - styleN.leading = 10 - P = Paragraph(cEnd, styleN) - w, h = P.wrap(55*mm, 30*mm) - P.drawOn(self.canvas, (self.nLeft+30)*mm, - (self.height-self.nlin-31)*mm) - - # Homologação - if tagtext(oNode=elem_ide, cTag='tpAmb') == '2': - self.canvas.saveState() - self.canvas.rotate(90) - self.canvas.setFont('Times-Bold', 40) - self.canvas.setFillColorRGB(0.57, 0.57, 0.57) - self.string(self.nLeft+65, 449, 'SEM VALOR FISCAL') - self.canvas.restoreState() - - self.nlin += 48 - - def destinatario(self, oXML=None): - elem_ide = oXML.find(".//{http://www.portalfiscal.inf.br/nfe}ide") - elem_dest = oXML.find(".//{http://www.portalfiscal.inf.br/nfe}dest") - nMr = self.width-self.nRight - - self.nlin += 1 - - self.canvas.setFont('NimbusSanL-Bold', 7) - self.string(self.nLeft+1, self.nlin+1, 'DESTINATÁRIO/REMETENTE') - self.rect(self.nLeft, self.nlin+2, - self.width-self.nLeft-self.nRight, 20) - self.vline(nMr-25, self.nlin+2, 20) - self.hline(self.nLeft, self.nlin+8.66, self.width-self.nLeft) - self.hline(self.nLeft, self.nlin+15.32, self.width-self.nLeft) - self.vline(nMr-70, self.nlin+2, 6.66) - self.vline(nMr-53, self.nlin+8.66, 6.66) - self.vline(nMr-99, self.nlin+8.66, 6.66) - self.vline(nMr-90, self.nlin+15.32, 6.66) - self.vline(nMr-102, self.nlin+15.32, 6.66) - self.vline(nMr-136, self.nlin+15.32, 6.66) - # Labels/Fields - self.canvas.setFont('NimbusSanL-Bold', 5) - self.string(self.nLeft+1, self.nlin+3.7, 'NOME/RAZÃO SOCIAL') - self.string(nMr-69, self.nlin+3.7, 'CNPJ/CPF') - self.string(nMr-24, self.nlin+3.7, 'DATA DA EMISSÃO') - self.string(self.nLeft+1, self.nlin+10.3, 'ENDEREÇO') - self.string(nMr-98, self.nlin+10.3, 'BAIRRO/DISTRITO') - self.string(nMr-52, self.nlin+10.3, 'CEP') - self.string(nMr-24, self.nlin+10.3, 'DATA DE ENTRADA/SAÍDA') - self.string(self.nLeft+1, self.nlin+17.1, 'MUNICÍPIO') - self.string(nMr-135, self.nlin+17.1, 'FONE/FAX') - self.string(nMr-101, self.nlin+17.1, 'UF') - self.string(nMr-89, self.nlin+17.1, 'INSCRIÇÃO ESTADUAL') - self.string(nMr-24, self.nlin+17.1, 'HORA DE ENTRADA/SAÍDA') - # Conteúdo campos - self.canvas.setFont('NimbusSanL-Regu', 8) - self.string(self.nLeft+1, self.nlin+7.5, - tagtext(oNode=elem_dest, cTag='xNome')) - self.string(nMr-69, self.nlin+7.5, - format_cnpj_cpf(tagtext(oNode=elem_dest, cTag='CNPJ'))) - cDt, cHr = getdateUTC(tagtext(oNode=elem_ide, cTag='dhEmi')) - self.string(nMr-24, self.nlin+7.7, cDt + ' ' + cHr) - cDt, cHr = getdateUTC(tagtext(oNode=elem_ide, cTag='dhSaiEnt')) - self.string(nMr-24, self.nlin+14.3, cDt + ' ' + cHr) # Dt saída - cEnd = tagtext(oNode=elem_dest, cTag='xLgr') + ', ' + tagtext( - oNode=elem_dest, cTag='nro') - self.string(self.nLeft+1, self.nlin+14.3, cEnd) - self.string(nMr-98, self.nlin+14.3, - tagtext(oNode=elem_dest, cTag='xBairro')) - self.string(nMr-52, self.nlin+14.3, - tagtext(oNode=elem_dest, cTag='CEP')) - self.string(self.nLeft+1, self.nlin+21.1, - tagtext(oNode=elem_dest, cTag='xMun')) - self.string(nMr-135, self.nlin+21.1, - tagtext(oNode=elem_dest, cTag='fone')) - self.string(nMr-101, self.nlin+21.1, - tagtext(oNode=elem_dest, cTag='UF')) - self.string(nMr-89, self.nlin+21.1, - tagtext(oNode=elem_dest, cTag='IE')) - - self.nlin += 24 # Nr linhas ocupadas pelo bloco - - def faturas(self, oXML=None): - - nMr = self.width-self.nRight - - self.canvas.setFont('NimbusSanL-Bold', 7) - self.string(self.nLeft+1, self.nlin+1, 'FATURA') - self.rect(self.nLeft, self.nlin+2, - self.width-self.nLeft-self.nRight, 13) - self.vline(nMr-47.5, self.nlin+2, 13) - self.vline(nMr-95, self.nlin+2, 13) - self.vline(nMr-142.5, self.nlin+2, 13) - self.hline(nMr-47.5, self.nlin+8.5, self.width-self.nLeft) - # Labels - self.canvas.setFont('NimbusSanL-Regu', 5) - self.string(nMr-46.5, self.nlin+3.8, 'CÓDIGO VENDEDOR') - self.string(nMr-46.5, self.nlin+10.2, 'NOME VENDEDOR') - self.string(nMr-93.5, self.nlin+3.8, - 'FATURA VENCIMENTO VALOR') - self.string(nMr-140.5, self.nlin+3.8, - 'FATURA VENCIMENTO VALOR') - self.string(self.nLeft+2, self.nlin+3.8, - 'FATURA VENCIMENTO VALOR') - - # Conteúdo campos - self.canvas.setFont('NimbusSanL-Bold', 6) - nLin = 7 - nPar = 1 - nCol = 0 - nAju = 0 - - line_iter = iter(oXML[1:10]) # Salta elemt 1 e considera os próximos 9 - for oXML_dup in line_iter: - - cDt, cHr = getdateUTC(tagtext(oNode=oXML_dup, cTag='dVenc')) - self.string(self.nLeft+nCol+1, self.nlin+nLin, - tagtext(oNode=oXML_dup, cTag='nDup')) - self.string(self.nLeft+nCol+17, self.nlin+nLin, cDt) - self.stringRight( - self.nLeft+nCol+47, self.nlin+nLin, - format_number(tagtext(oNode=oXML_dup, cTag='vDup'), - precision=2)) - - if nPar == 3: - nLin = 7 - nPar = 1 - nCol += 47 - nAju += 1 - nCol += nAju * (0.3) - else: - nLin += 3.3 - nPar += 1 - - # Campos adicionais XML - Condicionados a existencia de financeiro - elem_infAdic = oXML.getparent().find( - ".//{http://www.portalfiscal.inf.br/nfe}infAdic") - if elem_infAdic is not None: - codvend = elem_infAdic.find( - ".//{http://www.portalfiscal.inf.br/nfe}obsCont\ -[@xCampo='CodVendedor']") - self.string(nMr-46.5, self.nlin+7.7, - tagtext(oNode=codvend, cTag='xTexto')) - vend = elem_infAdic.find(".//{http://www.portalfiscal.inf.br/nfe}\ -obsCont[@xCampo='NomeVendedor']") - self.string(nMr-46.5, self.nlin+14.3, - tagtext(oNode=vend, cTag='xTexto')[:36]) - - self.nlin += 16 # Nr linhas ocupadas pelo bloco - - def impostos(self, oXML=None): - # Impostos - el_total = oXML.find(".//{http://www.portalfiscal.inf.br/nfe}total") - nMr = self.width-self.nRight - self.nlin += 1 - self.canvas.setFont('NimbusSanL-Bold', 7) - self.string(self.nLeft+1, self.nlin+1, 'CÁLCULO DO IMPOSTO') - self.rect(self.nLeft, self.nlin+2, - self.width-self.nLeft-self.nRight, 13) - self.hline(self.nLeft, self.nlin+8.5, self.width-self.nLeft) - self.vline(nMr-35, self.nlin+2, 6.5) - self.vline(nMr-65, self.nlin+2, 6.5) - self.vline(nMr-95, self.nlin+2, 6.5) - self.vline(nMr-125, self.nlin+2, 6.5) - self.vline(nMr-155, self.nlin+2, 6.5) - self.vline(nMr-35, self.nlin+8.5, 6.5) - self.vline(nMr-65, self.nlin+8.5, 6.5) - self.vline(nMr-95, self.nlin+8.5, 6.5) - self.vline(nMr-125, self.nlin+8.5, 6.5) - self.vline(nMr-155, self.nlin+8.5, 6.5) - # Labels - self.canvas.setFont('NimbusSanL-Regu', 5) - self.string(self.nLeft+1, self.nlin+3.8, 'BASE DE CÁLCULO DO ICMS') - self.string(nMr-154, self.nlin+3.8, 'VALOR DO ICMS') - self.string(nMr-124, self.nlin+3.8, 'BASE DE CÁLCULO DO ICMS ST') - self.string(nMr-94, self.nlin+3.8, 'VALOR DO ICMS ST') - self.string(nMr-64, self.nlin+3.8, 'VALOR APROX TRIBUTOS') - self.string(nMr-34, self.nlin+3.8, 'VALOR TOTAL DOS PRODUTOS') - - self.string(self.nLeft+1, self.nlin+10.2, 'VALOR DO FRETE') - self.string(nMr-154, self.nlin+10.2, 'VALOR DO SEGURO') - self.string(nMr-124, self.nlin+10.2, 'DESCONTO') - self.string(nMr-94, self.nlin+10.2, 'OUTRAS DESP. ACESSÓRIAS') - self.string(nMr-64, self.nlin+10.2, 'VALOR DO IPI') - self.string(nMr-34, self.nlin+10.2, 'VALOR TOTAL DA NOTA') - - # Conteúdo campos - self.canvas.setFont('NimbusSanL-Regu', 8) - self.stringRight( - self.nLeft+34, self.nlin+7.7, - format_number(tagtext(oNode=el_total, cTag='vBC'), precision=2)) - self.stringRight( - self.nLeft+64, self.nlin+7.7, - format_number(tagtext(oNode=el_total, cTag='vICMS'), precision=2)) - self.stringRight( - self.nLeft+94, self.nlin+7.7, - format_number(tagtext(oNode=el_total, cTag='vBCST'), precision=2)) - self.stringRight( - nMr-66, self.nlin+7.7, - format_number(tagtext(oNode=el_total, cTag='vST'), precision=2)) - self.stringRight( - nMr-36, self.nlin+7.7, - format_number(tagtext(oNode=el_total, cTag='vTotTrib'), - precision=2)) - self.stringRight( - nMr-1, self.nlin+7.7, - format_number(tagtext(oNode=el_total, cTag='vProd'), precision=2)) - self.stringRight( - self.nLeft+34, self.nlin+14.1, - format_number(tagtext(oNode=el_total, cTag='vFrete'), precision=2)) - self.stringRight( - self.nLeft+64, self.nlin+14.1, - format_number(tagtext(oNode=el_total, cTag='vSeg'), precision=2)) - self.stringRight( - self.nLeft+94, self.nlin+14.1, - format_number(tagtext(oNode=el_total, cTag='vDesc'), precision=2)) - self.stringRight( - self.nLeft+124, self.nlin+14.1, - format_number(tagtext(oNode=el_total, cTag='vOutro'), precision=2)) - self.stringRight( - self.nLeft+154, self.nlin+14.1, - format_number(tagtext(oNode=el_total, cTag='vIPI'), precision=2)) - self.stringRight( - nMr-1, self.nlin+14.1, - format_number(tagtext(oNode=el_total, cTag='vNF'), precision=2)) - - self.nlin += 17 # Nr linhas ocupadas pelo bloco - - def transportes(self, oXML=None): - el_transp = oXML.find(".//{http://www.portalfiscal.inf.br/nfe}transp") - nMr = self.width-self.nRight - - self.canvas.setFont('NimbusSanL-Bold', 7) - self.string(self.nLeft+1, self.nlin+1, - 'TRANSPORTADOR/VOLUMES TRANSPORTADOS') - self.canvas.setFont('NimbusSanL-Regu', 5) - self.rect(self.nLeft, self.nlin+2, - self.width-self.nLeft-self.nRight, 20) - self.hline(self.nLeft, self.nlin+8.6, self.width-self.nLeft) - self.hline(self.nLeft, self.nlin+15.2, self.width-self.nLeft) - self.vline(nMr-40, self.nlin+2, 13.2) - self.vline(nMr-49, self.nlin+2, 20) - self.vline(nMr-92, self.nlin+2, 6.6) - self.vline(nMr-120, self.nlin+2, 6.6) - self.vline(nMr-75, self.nlin+2, 6.6) - self.vline(nMr-26, self.nlin+15.2, 6.6) - self.vline(nMr-102, self.nlin+8.6, 6.6) - self.vline(nMr-85, self.nlin+15.2, 6.6) - self.vline(nMr-121, self.nlin+15.2, 6.6) - self.vline(nMr-160, self.nlin+15.2, 6.6) - # Labels/Fields - self.string(nMr-39, self.nlin+3.8, 'CNPJ/CPF') - self.string(nMr-74, self.nlin+3.8, 'PLACA DO VEÍCULO') - self.string(nMr-91, self.nlin+3.8, 'CÓDIGO ANTT') - self.string(nMr-119, self.nlin+3.8, 'FRETE POR CONTA') - self.string(self.nLeft+1, self.nlin+3.8, 'RAZÃO SOCIAL') - self.string(nMr-48, self.nlin+3.8, 'UF') - self.string(nMr-39, self.nlin+10.3, 'INSCRIÇÃO ESTADUAL') - self.string(nMr-48, self.nlin+10.3, 'UF') - self.string(nMr-101, self.nlin+10.3, 'MUNICÍPIO') - self.string(self.nLeft+1, self.nlin+10.3, 'ENDEREÇO') - self.string(nMr-48, self.nlin+17, 'PESO BRUTO') - self.string(nMr-25, self.nlin+17, 'PESO LÍQUIDO') - self.string(nMr-84, self.nlin+17, 'NUMERAÇÃO') - self.string(nMr-120, self.nlin+17, 'MARCA') - self.string(nMr-159, self.nlin+17, 'ESPÉCIE') - self.string(self.nLeft+1, self.nlin+17, 'QUANTIDADE') - # Conteúdo campos - self.canvas.setFont('NimbusSanL-Regu', 8) - self.string(self.nLeft+1, self.nlin+7.7, - tagtext(oNode=el_transp, cTag='xNome')[:40]) - self.string(self.nLeft+71, self.nlin+7.7, - self.oFrete[tagtext(oNode=el_transp, cTag='modFrete')]) - self.string(nMr-39, self.nlin+7.7, - format_cnpj_cpf(tagtext(oNode=el_transp, cTag='CNPJ'))) - self.string(self.nLeft+1, self.nlin+14.2, - tagtext(oNode=el_transp, cTag='xEnder')[:45]) - self.string(self.nLeft+89, self.nlin+14.2, - tagtext(oNode=el_transp, cTag='xMun')) - self.string(nMr-48, self.nlin+14.2, - tagtext(oNode=el_transp, cTag='UF')) - self.string(nMr-39, self.nlin+14.2, - tagtext(oNode=el_transp, cTag='IE')) - self.string(self.nLeft+1, self.nlin+21.2, - tagtext(oNode=el_transp, cTag='qVol')) - self.string(self.nLeft+31, self.nlin+21.2, - tagtext(oNode=el_transp, cTag='esp')) - self.string(self.nLeft+70, self.nlin+21.2, - tagtext(oNode=el_transp, cTag='marca')) - self.string(self.nLeft+106, self.nlin+21.2, - tagtext(oNode=el_transp, cTag='nVol')) - self.stringRight( - nMr-27, self.nlin+21.2, - format_number(tagtext(oNode=el_transp, cTag='pesoB'), precision=3)) - self.stringRight( - nMr-1, self.nlin+21.2, - format_number(tagtext(oNode=el_transp, cTag='pesoL'), precision=3)) - - self.nlin += 23 - - def produtos(self, oXML=None, el_det=None, oPaginator=None, - list_desc=None, list_cod_prod=None, nHeight=29): - - nMr = self.width-self.nRight - nStep = 2.5 # Passo entre linhas - nH = 7.5 + (nHeight * nStep) # cabeçalho 7.5 - self.nlin += 1 - - self.canvas.setFont('NimbusSanL-Bold', 7) - self.string(self.nLeft+1, self.nlin+1, 'DADOS DO PRODUTO/SERVIÇO') - self.rect(self.nLeft, self.nlin+2, - self.width-self.nLeft-self.nRight, nH) - self.hline(self.nLeft, self.nlin+8, self.width-self.nLeft) - - self.canvas.setFont('NimbusSanL-Regu', 5.5) - # Colunas - self.vline(self.nLeft+15, self.nlin+2, nH) - self.stringcenter(self.nLeft+7.5, self.nlin+5.5, 'CÓDIGO') - self.vline(nMr-7, self.nlin+2, nH) - self.stringcenter(nMr-3.5, self.nlin+4.5, 'ALÍQ') - self.stringcenter(nMr-3.5, self.nlin+6.5, 'IPI') - self.vline(nMr-14, self.nlin+2, nH) - self.stringcenter(nMr-10.5, self.nlin+4.5, 'ALÍQ') - self.stringcenter(nMr-10.5, self.nlin+6.5, 'ICMS') - self.vline(nMr-26, self.nlin+2, nH) - self.stringcenter(nMr-20, self.nlin+5.5, 'VLR. IPI') - self.vline(nMr-38, self.nlin+2, nH) - self.stringcenter(nMr-32, self.nlin+5.5, 'VLR. ICMS') - self.vline(nMr-50, self.nlin+2, nH) - self.stringcenter(nMr-44, self.nlin+5.5, 'BC ICMS') - self.vline(nMr-64, self.nlin+2, nH) - self.stringcenter(nMr-57, self.nlin+5.5, 'VLR TOTAL') - self.vline(nMr-77, self.nlin+2, nH) - self.stringcenter(nMr-70.5, self.nlin+5.5, 'VLR UNIT') - self.vline(nMr-90, self.nlin+2, nH) - self.stringcenter(nMr-83.5, self.nlin+5.5, 'QTD') - self.vline(nMr-96, self.nlin+2, nH) - self.stringcenter(nMr-93, self.nlin+5.5, 'UNID') - self.vline(nMr-102, self.nlin+2, nH) - self.stringcenter(nMr-99, self.nlin+5.5, 'CFOP') - self.vline(nMr-108, self.nlin+2, nH) - self.stringcenter(nMr-105, self.nlin+5.5, 'CST') - self.vline(nMr-117, self.nlin+2, nH) - self.stringcenter(nMr-112.5, self.nlin+5.5, 'NCM/SH') - - nWidth_Prod = nMr-135-self.nLeft-11 - nCol_ = self.nLeft+20 + (nWidth_Prod / 2) - self.stringcenter(nCol_, self.nlin+5.5, 'DESCRIÇÃO DO PRODUTO/SERVIÇO') - - # Conteúdo campos - self.canvas.setFont('NimbusSanL-Regu', 5) - nLin = self.nlin+10.5 - - for id in xrange(oPaginator[0], oPaginator[1]): - item = el_det[id] - el_prod = item.find(".//{http://www.portalfiscal.inf.br/nfe}prod") - el_imp = item.find( - ".//{http://www.portalfiscal.inf.br/nfe}imposto") - - el_imp_ICMS = el_imp.find( - ".//{http://www.portalfiscal.inf.br/nfe}ICMS") - el_imp_IPI = el_imp.find( - ".//{http://www.portalfiscal.inf.br/nfe}IPI") - - cCST = tagtext(oNode=el_imp_ICMS, cTag='orig') + \ - tagtext(oNode=el_imp_ICMS, cTag='CST') - vBC = tagtext(oNode=el_imp_ICMS, cTag='vBC') - vICMS = tagtext(oNode=el_imp_ICMS, cTag='vICMS') - pICMS = tagtext(oNode=el_imp_ICMS, cTag='pICMS') - - vIPI = tagtext(oNode=el_imp_IPI, cTag='vIPI') - pIPI = tagtext(oNode=el_imp_IPI, cTag='pIPI') - - self.stringcenter(nMr-112.5, nLin, - tagtext(oNode=el_prod, cTag='NCM')) - self.stringcenter(nMr-105, nLin, cCST) - self.stringcenter(nMr-99, nLin, - tagtext(oNode=el_prod, cTag='CFOP')) - self.stringcenter(nMr-93, nLin, - tagtext(oNode=el_prod, cTag='uCom')) - self.stringRight(nMr-77.5, nLin, format_number( - tagtext(oNode=el_prod, cTag='qCom'), precision=4)) - self.stringRight(nMr-64.5, nLin, format_number( - tagtext(oNode=el_prod, cTag='vUnCom'), precision=2)) - self.stringRight(nMr-50.5, nLin, format_number( - tagtext(oNode=el_prod, cTag='vProd'), precision=2)) - self.stringRight(nMr-38.5, nLin, format_number(vBC, precision=2)) - self.stringRight(nMr-26.5, nLin, format_number(vICMS, precision=2)) - self.stringRight(nMr-7.5, nLin, format_number(pICMS, precision=2)) - - if vIPI: - self.stringRight(nMr-14.5, nLin, - format_number(vIPI, precision=2)) - if pIPI: - self.stringRight(nMr-0.5, nLin, - format_number(pIPI, precision=2)) - - # Código Item - line_cod = nLin - for des in list_cod_prod[id]: - self.string(self.nLeft+0.2, line_cod, des) - line_cod += nStep - - # Descrição Item - line_desc = nLin - for des in list_desc[id]: - self.string(self.nLeft+15.5, line_desc, des) - line_desc += nStep - - nLin = max(line_cod, line_desc) - self.canvas.setStrokeColor(gray) - self.hline(self.nLeft, nLin-2, self.width-self.nLeft) - self.canvas.setStrokeColor(black) - - self.nlin += nH + 3 - - def adicionais(self, oXML=None): - el_infAdic = oXML.find( - ".//{http://www.portalfiscal.inf.br/nfe}infAdic") - - self.nlin += 2 - self.canvas.setFont('NimbusSanL-Bold', 6) - self.string(self.nLeft+1, self.nlin+1, 'DADOS ADICIONAIS') - self.canvas.setFont('NimbusSanL-Regu', 5) - self.string(self.nLeft+1, self.nlin+4, 'INFORMAÇÕES COMPLEMENTARES') - self.string((self.width/2)+1, self.nlin+4, 'RESERVADO AO FISCO') - self.rect(self.nLeft, self.nlin+2, - self.width-self.nLeft-self.nRight, 42) - self.vline(self.width/2, self.nlin+2, 42) - # Conteúdo campos - styles = getSampleStyleSheet() - styleN = styles['Normal'] - styleN.fontSize = 6 - styleN.fontName = 'NimbusSanL-Regu' - styleN.leading = 7 - - fisco = tagtext(oNode=el_infAdic, cTag='infAdFisco') - observacoes = tagtext(oNode=el_infAdic, cTag='infCpl') - if fisco: - observacoes = fisco + ' ' + observacoes - P = Paragraph(observacoes, styles['Normal']) - w, h = P.wrap(92*mm, 32*mm) - altura = (self.height-self.nlin-5)*mm - P.drawOn(self.canvas, (self.nLeft+1)*mm, altura - h) - self.nlin += 36 - - def recibo_entrega(self, oXML=None): - el_ide = oXML.find(".//{http://www.portalfiscal.inf.br/nfe}ide") - el_dest = oXML.find(".//{http://www.portalfiscal.inf.br/nfe}dest") - el_total = oXML.find(".//{http://www.portalfiscal.inf.br/nfe}total") - el_emit = oXML.find(".//{http://www.portalfiscal.inf.br/nfe}emit") - - # self.nlin = self.height-self.nBottom-18 # 17 altura recibo - nW = 40 - nH = 17 - self.canvas.setLineWidth(.5) - self.rect(self.nLeft, self.nlin, - self.width-(self.nLeft+self.nRight), nH) - self.hline(self.nLeft, self.nlin+8.5, self.width-self.nRight-nW) - self.vline(self.width-self.nRight-nW, self.nlin, nH) - self.vline(self.nLeft+nW, self.nlin+8.5, 8.5) - - # Labels - self.canvas.setFont('NimbusSanL-Regu', 5) - self.string(self.nLeft+1, self.nlin+10.2, 'DATA DE RECEBIMENTO') - self.string(self.nLeft+41, self.nlin+10.2, - 'IDENTIFICAÇÃO E ASSINATURA DO RECEBEDOR') - self.stringcenter(self.width-self.nRight-(nW/2), self.nlin+2, 'NF-e') - # Conteúdo campos - self.canvas.setFont('NimbusSanL-Bold', 8) - cNF = tagtext(oNode=el_ide, cTag='nNF') - cNF = '{0:011,}'.format(int(cNF)).replace(",", ".") - self.string(self.width-self.nRight-nW+2, self.nlin+8, "Nº %s" % (cNF)) - self.string(self.width-self.nRight-nW+2, self.nlin+14, - u"SÉRIE %s" % (tagtext(oNode=el_ide, cTag='serie'))) - - cDt, cHr = getdateUTC(tagtext(oNode=el_ide, cTag='dhEmi')) - cTotal = format_number(tagtext(oNode=el_total, cTag='vNF'), - precision=2) - - cEnd = tagtext(oNode=el_dest, cTag='xNome') + ' - ' - cEnd += tagtext(oNode=el_dest, cTag='xLgr') + ', ' + tagtext( - oNode=el_dest, cTag='nro') + ', ' - cEnd += tagtext(oNode=el_dest, cTag='xBairro') + ', ' + tagtext( - oNode=el_dest, cTag='xMun') + ' - ' - cEnd += tagtext(oNode=el_dest, cTag='UF') - - cString = u""" - RECEBEMOS DE %s OS PRODUTOS/SERVIÇOS CONSTANTES DA NOTA FISCAL INDICADA - ABAIXO. EMISSÃO: %s VALOR TOTAL: %s - DESTINATARIO: %s""" % (tagtext(oNode=el_emit, cTag='xNome'), - cDt, cTotal, cEnd) - - styles = getSampleStyleSheet() - styleN = styles['Normal'] - styleN.fontName = 'NimbusSanL-Regu' - styleN.fontSize = 6 - styleN.leading = 7 - - P = Paragraph(cString, styleN) - w, h = P.wrap(149*mm, 7*mm) - P.drawOn(self.canvas, (self.nLeft+1)*mm, - ((self.height-self.nlin)*mm) - h) - - self.nlin += 20 - self.hline(self.nLeft, self.nlin, self.width-self.nRight) - self.nlin += 2 - - def newpage(self): - self.nlin = self.nTop - self.Page += 1 - self.canvas.showPage() - - def hline(self, x, y, width): - y = self.height - y - self.canvas.line(x*mm, y*mm, width*mm, y*mm) - - def vline(self, x, y, width): - width = self.height - y - width - y = self.height - y - self.canvas.line(x*mm, y*mm, x*mm, width*mm) - - def rect(self, col, lin, nWidth, nHeight, fill=False): - lin = self.height - nHeight - lin - self.canvas.rect(col*mm, lin*mm, nWidth*mm, nHeight*mm, - stroke=True, fill=fill) - - def string(self, x, y, value): - y = self.height - y - self.canvas.drawString(x*mm, y*mm, value) - - def stringRight(self, x, y, value): - y = self.height - y - self.canvas.drawRightString(x*mm, y*mm, value) - - def stringcenter(self, x, y, value): - y = self.height - y - self.canvas.drawCentredString(x*mm, y*mm, value) - - def writeto_pdf(self, fileObj): - pdf_out = self.oPDF_IO.getvalue() - self.oPDF_IO.close() - fileObj.write(pdf_out) +# -*- coding: utf-8 -*- +# © 2017 Edson Bernardino, ITK Soft +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +# Classe para geração de PDF da DANFE a partir de xml etree.fromstring + + +from io import BytesIO +from textwrap import wrap + +from reportlab.lib import utils +from reportlab.pdfgen import canvas +from reportlab.lib.units import mm, cm +from reportlab.lib.pagesizes import A4 +from reportlab.lib.colors import black, gray +from reportlab.graphics.barcode import code128 +from reportlab.lib.styles import getSampleStyleSheet +from reportlab.lib.enums import TA_CENTER +from reportlab.platypus import Paragraph, Image + + +def chunks(cString, nLen): + for start in range(0, len(cString), nLen): + yield cString[start:start+nLen] + + +def format_cnpj_cpf(value): + if len(value) < 12: # CPF + cValue = '%s.%s.%s-%s' % (value[:-8], value[-8:-5], + value[-5:-2], value[-2:]) + else: + cValue = '%s.%s.%s/%s-%s' % (value[:-12], value[-12:-9], + value[-9:-6], value[-6:-2], value[-2:]) + return cValue + + +def getdateUTC(cDateUTC): + cDt = cDateUTC[0:10].split('-') + cDt.reverse() + return '/'.join(cDt), cDateUTC[11:16] + + +def format_number(cNumber, precision=0, group_sep='.', decimal_sep=','): + if cNumber: + number = float(cNumber) + return ("{:,." + str(precision) + "f}").format(number).\ + replace(",", "X").replace(".", ",").replace("X", ".") + return "" + + +def tagtext(oNode=None, cTag=None): + try: + xpath = ".//{http://www.portalfiscal.inf.br/nfe}%s" % (cTag) + cText = oNode.find(xpath).text + except: + cText = '' + return cText + +REGIME_TRIBUTACAO = { + '1': 'Simples Nacional', + '2': 'Simples Nacional, excesso sublimite de receita bruta', + '3': 'Regime Normal' +} + + +def get_image(path, width=1*cm): + img = utils.ImageReader(path) + iw, ih = img.getSize() + aspect = ih / float(iw) + return Image(path, width=width, height=(width * aspect)) + + +class danfe(object): + def __init__(self, sizepage=A4, list_xml=None, recibo=True, + orientation='portrait', logo=None): + self.width = 210 # 21 x 29,7cm + self.height = 297 + self.nLeft = 10 + self.nRight = 10 + self.nTop = 7 + self.nBottom = 8 + self.nlin = self.nTop + self.logo = logo + self.oFrete = {'0': '0 - Emitente', + '1': '1 - Dest/Remet', + '2': '2 - Terceiros', + '9': '9 - Sem Frete'} + + self.oPDF_IO = BytesIO() + if orientation == 'landscape': + raise NameError('Rotina não implementada') + else: + size = sizepage + + self.canvas = canvas.Canvas(self.oPDF_IO, pagesize=size) + self.canvas.setTitle('DANFE') + self.canvas.setStrokeColor(black) + + for oXML in list_xml: + oXML_cobr = oXML.find( + ".//{http://www.portalfiscal.inf.br/nfe}cobr") + + self.NrPages = 1 + self.Page = 1 + + # Calculando total linhas usadas para descrições dos itens + # Com bloco fatura, apenas 29 linhas para itens na primeira folha + nNr_Lin_Pg_1 = 34 if oXML_cobr is None else 30 + # [ rec_ini , rec_fim , lines , limit_lines ] + oPaginator = [[0, 0, 0, nNr_Lin_Pg_1]] + el_det = oXML.findall(".//{http://www.portalfiscal.inf.br/nfe}det") + if el_det is not None: + list_desc = [] + list_cod_prod = [] + nPg = 0 + for nId, item in enumerate(el_det): + el_prod = item.find( + ".//{http://www.portalfiscal.inf.br/nfe}prod") + infAdProd = item.find( + ".//{http://www.portalfiscal.inf.br/nfe}infAdProd") + + list_ = wrap(tagtext(oNode=el_prod, cTag='xProd'), 56) + if infAdProd is not None: + list_.extend(wrap(infAdProd.text, 56)) + list_desc.append(list_) + + list_cProd = wrap(tagtext(oNode=el_prod, cTag='cProd'), 14) + list_cod_prod.append(list_cProd) + + # Nr linhas necessárias p/ descrição item + nLin_Itens = len(list_) + + if (oPaginator[nPg][2] + nLin_Itens) >= oPaginator[nPg][3]: + oPaginator.append([0, 0, 0, 77]) + nPg += 1 + oPaginator[nPg][0] = nId + oPaginator[nPg][1] = nId + 1 + oPaginator[nPg][2] = nLin_Itens + else: + # adiciona-se 1 pelo funcionamento de xrange + oPaginator[nPg][1] = nId + 1 + oPaginator[nPg][2] += nLin_Itens + + self.NrPages = len(oPaginator) # Calculando nr. páginas + + if recibo: + self.recibo_entrega(oXML=oXML) + + self.ide_emit(oXML=oXML) + self.destinatario(oXML=oXML) + + if oXML_cobr is not None: + self.faturas(oXML=oXML_cobr) + + self.impostos(oXML=oXML) + self.transportes(oXML=oXML) + self.produtos(oXML=oXML, el_det=el_det, oPaginator=oPaginator[0], + list_desc=list_desc, list_cod_prod=list_cod_prod) + + self.adicionais(oXML=oXML) + + # Gera o restante das páginas do XML + for oPag in oPaginator[1:]: + self.newpage() + self.ide_emit(oXML=oXML) + self.produtos(oXML=oXML, el_det=el_det, oPaginator=oPag, + list_desc=list_desc, nHeight=77, + list_cod_prod=list_cod_prod) + + self.newpage() + + self.canvas.save() + + def ide_emit(self, oXML=None): + elem_infNFe = oXML.find( + ".//{http://www.portalfiscal.inf.br/nfe}infNFe") + elem_protNFe = oXML.find( + ".//{http://www.portalfiscal.inf.br/nfe}protNFe") + elem_emit = oXML.find(".//{http://www.portalfiscal.inf.br/nfe}emit") + elem_ide = oXML.find(".//{http://www.portalfiscal.inf.br/nfe}ide") + + cChave = elem_infNFe.attrib.get('Id')[3:] + barcode128 = code128.Code128(cChave, barHeight=10*mm, barWidth=0.25*mm) + + self.canvas.setLineWidth(.5) + self.rect(self.nLeft, self.nlin+1, self.nLeft+75, 32) + self.rect(self.nLeft+115, self.nlin+1, + self.width-self.nLeft-self.nRight-115, 39) + + self.hline(self.nLeft+85, self.nlin+1, 125) + + self.rect(self.nLeft+116, self.nlin+15, + self.width-self.nLeft-self.nRight-117, 6) + + self.rect(self.nLeft, self.nlin+33, + self.width-self.nLeft-self.nRight, 14) + self.hline(self.nLeft, self.nlin+40, self.width-self.nRight) + self.vline(self.nLeft+60, self.nlin+40, 7) + self.vline(self.nLeft+100, self.nlin+40, 7) + + # Labels + self.canvas.setFont('NimbusSanL-Bold', 12) + self.stringcenter(self.nLeft+98, self.nlin+5, 'DANFE') + self.stringcenter(self.nLeft+109, self.nlin+19.5, + tagtext(oNode=elem_ide, cTag='tpNF')) + self.canvas.setFont('NimbusSanL-Bold', 8) + cNF = tagtext(oNode=elem_ide, cTag='nNF') + cNF = '{0:011,}'.format(int(cNF)).replace(",", ".") + self.stringcenter(self.nLeft+100, self.nlin+25, "Nº %s" % (cNF)) + + self.stringcenter(self.nLeft+100, self.nlin+29, "SÉRIE %s" % ( + tagtext(oNode=elem_ide, cTag='serie'))) + cPag = "Página %s de %s" % (str(self.Page), str(self.NrPages)) + self.stringcenter(self.nLeft+100, self.nlin+32, cPag) + self.canvas.setFont('NimbusSanL-Regu', 6) + self.string(self.nLeft+86, self.nlin+8, 'Documento Auxiliar da') + self.string(self.nLeft+86, self.nlin+10.5, 'Nota Fiscal Eletrônica') + self.string(self.nLeft+86, self.nlin+16, '0 - Entrada') + self.string(self.nLeft+86, self.nlin+19, '1 - Saída') + self.rect(self.nLeft+105, self.nlin+15, 8, 6) + + self.stringcenter( + self.nLeft+152, self.nlin+25, + 'Consulta de autenticidade no portal nacional da NF-e') + self.stringcenter( + self.nLeft+152, self.nlin+28, + 'www.nfe.fazenda.gov.br/portal ou no site da SEFAZ Autorizadora') + self.canvas.setFont('NimbusSanL-Regu', 5) + self.string(self.nLeft+117, self.nlin+16.7, 'CHAVE DE ACESSO') + self.string(self.nLeft+116, self.nlin+2.7, 'CONTROLE DO FISCO') + + self.string(self.nLeft+1, self.nlin+34.7, 'NATUREZA DA OPERAÇÃO') + self.string(self.nLeft+116, self.nlin+34.7, + 'PROTOCOLO DE AUTORIZAÇÃO DE USO') + self.string(self.nLeft+1, self.nlin+41.7, 'INSCRIÇÃO ESTADUAL') + self.string(self.nLeft+61, self.nlin+41.7, + 'INSCRIÇÃO ESTADUAL DO SUBST. TRIB.') + self.string(self.nLeft+101, self.nlin+41.7, 'CNPJ') + + # Conteúdo campos + barcode128.drawOn(self.canvas, (self.nLeft+111.5)*mm, + (self.height-self.nlin-14)*mm) + self.canvas.setFont('NimbusSanL-Bold', 6) + nW_Rect = (self.width-self.nLeft-self.nRight-117) / 2 + self.stringcenter(self.nLeft+116.5+nW_Rect, self.nlin+19.5, + ' '.join(chunks(cChave, 4))) # Chave + self.canvas.setFont('NimbusSanL-Regu', 8) + cDt, cHr = getdateUTC(tagtext(oNode=elem_protNFe, cTag='dhRecbto')) + cProtocolo = tagtext(oNode=elem_protNFe, cTag='nProt') + cDt = cProtocolo + ' - ' + cDt + ' ' + cHr + nW_Rect = (self.width-self.nLeft-self.nRight-110) / 2 + self.stringcenter(self.nLeft+115+nW_Rect, self.nlin+38.7, cDt) + self.canvas.setFont('NimbusSanL-Regu', 8) + self.string(self.nLeft+1, self.nlin+38.7, + tagtext(oNode=elem_ide, cTag='natOp')) + self.string(self.nLeft+1, self.nlin+46, + tagtext(oNode=elem_emit, cTag='IE')) + self.string(self.nLeft+101, self.nlin+46, + format_cnpj_cpf(tagtext(oNode=elem_emit, cTag='CNPJ'))) + + styles = getSampleStyleSheet() + styleN = styles['Normal'] + styleN.fontSize = 10 + styleN.fontName = 'NimbusSanL-Bold' + styleN.alignment = TA_CENTER + + # Razão Social emitente + P = Paragraph(tagtext(oNode=elem_emit, cTag='xNome'), styleN) + w, h = P.wrap(55*mm, 50*mm) + P.drawOn(self.canvas, (self.nLeft+30)*mm, + (self.height-self.nlin-12)*mm) + + if self.logo: + img = get_image(self.logo, width=2*cm) + img.drawOn(self.canvas, (self.nLeft+5)*mm, + (self.height-self.nlin-22)*mm) + + cEnd = tagtext(oNode=elem_emit, cTag='xLgr') + ', ' + tagtext( + oNode=elem_emit, cTag='nro') + ' - ' + cEnd += tagtext(oNode=elem_emit, cTag='xBairro') + '
' + tagtext( + oNode=elem_emit, cTag='xMun') + ' - ' + cEnd += 'Fone: ' + tagtext(oNode=elem_emit, cTag='fone') + '
' + cEnd += tagtext(oNode=elem_emit, cTag='UF') + ' - ' + tagtext( + oNode=elem_emit, cTag='CEP') + + regime = tagtext(oNode=elem_emit, cTag='CRT') + cEnd += '
Regime Tributário: %s' % (REGIME_TRIBUTACAO[regime]) + + styleN.fontName = 'NimbusSanL-Regu' + styleN.fontSize = 7 + styleN.leading = 10 + P = Paragraph(cEnd, styleN) + w, h = P.wrap(55*mm, 30*mm) + P.drawOn(self.canvas, (self.nLeft+30)*mm, + (self.height-self.nlin-31)*mm) + + # Homologação + if tagtext(oNode=elem_ide, cTag='tpAmb') == '2': + self.canvas.saveState() + self.canvas.rotate(90) + self.canvas.setFont('Times-Bold', 40) + self.canvas.setFillColorRGB(0.57, 0.57, 0.57) + self.string(self.nLeft+65, 449, 'SEM VALOR FISCAL') + self.canvas.restoreState() + + self.nlin += 48 + + def destinatario(self, oXML=None): + elem_ide = oXML.find(".//{http://www.portalfiscal.inf.br/nfe}ide") + elem_dest = oXML.find(".//{http://www.portalfiscal.inf.br/nfe}dest") + nMr = self.width-self.nRight + + self.nlin += 1 + + self.canvas.setFont('NimbusSanL-Bold', 7) + self.string(self.nLeft+1, self.nlin+1, 'DESTINATÁRIO/REMETENTE') + self.rect(self.nLeft, self.nlin+2, + self.width-self.nLeft-self.nRight, 20) + self.vline(nMr-25, self.nlin+2, 20) + self.hline(self.nLeft, self.nlin+8.66, self.width-self.nLeft) + self.hline(self.nLeft, self.nlin+15.32, self.width-self.nLeft) + self.vline(nMr-70, self.nlin+2, 6.66) + self.vline(nMr-53, self.nlin+8.66, 6.66) + self.vline(nMr-99, self.nlin+8.66, 6.66) + self.vline(nMr-90, self.nlin+15.32, 6.66) + self.vline(nMr-102, self.nlin+15.32, 6.66) + self.vline(nMr-136, self.nlin+15.32, 6.66) + # Labels/Fields + self.canvas.setFont('NimbusSanL-Bold', 5) + self.string(self.nLeft+1, self.nlin+3.7, 'NOME/RAZÃO SOCIAL') + self.string(nMr-69, self.nlin+3.7, 'CNPJ/CPF') + self.string(nMr-24, self.nlin+3.7, 'DATA DA EMISSÃO') + self.string(self.nLeft+1, self.nlin+10.3, 'ENDEREÇO') + self.string(nMr-98, self.nlin+10.3, 'BAIRRO/DISTRITO') + self.string(nMr-52, self.nlin+10.3, 'CEP') + self.string(nMr-24, self.nlin+10.3, 'DATA DE ENTRADA/SAÍDA') + self.string(self.nLeft+1, self.nlin+17.1, 'MUNICÍPIO') + self.string(nMr-135, self.nlin+17.1, 'FONE/FAX') + self.string(nMr-101, self.nlin+17.1, 'UF') + self.string(nMr-89, self.nlin+17.1, 'INSCRIÇÃO ESTADUAL') + self.string(nMr-24, self.nlin+17.1, 'HORA DE ENTRADA/SAÍDA') + # Conteúdo campos + self.canvas.setFont('NimbusSanL-Regu', 8) + self.string(self.nLeft+1, self.nlin+7.5, + tagtext(oNode=elem_dest, cTag='xNome')) + self.string(nMr-69, self.nlin+7.5, + format_cnpj_cpf(tagtext(oNode=elem_dest, cTag='CNPJ'))) + cDt, cHr = getdateUTC(tagtext(oNode=elem_ide, cTag='dhEmi')) + self.string(nMr-24, self.nlin+7.7, cDt + ' ' + cHr) + cDt, cHr = getdateUTC(tagtext(oNode=elem_ide, cTag='dhSaiEnt')) + self.string(nMr-24, self.nlin+14.3, cDt + ' ' + cHr) # Dt saída + cEnd = tagtext(oNode=elem_dest, cTag='xLgr') + ', ' + tagtext( + oNode=elem_dest, cTag='nro') + self.string(self.nLeft+1, self.nlin+14.3, cEnd) + self.string(nMr-98, self.nlin+14.3, + tagtext(oNode=elem_dest, cTag='xBairro')) + self.string(nMr-52, self.nlin+14.3, + tagtext(oNode=elem_dest, cTag='CEP')) + self.string(self.nLeft+1, self.nlin+21.1, + tagtext(oNode=elem_dest, cTag='xMun')) + self.string(nMr-135, self.nlin+21.1, + tagtext(oNode=elem_dest, cTag='fone')) + self.string(nMr-101, self.nlin+21.1, + tagtext(oNode=elem_dest, cTag='UF')) + self.string(nMr-89, self.nlin+21.1, + tagtext(oNode=elem_dest, cTag='IE')) + + self.nlin += 24 # Nr linhas ocupadas pelo bloco + + def faturas(self, oXML=None): + + nMr = self.width-self.nRight + + self.canvas.setFont('NimbusSanL-Bold', 7) + self.string(self.nLeft+1, self.nlin+1, 'FATURA') + self.rect(self.nLeft, self.nlin+2, + self.width-self.nLeft-self.nRight, 13) + self.vline(nMr-47.5, self.nlin+2, 13) + self.vline(nMr-95, self.nlin+2, 13) + self.vline(nMr-142.5, self.nlin+2, 13) + self.hline(nMr-47.5, self.nlin+8.5, self.width-self.nLeft) + # Labels + self.canvas.setFont('NimbusSanL-Regu', 5) + self.string(nMr-46.5, self.nlin+3.8, 'CÓDIGO VENDEDOR') + self.string(nMr-46.5, self.nlin+10.2, 'NOME VENDEDOR') + self.string(nMr-93.5, self.nlin+3.8, + 'FATURA VENCIMENTO VALOR') + self.string(nMr-140.5, self.nlin+3.8, + 'FATURA VENCIMENTO VALOR') + self.string(self.nLeft+2, self.nlin+3.8, + 'FATURA VENCIMENTO VALOR') + + # Conteúdo campos + self.canvas.setFont('NimbusSanL-Bold', 6) + nLin = 7 + nPar = 1 + nCol = 0 + nAju = 0 + + line_iter = iter(oXML[1:10]) # Salta elemt 1 e considera os próximos 9 + for oXML_dup in line_iter: + + cDt, cHr = getdateUTC(tagtext(oNode=oXML_dup, cTag='dVenc')) + self.string(self.nLeft+nCol+1, self.nlin+nLin, + tagtext(oNode=oXML_dup, cTag='nDup')) + self.string(self.nLeft+nCol+17, self.nlin+nLin, cDt) + self.stringRight( + self.nLeft+nCol+47, self.nlin+nLin, + format_number(tagtext(oNode=oXML_dup, cTag='vDup'), + precision=2)) + + if nPar == 3: + nLin = 7 + nPar = 1 + nCol += 47 + nAju += 1 + nCol += nAju * (0.3) + else: + nLin += 3.3 + nPar += 1 + + # Campos adicionais XML - Condicionados a existencia de financeiro + elem_infAdic = oXML.getparent().find( + ".//{http://www.portalfiscal.inf.br/nfe}infAdic") + if elem_infAdic is not None: + codvend = elem_infAdic.find( + ".//{http://www.portalfiscal.inf.br/nfe}obsCont\ +[@xCampo='CodVendedor']") + self.string(nMr-46.5, self.nlin+7.7, + tagtext(oNode=codvend, cTag='xTexto')) + vend = elem_infAdic.find(".//{http://www.portalfiscal.inf.br/nfe}\ +obsCont[@xCampo='NomeVendedor']") + self.string(nMr-46.5, self.nlin+14.3, + tagtext(oNode=vend, cTag='xTexto')[:36]) + + self.nlin += 16 # Nr linhas ocupadas pelo bloco + + def impostos(self, oXML=None): + # Impostos + el_total = oXML.find(".//{http://www.portalfiscal.inf.br/nfe}total") + nMr = self.width-self.nRight + self.nlin += 1 + self.canvas.setFont('NimbusSanL-Bold', 7) + self.string(self.nLeft+1, self.nlin+1, 'CÁLCULO DO IMPOSTO') + self.rect(self.nLeft, self.nlin+2, + self.width-self.nLeft-self.nRight, 13) + self.hline(self.nLeft, self.nlin+8.5, self.width-self.nLeft) + self.vline(nMr-35, self.nlin+2, 6.5) + self.vline(nMr-65, self.nlin+2, 6.5) + self.vline(nMr-95, self.nlin+2, 6.5) + self.vline(nMr-125, self.nlin+2, 6.5) + self.vline(nMr-155, self.nlin+2, 6.5) + self.vline(nMr-35, self.nlin+8.5, 6.5) + self.vline(nMr-65, self.nlin+8.5, 6.5) + self.vline(nMr-95, self.nlin+8.5, 6.5) + self.vline(nMr-125, self.nlin+8.5, 6.5) + self.vline(nMr-155, self.nlin+8.5, 6.5) + # Labels + self.canvas.setFont('NimbusSanL-Regu', 5) + self.string(self.nLeft+1, self.nlin+3.8, 'BASE DE CÁLCULO DO ICMS') + self.string(nMr-154, self.nlin+3.8, 'VALOR DO ICMS') + self.string(nMr-124, self.nlin+3.8, 'BASE DE CÁLCULO DO ICMS ST') + self.string(nMr-94, self.nlin+3.8, 'VALOR DO ICMS ST') + self.string(nMr-64, self.nlin+3.8, 'VALOR APROX TRIBUTOS') + self.string(nMr-34, self.nlin+3.8, 'VALOR TOTAL DOS PRODUTOS') + + self.string(self.nLeft+1, self.nlin+10.2, 'VALOR DO FRETE') + self.string(nMr-154, self.nlin+10.2, 'VALOR DO SEGURO') + self.string(nMr-124, self.nlin+10.2, 'DESCONTO') + self.string(nMr-94, self.nlin+10.2, 'OUTRAS DESP. ACESSÓRIAS') + self.string(nMr-64, self.nlin+10.2, 'VALOR DO IPI') + self.string(nMr-34, self.nlin+10.2, 'VALOR TOTAL DA NOTA') + + # Conteúdo campos + self.canvas.setFont('NimbusSanL-Regu', 8) + self.stringRight( + self.nLeft+34, self.nlin+7.7, + format_number(tagtext(oNode=el_total, cTag='vBC'), precision=2)) + self.stringRight( + self.nLeft+64, self.nlin+7.7, + format_number(tagtext(oNode=el_total, cTag='vICMS'), precision=2)) + self.stringRight( + self.nLeft+94, self.nlin+7.7, + format_number(tagtext(oNode=el_total, cTag='vBCST'), precision=2)) + self.stringRight( + nMr-66, self.nlin+7.7, + format_number(tagtext(oNode=el_total, cTag='vST'), precision=2)) + self.stringRight( + nMr-36, self.nlin+7.7, + format_number(tagtext(oNode=el_total, cTag='vTotTrib'), + precision=2)) + self.stringRight( + nMr-1, self.nlin+7.7, + format_number(tagtext(oNode=el_total, cTag='vProd'), precision=2)) + self.stringRight( + self.nLeft+34, self.nlin+14.1, + format_number(tagtext(oNode=el_total, cTag='vFrete'), precision=2)) + self.stringRight( + self.nLeft+64, self.nlin+14.1, + format_number(tagtext(oNode=el_total, cTag='vSeg'), precision=2)) + self.stringRight( + self.nLeft+94, self.nlin+14.1, + format_number(tagtext(oNode=el_total, cTag='vDesc'), precision=2)) + self.stringRight( + self.nLeft+124, self.nlin+14.1, + format_number(tagtext(oNode=el_total, cTag='vOutro'), precision=2)) + self.stringRight( + self.nLeft+154, self.nlin+14.1, + format_number(tagtext(oNode=el_total, cTag='vIPI'), precision=2)) + self.stringRight( + nMr-1, self.nlin+14.1, + format_number(tagtext(oNode=el_total, cTag='vNF'), precision=2)) + + self.nlin += 17 # Nr linhas ocupadas pelo bloco + + def transportes(self, oXML=None): + el_transp = oXML.find(".//{http://www.portalfiscal.inf.br/nfe}transp") + nMr = self.width-self.nRight + + self.canvas.setFont('NimbusSanL-Bold', 7) + self.string(self.nLeft+1, self.nlin+1, + 'TRANSPORTADOR/VOLUMES TRANSPORTADOS') + self.canvas.setFont('NimbusSanL-Regu', 5) + self.rect(self.nLeft, self.nlin+2, + self.width-self.nLeft-self.nRight, 20) + self.hline(self.nLeft, self.nlin+8.6, self.width-self.nLeft) + self.hline(self.nLeft, self.nlin+15.2, self.width-self.nLeft) + self.vline(nMr-40, self.nlin+2, 13.2) + self.vline(nMr-49, self.nlin+2, 20) + self.vline(nMr-92, self.nlin+2, 6.6) + self.vline(nMr-120, self.nlin+2, 6.6) + self.vline(nMr-75, self.nlin+2, 6.6) + self.vline(nMr-26, self.nlin+15.2, 6.6) + self.vline(nMr-102, self.nlin+8.6, 6.6) + self.vline(nMr-85, self.nlin+15.2, 6.6) + self.vline(nMr-121, self.nlin+15.2, 6.6) + self.vline(nMr-160, self.nlin+15.2, 6.6) + # Labels/Fields + self.string(nMr-39, self.nlin+3.8, 'CNPJ/CPF') + self.string(nMr-74, self.nlin+3.8, 'PLACA DO VEÍCULO') + self.string(nMr-91, self.nlin+3.8, 'CÓDIGO ANTT') + self.string(nMr-119, self.nlin+3.8, 'FRETE POR CONTA') + self.string(self.nLeft+1, self.nlin+3.8, 'RAZÃO SOCIAL') + self.string(nMr-48, self.nlin+3.8, 'UF') + self.string(nMr-39, self.nlin+10.3, 'INSCRIÇÃO ESTADUAL') + self.string(nMr-48, self.nlin+10.3, 'UF') + self.string(nMr-101, self.nlin+10.3, 'MUNICÍPIO') + self.string(self.nLeft+1, self.nlin+10.3, 'ENDEREÇO') + self.string(nMr-48, self.nlin+17, 'PESO BRUTO') + self.string(nMr-25, self.nlin+17, 'PESO LÍQUIDO') + self.string(nMr-84, self.nlin+17, 'NUMERAÇÃO') + self.string(nMr-120, self.nlin+17, 'MARCA') + self.string(nMr-159, self.nlin+17, 'ESPÉCIE') + self.string(self.nLeft+1, self.nlin+17, 'QUANTIDADE') + # Conteúdo campos + self.canvas.setFont('NimbusSanL-Regu', 8) + self.string(self.nLeft+1, self.nlin+7.7, + tagtext(oNode=el_transp, cTag='xNome')[:40]) + self.string(self.nLeft+71, self.nlin+7.7, + self.oFrete[tagtext(oNode=el_transp, cTag='modFrete')]) + self.string(nMr-39, self.nlin+7.7, + format_cnpj_cpf(tagtext(oNode=el_transp, cTag='CNPJ'))) + self.string(self.nLeft+1, self.nlin+14.2, + tagtext(oNode=el_transp, cTag='xEnder')[:45]) + self.string(self.nLeft+89, self.nlin+14.2, + tagtext(oNode=el_transp, cTag='xMun')) + self.string(nMr-48, self.nlin+14.2, + tagtext(oNode=el_transp, cTag='UF')) + self.string(nMr-39, self.nlin+14.2, + tagtext(oNode=el_transp, cTag='IE')) + self.string(self.nLeft+1, self.nlin+21.2, + tagtext(oNode=el_transp, cTag='qVol')) + self.string(self.nLeft+31, self.nlin+21.2, + tagtext(oNode=el_transp, cTag='esp')) + self.string(self.nLeft+70, self.nlin+21.2, + tagtext(oNode=el_transp, cTag='marca')) + self.string(self.nLeft+106, self.nlin+21.2, + tagtext(oNode=el_transp, cTag='nVol')) + self.stringRight( + nMr-27, self.nlin+21.2, + format_number(tagtext(oNode=el_transp, cTag='pesoB'), precision=3)) + self.stringRight( + nMr-1, self.nlin+21.2, + format_number(tagtext(oNode=el_transp, cTag='pesoL'), precision=3)) + + self.nlin += 23 + + def produtos(self, oXML=None, el_det=None, oPaginator=None, + list_desc=None, list_cod_prod=None, nHeight=29): + + nMr = self.width-self.nRight + nStep = 2.5 # Passo entre linhas + nH = 7.5 + (nHeight * nStep) # cabeçalho 7.5 + self.nlin += 1 + + self.canvas.setFont('NimbusSanL-Bold', 7) + self.string(self.nLeft+1, self.nlin+1, 'DADOS DO PRODUTO/SERVIÇO') + self.rect(self.nLeft, self.nlin+2, + self.width-self.nLeft-self.nRight, nH) + self.hline(self.nLeft, self.nlin+8, self.width-self.nLeft) + + self.canvas.setFont('NimbusSanL-Regu', 5.5) + # Colunas + self.vline(self.nLeft+15, self.nlin+2, nH) + self.stringcenter(self.nLeft+7.5, self.nlin+5.5, 'CÓDIGO') + self.vline(nMr-7, self.nlin+2, nH) + self.stringcenter(nMr-3.5, self.nlin+4.5, 'ALÍQ') + self.stringcenter(nMr-3.5, self.nlin+6.5, 'IPI') + self.vline(nMr-14, self.nlin+2, nH) + self.stringcenter(nMr-10.5, self.nlin+4.5, 'ALÍQ') + self.stringcenter(nMr-10.5, self.nlin+6.5, 'ICMS') + self.vline(nMr-26, self.nlin+2, nH) + self.stringcenter(nMr-20, self.nlin+5.5, 'VLR. IPI') + self.vline(nMr-38, self.nlin+2, nH) + self.stringcenter(nMr-32, self.nlin+5.5, 'VLR. ICMS') + self.vline(nMr-50, self.nlin+2, nH) + self.stringcenter(nMr-44, self.nlin+5.5, 'BC ICMS') + self.vline(nMr-64, self.nlin+2, nH) + self.stringcenter(nMr-57, self.nlin+5.5, 'VLR TOTAL') + self.vline(nMr-77, self.nlin+2, nH) + self.stringcenter(nMr-70.5, self.nlin+5.5, 'VLR UNIT') + self.vline(nMr-90, self.nlin+2, nH) + self.stringcenter(nMr-83.5, self.nlin+5.5, 'QTD') + self.vline(nMr-96, self.nlin+2, nH) + self.stringcenter(nMr-93, self.nlin+5.5, 'UNID') + self.vline(nMr-102, self.nlin+2, nH) + self.stringcenter(nMr-99, self.nlin+5.5, 'CFOP') + self.vline(nMr-108, self.nlin+2, nH) + self.stringcenter(nMr-105, self.nlin+5.5, 'CST') + self.vline(nMr-117, self.nlin+2, nH) + self.stringcenter(nMr-112.5, self.nlin+5.5, 'NCM/SH') + + nWidth_Prod = nMr-135-self.nLeft-11 + nCol_ = self.nLeft+20 + (nWidth_Prod / 2) + self.stringcenter(nCol_, self.nlin+5.5, 'DESCRIÇÃO DO PRODUTO/SERVIÇO') + + # Conteúdo campos + self.canvas.setFont('NimbusSanL-Regu', 5) + nLin = self.nlin+10.5 + + for id in range(oPaginator[0], oPaginator[1]): + item = el_det[id] + el_prod = item.find(".//{http://www.portalfiscal.inf.br/nfe}prod") + el_imp = item.find( + ".//{http://www.portalfiscal.inf.br/nfe}imposto") + + el_imp_ICMS = el_imp.find( + ".//{http://www.portalfiscal.inf.br/nfe}ICMS") + el_imp_IPI = el_imp.find( + ".//{http://www.portalfiscal.inf.br/nfe}IPI") + + cCST = tagtext(oNode=el_imp_ICMS, cTag='orig') + \ + tagtext(oNode=el_imp_ICMS, cTag='CST') + vBC = tagtext(oNode=el_imp_ICMS, cTag='vBC') + vICMS = tagtext(oNode=el_imp_ICMS, cTag='vICMS') + pICMS = tagtext(oNode=el_imp_ICMS, cTag='pICMS') + + vIPI = tagtext(oNode=el_imp_IPI, cTag='vIPI') + pIPI = tagtext(oNode=el_imp_IPI, cTag='pIPI') + + self.stringcenter(nMr-112.5, nLin, + tagtext(oNode=el_prod, cTag='NCM')) + self.stringcenter(nMr-105, nLin, cCST) + self.stringcenter(nMr-99, nLin, + tagtext(oNode=el_prod, cTag='CFOP')) + self.stringcenter(nMr-93, nLin, + tagtext(oNode=el_prod, cTag='uCom')) + self.stringRight(nMr-77.5, nLin, format_number( + tagtext(oNode=el_prod, cTag='qCom'), precision=4)) + self.stringRight(nMr-64.5, nLin, format_number( + tagtext(oNode=el_prod, cTag='vUnCom'), precision=2)) + self.stringRight(nMr-50.5, nLin, format_number( + tagtext(oNode=el_prod, cTag='vProd'), precision=2)) + self.stringRight(nMr-38.5, nLin, format_number(vBC, precision=2)) + self.stringRight(nMr-26.5, nLin, format_number(vICMS, precision=2)) + self.stringRight(nMr-7.5, nLin, format_number(pICMS, precision=2)) + + if vIPI: + self.stringRight(nMr-14.5, nLin, + format_number(vIPI, precision=2)) + if pIPI: + self.stringRight(nMr-0.5, nLin, + format_number(pIPI, precision=2)) + + # Código Item + line_cod = nLin + for des in list_cod_prod[id]: + self.string(self.nLeft+0.2, line_cod, des) + line_cod += nStep + + # Descrição Item + line_desc = nLin + for des in list_desc[id]: + self.string(self.nLeft+15.5, line_desc, des) + line_desc += nStep + + nLin = max(line_cod, line_desc) + self.canvas.setStrokeColor(gray) + self.hline(self.nLeft, nLin-2, self.width-self.nLeft) + self.canvas.setStrokeColor(black) + + self.nlin += nH + 3 + + def adicionais(self, oXML=None): + el_infAdic = oXML.find( + ".//{http://www.portalfiscal.inf.br/nfe}infAdic") + + self.nlin += 2 + self.canvas.setFont('NimbusSanL-Bold', 6) + self.string(self.nLeft+1, self.nlin+1, 'DADOS ADICIONAIS') + self.canvas.setFont('NimbusSanL-Regu', 5) + self.string(self.nLeft+1, self.nlin+4, 'INFORMAÇÕES COMPLEMENTARES') + self.string((self.width/2)+1, self.nlin+4, 'RESERVADO AO FISCO') + self.rect(self.nLeft, self.nlin+2, + self.width-self.nLeft-self.nRight, 42) + self.vline(self.width/2, self.nlin+2, 42) + # Conteúdo campos + styles = getSampleStyleSheet() + styleN = styles['Normal'] + styleN.fontSize = 6 + styleN.fontName = 'NimbusSanL-Regu' + styleN.leading = 7 + + fisco = tagtext(oNode=el_infAdic, cTag='infAdFisco') + observacoes = tagtext(oNode=el_infAdic, cTag='infCpl') + if fisco: + observacoes = fisco + ' ' + observacoes + P = Paragraph(observacoes, styles['Normal']) + w, h = P.wrap(92*mm, 32*mm) + altura = (self.height-self.nlin-5)*mm + P.drawOn(self.canvas, (self.nLeft+1)*mm, altura - h) + self.nlin += 36 + + def recibo_entrega(self, oXML=None): + el_ide = oXML.find(".//{http://www.portalfiscal.inf.br/nfe}ide") + el_dest = oXML.find(".//{http://www.portalfiscal.inf.br/nfe}dest") + el_total = oXML.find(".//{http://www.portalfiscal.inf.br/nfe}total") + el_emit = oXML.find(".//{http://www.portalfiscal.inf.br/nfe}emit") + + # self.nlin = self.height-self.nBottom-18 # 17 altura recibo + nW = 40 + nH = 17 + self.canvas.setLineWidth(.5) + self.rect(self.nLeft, self.nlin, + self.width-(self.nLeft+self.nRight), nH) + self.hline(self.nLeft, self.nlin+8.5, self.width-self.nRight-nW) + self.vline(self.width-self.nRight-nW, self.nlin, nH) + self.vline(self.nLeft+nW, self.nlin+8.5, 8.5) + + # Labels + self.canvas.setFont('NimbusSanL-Regu', 5) + self.string(self.nLeft+1, self.nlin+10.2, 'DATA DE RECEBIMENTO') + self.string(self.nLeft+41, self.nlin+10.2, + 'IDENTIFICAÇÃO E ASSINATURA DO RECEBEDOR') + self.stringcenter(self.width-self.nRight-(nW/2), self.nlin+2, 'NF-e') + # Conteúdo campos + self.canvas.setFont('NimbusSanL-Bold', 8) + cNF = tagtext(oNode=el_ide, cTag='nNF') + cNF = '{0:011,}'.format(int(cNF)).replace(",", ".") + self.string(self.width-self.nRight-nW+2, self.nlin+8, "Nº %s" % (cNF)) + self.string(self.width-self.nRight-nW+2, self.nlin+14, + "SÉRIE %s" % (tagtext(oNode=el_ide, cTag='serie'))) + + cDt, cHr = getdateUTC(tagtext(oNode=el_ide, cTag='dhEmi')) + cTotal = format_number(tagtext(oNode=el_total, cTag='vNF'), + precision=2) + + cEnd = tagtext(oNode=el_dest, cTag='xNome') + ' - ' + cEnd += tagtext(oNode=el_dest, cTag='xLgr') + ', ' + tagtext( + oNode=el_dest, cTag='nro') + ', ' + cEnd += tagtext(oNode=el_dest, cTag='xBairro') + ', ' + tagtext( + oNode=el_dest, cTag='xMun') + ' - ' + cEnd += tagtext(oNode=el_dest, cTag='UF') + + cString = """ + RECEBEMOS DE %s OS PRODUTOS/SERVIÇOS CONSTANTES DA NOTA FISCAL INDICADA + ABAIXO. EMISSÃO: %s VALOR TOTAL: %s + DESTINATARIO: %s""" % (tagtext(oNode=el_emit, cTag='xNome'), + cDt, cTotal, cEnd) + + styles = getSampleStyleSheet() + styleN = styles['Normal'] + styleN.fontName = 'NimbusSanL-Regu' + styleN.fontSize = 6 + styleN.leading = 7 + + P = Paragraph(cString, styleN) + w, h = P.wrap(149*mm, 7*mm) + P.drawOn(self.canvas, (self.nLeft+1)*mm, + ((self.height-self.nlin)*mm) - h) + + self.nlin += 20 + self.hline(self.nLeft, self.nlin, self.width-self.nRight) + self.nlin += 2 + + def newpage(self): + self.nlin = self.nTop + self.Page += 1 + self.canvas.showPage() + + def hline(self, x, y, width): + y = self.height - y + self.canvas.line(x*mm, y*mm, width*mm, y*mm) + + def vline(self, x, y, width): + width = self.height - y - width + y = self.height - y + self.canvas.line(x*mm, y*mm, x*mm, width*mm) + + def rect(self, col, lin, nWidth, nHeight, fill=False): + lin = self.height - nHeight - lin + self.canvas.rect(col*mm, lin*mm, nWidth*mm, nHeight*mm, + stroke=True, fill=fill) + + def string(self, x, y, value): + y = self.height - y + self.canvas.drawString(x*mm, y*mm, value) + + def stringRight(self, x, y, value): + y = self.height - y + self.canvas.drawRightString(x*mm, y*mm, value) + + def stringcenter(self, x, y, value): + y = self.height - y + self.canvas.drawCentredString(x*mm, y*mm, value) + + def writeto_pdf(self, fileObj): + pdf_out = self.oPDF_IO.getvalue() + self.oPDF_IO.close() + fileObj.write(pdf_out) diff --git a/pytrustnfe/nfse/assinatura.py b/pytrustnfe/nfse/assinatura.py index b2a8d28..0c65cee 100644 --- a/pytrustnfe/nfse/assinatura.py +++ b/pytrustnfe/nfse/assinatura.py @@ -2,86 +2,52 @@ # © 2016 Danimar Ribeiro, Trustcode # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from lxml import etree import xmlsec -import libxml2 import os.path +consts = xmlsec.constants + NAMESPACE_SIG = 'http://www.w3.org/2000/09/xmldsig#' class Assinatura(object): - def __init__(self, arquivo, senha): - self.arquivo = arquivo - self.senha = senha + def __init__(self, cert_pem, private_key, password): + self.cert_pem = cert_pem + self.private_key = private_key + self.password = password def _checar_certificado(self): - if not os.path.isfile(self.arquivo): + if not os.path.isfile(self.private_key): raise Exception('Caminho do certificado não existe.') - def _inicializar_cripto(self): - libxml2.initParser() - libxml2.substituteEntitiesDefault(1) - - xmlsec.init() - xmlsec.cryptoAppInit(None) - xmlsec.cryptoInit() - - def _finalizar_cripto(self): - xmlsec.cryptoShutdown() - xmlsec.cryptoAppShutdown() - xmlsec.shutdown() - - libxml2.cleanupParser() - def assina_xml(self, xml, reference): self._checar_certificado() - self._inicializar_cripto() - try: - doc_xml = libxml2.parseMemory( - xml, len(xml)) - - signNode = xmlsec.TmplSignature(doc_xml, - xmlsec.transformInclC14NId(), - xmlsec.transformRsaSha1Id(), None) - - doc_xml.getRootElement().addChild(signNode) - refNode = signNode.addReference(xmlsec.transformSha1Id(), - None, reference, None) + template = etree.fromstring(xml) - refNode.addTransform(xmlsec.transformEnvelopedId()) - refNode.addTransform(xmlsec.transformInclC14NId()) - keyInfoNode = signNode.ensureKeyInfo() - keyInfoNode.addX509Data() + key = xmlsec.Key.from_file( + self.private_key, format=xmlsec.constants.KeyDataFormatPem, + password=self.password) - dsig_ctx = xmlsec.DSigCtx() - chave = xmlsec.cryptoAppKeyLoad(filename=str(self.arquivo), - format=xmlsec.KeyDataFormatPkcs12, - pwd=str(self.senha), - pwdCallback=None, - pwdCallbackCtx=None) + signature_node = xmlsec.template.create( + template, c14n_method=consts.TransformInclC14N, + sign_method=consts.TransformRsaSha1) + template.append(signature_node) + ref = xmlsec.template.add_reference( + signature_node, consts.TransformSha1, uri='') - dsig_ctx.signKey = chave - dsig_ctx.sign(signNode) + xmlsec.template.add_transform(ref, consts.TransformEnveloped) + xmlsec.template.add_transform(ref, consts.TransformInclC14N) - status = dsig_ctx.status - dsig_ctx.destroy() + ki = xmlsec.template.ensure_key_info(signature_node) + xmlsec.template.add_x509_data(ki) - if status != xmlsec.DSigStatusSucceeded: - raise RuntimeError( - 'Erro ao realizar a assinatura do arquivo; status: "' + - str(status) + - '"') + ctx = xmlsec.SignatureContext() + ctx.key = key - xpath = doc_xml.xpathNewContext() - xpath.xpathRegisterNs('sig', NAMESPACE_SIG) - certificados = xpath.xpathEval( - '//sig:X509Data/sig:X509Certificate') - for i in range(len(certificados) - 1): - certificados[i].unlinkNode() - certificados[i].freeNode() + ctx.key.load_cert_from_file( + self.cert_pem, consts.KeyDataFormatPem) - xml = doc_xml.serialize() - return xml - finally: - doc_xml.freeDoc() + ctx.sign(signature_node) + return etree.tostring(template, encoding=str) diff --git a/pytrustnfe/nfse/betha/__init__.py b/pytrustnfe/nfse/betha/__init__.py index f2517fa..67ae42d 100644 --- a/pytrustnfe/nfse/betha/__init__.py +++ b/pytrustnfe/nfse/betha/__init__.py @@ -50,7 +50,7 @@ def _send(certificado, method, **kwargs): try: response = getattr(client.service, method)(1, xml_send) - except suds.WebFault, e: + except suds.WebFault as e: return { 'sent_xml': xml_send, 'received_xml': e.fault.faultstring, diff --git a/pytrustnfe/nfse/ginfes/__init__.py b/pytrustnfe/nfse/ginfes/__init__.py index f2655dc..e662f2f 100644 --- a/pytrustnfe/nfse/ginfes/__init__.py +++ b/pytrustnfe/nfse/ginfes/__init__.py @@ -39,7 +39,7 @@ def _send(certificado, method, **kwargs): xml_send = kwargs['xml'] header = '3' #noqa response = getattr(client.service, method)(header, xml_send) - except suds.WebFault, e: + except suds.WebFault as e: return { 'sent_xml': xml_send, 'received_xml': e.fault.faultstring, diff --git a/pytrustnfe/nfse/paulistana/__init__.py b/pytrustnfe/nfse/paulistana/__init__.py index 48818e8..ed0611b 100644 --- a/pytrustnfe/nfse/paulistana/__init__.py +++ b/pytrustnfe/nfse/paulistana/__init__.py @@ -18,10 +18,10 @@ def sign_tag(certificado, **kwargs): if 'nfse' in kwargs: for item in kwargs['nfse']['lista_rps']: signed = crypto.sign(key, item['assinatura'], 'SHA1') - item['assinatura'] = b64encode(signed) + item['assinatura'] = b64encode(signed).decode() if 'cancelamento' in kwargs: signed = crypto.sign(key, kwargs['cancelamento']['assinatura'], 'SHA1') - kwargs['cancelamento']['assinatura'] = b64encode(signed) + kwargs['cancelamento']['assinatura'] = b64encode(signed).decode() def _send(certificado, method, **kwargs): @@ -42,13 +42,12 @@ def _send(certificado, method, **kwargs): cert, key = save_cert_key(cert, key) client = get_authenticated_client(base_url, cert, key) - pfx_path = certificado.save_pfx() - signer = Assinatura(pfx_path, certificado.password) + signer = Assinatura(cert, key, certificado.password) xml_send = signer.assina_xml(xml_send, '') try: response = getattr(client.service, method)(1, xml_send) - except suds.WebFault, e: + except suds.WebFault as e: return { 'sent_xml': xml_send, 'received_xml': e.fault.faultstring, diff --git a/pytrustnfe/nfse/susesu/__init__.py b/pytrustnfe/nfse/susesu/__init__.py index 2295bb2..557a168 100644 --- a/pytrustnfe/nfse/susesu/__init__.py +++ b/pytrustnfe/nfse/susesu/__init__.py @@ -29,7 +29,7 @@ def _send(method, **kwargs): 'sent_xml': xml_send, 'received_xml': e.fault.faultstring, } - result = unicode(result) + result = str(result) result = unicodedata.normalize('NFKD', result).encode('ascii', 'ignore') return { 'sent_xml': xml_send, diff --git a/pytrustnfe/test/XMLs/paulistana_canc_errado.xml b/pytrustnfe/test/XMLs/paulistana_canc_errado.xml index fc1aeae..4fd39b4 100644 --- a/pytrustnfe/test/XMLs/paulistana_canc_errado.xml +++ b/pytrustnfe/test/XMLs/paulistana_canc_errado.xml @@ -1,4 +1,3 @@ - true265436451212213329001632016-08-29T10:52:15101.3552382446APR9MJR5128216 +true265436451212213329001632016-08-29T10:52:15101.3552382446APR9MJR5128216 diff --git a/pytrustnfe/test/XMLs/paulistana_signature.xml b/pytrustnfe/test/XMLs/paulistana_signature.xml index 013053e..c4f8cbc 100644 --- a/pytrustnfe/test/XMLs/paulistana_signature.xml +++ b/pytrustnfe/test/XMLs/paulistana_signature.xml @@ -1,8 +1,4 @@ - -12345678901234false2016-08-292016-08-291E4fpHYkQa7Naxn6IKGb7NwwZu5tPk/KXJ9hCwtZgq0xvKS450aQqqBL+7Iv46lTgqrSMu7+gLrl+LC1qs/8aT2mbHE8uaVFSbzwZ+sF/BkcT6nsFHLMswEiTAEs95Jb7hN1cC91xqQGRH4buw0TzxHKmhuLJ22WwtG/scxyKtjM=12345611RPS2016-08-29NT0.000.000.000.000.00074985.00false - - - 123456Trustcode1Vinicius de Moraes, 4242CorregoFloripaSC88037240Venda de servico +12345678901234false2016-08-292016-08-291E4fpHYkQa7Naxn6IKGb7NwwZu5tPk/KXJ9hCwtZgq0xvKS450aQqqBL+7Iv46lTgqrSMu7+gLrl+LC1qs/8aT2mbHE8uaVFSbzwZ+sF/BkcT6nsFHLMswEiTAEs95Jb7hN1cC91xqQGRH4buw0TzxHKmhuLJ22WwtG/scxyKtjM=12345611RPS2016-08-29NT0.000.000.000.000.00074985.00false123456Trustcode1Vinicius de Moraes, 4242CorregoFloripaSC88037240Venda de servico @@ -12,12 +8,12 @@ -ivaOwkcrt0pfuMYsAdfyLaUAcIk= +ePJnD6hyDvlJo08PFX8h2TXk0ZM= -FjIHdfPavSEyaWYhAT0z0shPLuTsqBKyy78PUEZ8PUhTZ+iSV0MOvAIRq9MPPVK9 -jjXOw1TE903uSK8aJon52RNKPd68ORVJ3bKFSjTqQLxFRR9tiiAQFrWDETf7FF89 -EhG6dy6TGcgVbOyn0Jqm8MkqrE1XrJ44orN1X+Jt+7U= +GbaQaTEtxuKdRRaadginWPFH5K65ywqEikkwChWO3xX5Kglq8RPm4+LjnpJmuTcE +9I2BVon3GJFh+c/6RKzJPose6FXog2xnCpTOgwA/rks/gKsUAaRlXCPsLcKMKaOj +3eH21RHEyrxBAbdpEUdlEgQWaWzmGq009EiQ544sD6c= MIICMTCCAZqgAwIBAgIQfYOsIEVuAJ1FwwcTrY0t1DANBgkqhkiG9w0BAQUFADBX @@ -34,4 +30,4 @@ QtgAhuZM9rxpOJuNKc+pM29EixpAiZZiRMCSWEItNyEVdUIi+YnKBcAHd88TwO86 d126MWQ2O8cu5W1VoDp7hYBYKOnLbYi11/StO+0rzK+oPYAvIw== - + \ No newline at end of file diff --git a/pytrustnfe/test/test_add_qr_code.py b/pytrustnfe/test/test_add_qr_code.py index 17965c9..da9e381 100644 --- a/pytrustnfe/test/test_add_qr_code.py +++ b/pytrustnfe/test/test_add_qr_code.py @@ -12,7 +12,7 @@ class TestAddQRCode(unittest.TestCase): self.xml_sem_qrcode = open('pytrustnfe/test/xml_sem_qrcode.xml', 'r') self.xml_com_qrcode = open('pytrustnfe/test/xml_com_qrcode.xml', 'r') dhEmi = '2016-11-09T16:03:25-00:00' - chave_nfe = u'NFe35161121332917000163650010000000011448875034' + chave_nfe = 'NFe35161121332917000163650010000000011448875034' ambiente = 2 valor_total = '324.00' icms_total = '61.56' diff --git a/pytrustnfe/test/test_assinatura.py b/pytrustnfe/test/test_assinatura.py index 380e1fd..f13b851 100644 --- a/pytrustnfe/test/test_assinatura.py +++ b/pytrustnfe/test/test_assinatura.py @@ -11,16 +11,14 @@ from lxml import etree from pytrustnfe.nfe.assinatura import Assinatura -XML_ASSINAR = '' \ - '' \ +XML_ASSINAR = '' \ ' '\ ' Hello, World!' \ ' ' \ '' -XML_ERRADO = '' \ - '' \ +XML_ERRADO = '' \ ' ' \ ' Hello, World!' \ ' ' \ @@ -32,21 +30,21 @@ class test_assinatura(unittest.TestCase): caminho = os.path.dirname(__file__) def test_assinar_xml_senha_invalida(self): - pfx = open(os.path.join(self.caminho, 'teste.pfx')).read() + pfx = open(os.path.join(self.caminho, 'teste.pfx'), 'rb').read() signer = Assinatura(pfx, '123') self.assertRaises(Exception, signer.assina_xml, signer, etree.fromstring(XML_ASSINAR), 'NFe43150602261542000143550010000000761792265342') def test_assinar_xml_invalido(self): - pfx = open(os.path.join(self.caminho, 'teste.pfx')).read() + pfx = open(os.path.join(self.caminho, 'teste.pfx'), 'rb').read() signer = Assinatura(pfx, '123456') self.assertRaises(Exception, signer.assina_xml, signer, etree.fromstring(XML_ERRADO), 'NFe43150602261542000143550010000000761792265342') def test_assinar_xml_valido(self): - pfx = open(os.path.join(self.caminho, 'teste.pfx')).read() + pfx = open(os.path.join(self.caminho, 'teste.pfx'), 'rb').read() signer = Assinatura(pfx, '123456') xml = signer.assina_xml( etree.fromstring(XML_ASSINAR), diff --git a/pytrustnfe/test/test_certificado.py b/pytrustnfe/test/test_certificado.py index 2e7f248..e05a8f0 100644 --- a/pytrustnfe/test/test_certificado.py +++ b/pytrustnfe/test/test_certificado.py @@ -49,21 +49,21 @@ class test_assinatura(unittest.TestCase): caminho = os.path.dirname(__file__) def test_preparar_pfx(self): - dir_pfx = open(os.path.join(self.caminho, 'teste.pfx'), 'r').read() + dir_pfx = open(os.path.join(self.caminho, 'teste.pfx'), 'rb').read() cert, key = extract_cert_and_key_from_pfx(dir_pfx, '123456') self.assertEqual(key, CHAVE, 'Chave gerada inválida') self.assertEqual(cert, CERTIFICADO, 'Certificado inválido') def test_save_pfx(self): - pfx_source = open(os.path.join(self.caminho, 'teste.pfx'), 'r').read() + pfx_source = open(os.path.join(self.caminho, 'teste.pfx'), 'rb').read() pfx = Certificado(pfx_source, '123') path = pfx.save_pfx() - saved = open(path, 'r').read() + saved = open(path, 'rb').read() self.assertEqual(pfx_source, saved, 'Arquivo pfx salvo não bate com arquivo lido') def test_save_cert_and_key(self): - dir_pfx = open(os.path.join(self.caminho, 'teste.pfx'), 'r').read() + dir_pfx = open(os.path.join(self.caminho, 'teste.pfx'), 'rb').read() cert, key = extract_cert_and_key_from_pfx(dir_pfx, '123456') cert_path, key_path = save_cert_key(cert, key) cert_saved = open(cert_path, 'r').read() diff --git a/pytrustnfe/test/test_consulta_cadastro.py b/pytrustnfe/test/test_consulta_cadastro.py index 9c63f03..a284b56 100644 --- a/pytrustnfe/test/test_consulta_cadastro.py +++ b/pytrustnfe/test/test_consulta_cadastro.py @@ -12,7 +12,7 @@ class test_consulta_cadastro(unittest.TestCase): caminho = os.path.dirname(__file__) def test_conta_de_cadastro(self): - pfx_source = open(os.path.join(self.caminho, 'teste.pfx'), 'r').read() + pfx_source = open(os.path.join(self.caminho, 'teste.pfx'), 'rb').read() pfx = Certificado(pfx_source, '123456') obj = {'cnpj': '12345678901234', 'estado': '42'} diff --git a/pytrustnfe/test/test_danfe.py b/pytrustnfe/test/test_danfe.py index e7385c7..a854858 100644 --- a/pytrustnfe/test/test_danfe.py +++ b/pytrustnfe/test/test_danfe.py @@ -22,5 +22,5 @@ class test_danfe(unittest.TestCase): # Para testar localmente o Danfe # with open('/home/danimar/danfe.pdf', 'w') as oFile: - with tempfile.TemporaryFile(mode='w') as oFile: + with tempfile.TemporaryFile(mode='wb') as oFile: oDanfe.writeto_pdf(oFile) diff --git a/pytrustnfe/test/test_ginfes.py b/pytrustnfe/test/test_ginfes.py index d2895f2..6d23e5e 100644 --- a/pytrustnfe/test/test_ginfes.py +++ b/pytrustnfe/test/test_ginfes.py @@ -13,7 +13,7 @@ class test_nfse_ginfes(unittest.TestCase): @unittest.skip def test_consulta_situacao_lote(self): - pfx_source = open('/home/danimar/Downloads/machado.pfx', 'r').read() + pfx_source = open('/home/danimar/Downloads/machado.pfx', 'rb').read() pfx = Certificado(pfx_source, '123456789') dados = {'ambiente': 'homologacao'} diff --git a/pytrustnfe/test/test_nfse_paulistana.py b/pytrustnfe/test/test_nfse_paulistana.py index 0ed4ded..06d4200 100644 --- a/pytrustnfe/test/test_nfse_paulistana.py +++ b/pytrustnfe/test/test_nfse_paulistana.py @@ -54,7 +54,7 @@ class test_nfse_paulistana(unittest.TestCase): return nfse def test_envio_nfse(self): - pfx_source = open(os.path.join(self.caminho, 'teste.pfx'), 'r').read() + pfx_source = open(os.path.join(self.caminho, 'teste.pfx'), 'rb').read() pfx = Certificado(pfx_source, '123456') nfse = self._get_nfse() @@ -77,7 +77,7 @@ class test_nfse_paulistana(unittest.TestCase): retorno['object'].ChaveNFeRPS.ChaveRPS.NumeroRPS, 6) def test_nfse_signature(self): - pfx_source = open(os.path.join(self.caminho, 'teste.pfx'), 'r').read() + pfx_source = open(os.path.join(self.caminho, 'teste.pfx'), 'rb').read() pfx = Certificado(pfx_source, '123456') nfse = self._get_nfse() @@ -103,7 +103,7 @@ class test_nfse_paulistana(unittest.TestCase): } def test_cancelamento_nfse_ok(self): - pfx_source = open(os.path.join(self.caminho, 'teste.pfx'), 'r').read() + pfx_source = open(os.path.join(self.caminho, 'teste.pfx'), 'rb').read() pfx = Certificado(pfx_source, '123456') cancelamento = self._get_cancelamento() @@ -122,7 +122,7 @@ class test_nfse_paulistana(unittest.TestCase): self.assertEqual(retorno['object'].Cabecalho.Sucesso, True) def test_cancelamento_nfse_com_erro(self): - pfx_source = open(os.path.join(self.caminho, 'teste.pfx'), 'r').read() + pfx_source = open(os.path.join(self.caminho, 'teste.pfx'), 'rb').read() pfx = Certificado(pfx_source, '123456') cancelamento = self._get_cancelamento() diff --git a/pytrustnfe/test/test_utils.py b/pytrustnfe/test/test_utils.py index 64cf1fa..247327d 100644 --- a/pytrustnfe/test/test_utils.py +++ b/pytrustnfe/test/test_utils.py @@ -60,7 +60,7 @@ class test_utils(unittest.TestCase): chave.validar() chave.cnpj = '1234567891011' self.assertEqual('CNPJ necessário para criar chave NF-e', - cm.exception.message, + str(cm.exception), 'Validação da chave nf-e incorreta') with self.assertRaises(AssertionError) as cm: @@ -68,7 +68,7 @@ class test_utils(unittest.TestCase): chave.validar() chave.estado = '42' self.assertEqual('Estado necessário para criar chave NF-e', - cm.exception.message, + str(cm.exception), 'Validação da chave nf-e incorreta') with self.assertRaises(AssertionError) as cm: @@ -76,7 +76,7 @@ class test_utils(unittest.TestCase): chave.validar() chave.emissao = '0' self.assertEqual('Emissão necessário para criar chave NF-e', - cm.exception.message, + str(cm.exception), 'Validação da chave nf-e incorreta') with self.assertRaises(AssertionError) as cm: @@ -84,7 +84,7 @@ class test_utils(unittest.TestCase): chave.validar() chave.modelo = '55' self.assertEqual('Modelo necessário para criar chave NF-e', - cm.exception.message, + str(cm.exception), 'Validação da chave nf-e incorreta') with self.assertRaises(AssertionError) as cm: @@ -92,7 +92,7 @@ class test_utils(unittest.TestCase): chave.validar() chave.serie = '012' self.assertEqual('Série necessária para criar chave NF-e', - cm.exception.message, + str(cm.exception), 'Validação da chave nf-e incorreta') with self.assertRaises(AssertionError) as cm: @@ -100,7 +100,7 @@ class test_utils(unittest.TestCase): chave.validar() chave.numero = '000000780' self.assertEqual('Número necessário para criar chave NF-e', - cm.exception.message, + str(cm.exception), 'Validação da chave nf-e incorreta') with self.assertRaises(AssertionError) as cm: @@ -108,12 +108,12 @@ class test_utils(unittest.TestCase): chave.validar() chave.tipo = '42' self.assertEqual('Tipo necessário para criar chave NF-e', - cm.exception.message, + str(cm.exception), 'Validação da chave nf-e incorreta') with self.assertRaises(AssertionError) as cm: chave.codigo = '' chave.validar() self.assertEqual('Código necessário para criar chave NF-e', - cm.exception.message, + str(cm.exception), 'Validação da chave nf-e incorreta') diff --git a/pytrustnfe/test/test_xml.py b/pytrustnfe/test/test_xml.py index ad5162f..45b70b7 100644 --- a/pytrustnfe/test/test_xml.py +++ b/pytrustnfe/test/test_xml.py @@ -26,5 +26,5 @@ class test_xmlfilters(unittest.TestCase): self.assertEqual('2016-09-17', format_date(dt.date())) self.assertEqual('2016-09-17T12:12:12', format_datetime(dt)) - word = strip_line_feed(u"olá\ncomo vai\r senhor ") - self.assertEqual(word, u"olá como vai senhor") + word = strip_line_feed("olá\ncomo vai\r senhor ") + self.assertEqual(word, "olá como vai senhor") diff --git a/pytrustnfe/test/test_xml_serializacao.py b/pytrustnfe/test/test_xml_serializacao.py index 64d0a2a..82f3dbc 100644 --- a/pytrustnfe/test/test_xml_serializacao.py +++ b/pytrustnfe/test/test_xml_serializacao.py @@ -15,13 +15,13 @@ class test_xml_serializacao(unittest.TestCase): tag2='ola', tag3='comovai') result = open(os.path.join(path, 'jinja_result.xml'), 'r').read() - self.assertEqual(xml + '\n', result) + self.assertEqual(xml + "\n", result) def test_serializacao_remove_empty(self): path = os.path.join(os.path.dirname(__file__), 'XMLs') xmlElem = render_xml(path, 'jinja_template.xml', True, tag1='oi', tag2='ola', tag3='comovai') - xml = etree.tostring(xmlElem) + xml = etree.tostring(xmlElem, encoding=str) result = open(os.path.join(path, 'jinja_remove_empty.xml'), 'r').read() self.assertEqual(xml + '\n', result) @@ -29,7 +29,7 @@ class test_xml_serializacao(unittest.TestCase): path = os.path.join(os.path.dirname(__file__), 'XMLs') xml_to_clear = open(os.path.join(path, 'jinja_result.xml'), 'r').read() xml, obj = sanitize_response(xml_to_clear) - + print(type(xml)) self.assertEqual(xml, xml_to_clear) self.assertEqual(obj.tpAmb, 'oi') self.assertEqual(obj.CNPJ, 'ola') diff --git a/pytrustnfe/test/xml_com_qrcode.xml b/pytrustnfe/test/xml_com_qrcode.xml index 5731c5f..22cf1ef 100644 --- a/pytrustnfe/test/xml_com_qrcode.xml +++ b/pytrustnfe/test/xml_com_qrcode.xml @@ -30,11 +30,11 @@ LEL AMBIENTAL LTDA - EPP Zell Ambiental - Rua Padre João + Rua Padre João 444 - Penha de França + Penha de França 3550308 - São Paulo + São Paulo SP 03637000 1058 diff --git a/pytrustnfe/test/xml_sem_qrcode.xml b/pytrustnfe/test/xml_sem_qrcode.xml index 74b8c78..3352a22 100644 --- a/pytrustnfe/test/xml_sem_qrcode.xml +++ b/pytrustnfe/test/xml_sem_qrcode.xml @@ -31,11 +31,11 @@ LEL AMBIENTAL LTDA - EPP Zell Ambiental - Rua Padre João + Rua Padre João 444 - Penha de França + Penha de França 3550308 - São Paulo + São Paulo SP 03637000 1058 diff --git a/pytrustnfe/xml/__init__.py b/pytrustnfe/xml/__init__.py index d3eeeea..3bc6321 100644 --- a/pytrustnfe/xml/__init__.py +++ b/pytrustnfe/xml/__init__.py @@ -39,14 +39,14 @@ def render_xml(path, template_name, remove_empty, **nfe): if recursively_empty(elem): parent.remove(elem) return root - return etree.tostring(root) + for element in root.iter("*"): # remove espaços em branco + if element.text is not None and not element.text.strip(): + element.text = None + return etree.tostring(root, encoding=str) def sanitize_response(response): - response = unicode(response) - response = unicodedata.normalize('NFKD', response).encode('ascii', - 'ignore') - + print(response) tree = etree.fromstring(response) # Remove namespaces inuteis na resposta for elem in tree.getiterator(): diff --git a/pytrustnfe/xml/filters.py b/pytrustnfe/xml/filters.py index a9ed689..afd19ec 100644 --- a/pytrustnfe/xml/filters.py +++ b/pytrustnfe/xml/filters.py @@ -13,24 +13,24 @@ def normalize_str(string): Remove special characters and strip spaces """ if string: - if not isinstance(string, unicode): - string = unicode(string, 'utf-8', 'replace') + if not isinstance(string, str): + string = str(string, 'utf-8', 'replace') string = string.encode('utf-8') return normalize( - 'NFKD', string.decode('utf-8')).encode('ASCII', 'ignore') + 'NFKD', string.decode('utf-8')).encode('ASCII', 'ignore').decode() return '' def strip_line_feed(string): if string: - if not isinstance(string, unicode): - string = unicode(string, 'utf-8', 'replace') + if not isinstance(string, str): + string = str(string, 'utf-8', 'replace') remap = { - ord(u'\t'): u' ', - ord(u'\n'): u' ', - ord(u'\f'): u' ', - ord(u'\r'): None, # Delete + ord('\t'): ' ', + ord('\n'): ' ', + ord('\f'): ' ', + ord('\r'): None, # Delete } return string.translate(remap).strip() return string diff --git a/requirements.txt b/requirements.txt index 2683ae6..886103b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,6 +2,7 @@ lxml >= 3.5.0, < 4 coveralls Jinja2 signxml +urllib3 >= 1.22 suds-jurko >= 0.6 suds-jurko-requests >= 1.0 defusedxml >= 0.4.1, < 0.6 @@ -9,6 +10,7 @@ eight >= 0.3.0, < 0.5 cryptography >= 1.8, < 1.10 pyOpenSSL >= 16.0.0, < 17 certifi >= 2015.11.20.1 +xmlsec >= 1.3.3 reportlab pytest pytest-cov