diff --git a/pytrustnfe/nfe/danfce.py b/pytrustnfe/nfe/danfce.py
new file mode 100644
index 0000000..21cd50b
--- /dev/null
+++ b/pytrustnfe/nfe/danfce.py
@@ -0,0 +1,456 @@
+# -*- coding: utf-8 -*-
+# © 2017 Johny Chen Jy, Trustcode
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
+
+from textwrap import wrap
+from io import BytesIO
+
+from reportlab.lib import utils
+from reportlab.pdfgen import canvas
+from reportlab.lib.units import cm, mm
+from reportlab.graphics.barcode import qr
+from reportlab.graphics import renderPDF
+from reportlab.graphics.shapes import Drawing
+from reportlab.platypus import Table, TableStyle, Paragraph, Image
+from reportlab.lib.enums import TA_CENTER
+from reportlab.lib.styles import ParagraphStyle
+
+
+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
+
+
+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))
+
+
+def format_telefone(numero_telefone):
+ if len(numero_telefone) == 10:
+ telefone = '(%s) %s-%s' % (numero_telefone[0:2],
+ numero_telefone[2:6],
+ numero_telefone[6:])
+ else:
+ telefone = '(%s) %s-%s' % (numero_telefone[0:2],
+ numero_telefone[2:7],
+ numero_telefone[7:])
+ return telefone
+
+
+class danfce(object):
+
+ def __init__(self, list_xml, logo=None):
+
+ self.current_font_size = 7
+ self.current_font_name = 'NimbusSanL-Regu'
+
+ self.max_height = 840
+ self.min_height = 1
+ self.min_width = 5
+ self.max_width = 200
+ self.current_height = 840
+
+ self.oPDF_IO = BytesIO()
+ self.canvas = canvas.Canvas(self.oPDF_IO, pagesize=(7.2 * cm, 30 * cm))
+ self.canvas.setTitle('DANFCE')
+ self.canvas.setLineWidth(.5)
+ self.canvas.setFont(self.current_font_name, self.current_font_size)
+
+ self.list_xml = list_xml
+ self.logo = logo
+
+ self.nfce_generate()
+
+ def ide_emit(self, oXML=None):
+
+ elem_emit = oXML.find(".//{http://www.portalfiscal.inf.br/nfe}emit")
+
+ # Razão Social emitente
+ nomeEmpresa = tagtext(oNode=elem_emit, cTag='xFant')
+ self.drawTitle(nomeEmpresa, 10)
+
+ if self.logo:
+ img = get_image(self.logo, width=10 * mm)
+ img.drawOn(self.canvas, 5, 830)
+
+ cEnd = tagtext(oNode=elem_emit, cTag="xNome") + '
'
+ cEnd += "CNPJ: %s " % (format_cnpj_cpf(
+ tagtext(oNode=elem_emit, cTag='CNPJ')))
+ cEnd += "IE: %s" % (tagtext(oNode=elem_emit, cTag="IE")) + '
'
+ 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 += tagtext(oNode=elem_emit, cTag='UF') + ' - ' + tagtext(
+ oNode=elem_emit, cTag='CEP') + '
'
+ cEnd += 'Fone: ' + format_telefone(tagtext(
+ oNode=elem_emit, cTag='fone'))
+
+ self._drawCenteredParagraph(cEnd)
+ self.drawLine()
+
+ def danfce_information(self):
+ self.drawTitle(
+ "DANFE NFC-e - Documento Auxiliar da Nota Fiscal de",
+ 7, 'NimbusSanL-Bold')
+
+ self.drawTitle("Consumidor Eletrônica", 7, 'NimbusSanL-Bold')
+
+ self.drawString(
+ "NFC-e não permite aproveitamento de crédito de ICMS", True)
+ self.drawLine()
+
+ def produtos(self, oXML=None, el_det=None, oPaginator=None,
+ list_desc=None, list_cod_prod=None):
+
+ rows = [['Codigo', 'Descricao', 'Qtde', 'Un', 'Vl Unit', 'Vl Total']]
+ colWidths = (25, 90, 15, 15, 25, 25)
+ rowHeights = [7]
+
+ for id in range(oPaginator[0], oPaginator[1]):
+
+ item = el_det[id]
+ el_prod = item.find(".//{http://www.portalfiscal.inf.br/nfe}prod")
+
+ cod = tagtext(oNode=el_prod, cTag='cProd')
+ descricao = tagtext(oNode=el_prod, cTag='xProd')
+ Un = tagtext(oNode=el_prod, cTag='uCom')
+ qtde = format_number(tagtext(oNode=el_prod, cTag='qCom'),
+ precision=2)
+ vl_unit = format_number(tagtext(oNode=el_prod, cTag='vUnCom'),
+ precision=2)
+ vl_total = format_number(
+ tagtext(oNode=el_prod, cTag='vProd'), precision=2)
+
+ new_row = [cod, descricao, qtde, Un, vl_unit, vl_total]
+
+ rows.append(new_row)
+ rowHeights.append(self.current_font_size + 2)
+
+ self._draw_product_table(rows, colWidths, rowHeights)
+
+ def _draw_product_table(self, rows, colWidths, rowHeights):
+ table = Table(rows, colWidths, tuple(rowHeights))
+ table.setStyle(TableStyle([
+ ('FONTSIZE', (0, 0), (-1, -1), 7),
+ ('FONT', (0, 1), (-1, -1), 'NimbusSanL-Regu'),
+ ('FONT', (0, 0), (-1, 0), 'NimbusSanL-Bold'),
+ ('ALIGN', (0, 0), (-1, 0), "LEFT"),
+ ('ALIGN', (1, 0), (-1, 0), "LEFT"),
+ ('ALIGN', (2, 0), (-1, 0), "CENTER"),
+ ('ALIGN', (3, 0), (-1, 0), "CENTER"),
+ ('ALIGN', (0, 1), (-1, -1), "LEFT"),
+ ('ALIGN', (1, 1), (-1, -1), "LEFT"),
+ ('ALIGN', (2, 1), (-1, -1), "CENTER"),
+ ('ALIGN', (3, 1), (-1, -1), "CENTER"),
+ ]))
+
+ w, h = table.wrapOn(self.canvas, 200, 450)
+ table.drawOn(self.canvas, 0, self.current_height - (h * 1.2))
+ self.current_height -= (h * 1.1)
+
+ def totais(self, oXML=None):
+ # Impostos
+ el_total = oXML.find(".//{http://www.portalfiscal.inf.br/nfe}total")
+
+ total_tributo = format_number(tagtext(oNode=el_total, cTag='vTotTrib'),
+ precision=2)
+ valor_total = format_number(tagtext(oNode=el_total, cTag='vProd'),
+ precision=2)
+ desconto = format_number(tagtext(oNode=el_total, cTag='vDesc'),
+ precision=2)
+ valor_a_pagar = format_number(tagtext(oNode=el_total, cTag='vNF'),
+ precision=2)
+ el_pag = oXML.find(".//{http://www.portalfiscal.inf.br/nfe}pag")
+ troco = format_number(tagtext(oNode=el_pag, cTag="vTroco"))
+
+ payment_method_list = {'01': 'Dinheiro',
+ '02': 'Cheque',
+ '03': 'Cartão de Crédito',
+ '04': 'Cartão de Débito',
+ "05": "Crédito Loja",
+ '10': 'Vale Alimentação',
+ '11': 'Vale Refeição',
+ '12': 'Vale Presente',
+ '13': 'Vale Combustível',
+ '14': 'Duplicata Mercantil',
+ '15': 'Boleto Bancario',
+ '90': 'Sem Pagamento',
+ '99': 'Outros'}
+ quant_produtos = len(oXML.findall(
+ ".//{http://www.portalfiscal.inf.br/nfe}det"))
+
+ payment_methods = []
+
+ for pagId, item in enumerate(el_pag):
+ if 'tPag' not in item.tag:
+ continue
+
+ payment = []
+ method = payment_method_list[item.text]
+
+ payment.append(method)
+ payment.append(format_number(item.getnext().text, precision=2))
+ payment_methods.append(payment)
+
+ values = {'quantidade_itens': quant_produtos,
+ 'total_tributo': total_tributo,
+ 'valor_total': valor_total,
+ 'desconto': desconto,
+ 'valor_a_pagar': valor_a_pagar,
+ 'formas_de_pagamento': payment_methods,
+ 'troco': troco,
+ }
+
+ self.draw_totals_table(values)
+
+ self.drawLine()
+
+ def draw_totals_table(self, values):
+ rowHeights = [7, 7, 7, 7, 10]
+ data = [['QTD.TOTAL DE ITENS', values['quantidade_itens']],
+ ['VALOR TOTAL R$', values['valor_total']],
+ ['DESCONTO R$', values['desconto']],
+ ['VALOR A PAGAR R$', values['valor_a_pagar']],
+ ['FORMA DE PAGAMENTO', 'VALOR PAGO R$'],
+ ]
+
+ for item in values['formas_de_pagamento']:
+ data.append([item[0], item[1]])
+ rowHeights.append(7)
+ data.append(['TROCO', format_number(values['troco'], precision=2)])
+ rowHeights.append(7)
+
+ table2 = Table(data, colWidths=(150, 50), rowHeights=tuple(rowHeights))
+ table2.setStyle(TableStyle([
+ ('FONTSIZE', (0, 0), (-1, -1), 7),
+ ('FONT', (0, 0), (1, -1), 'NimbusSanL-Regu'),
+ ('FONT', (0, 4), (1, 4), 'NimbusSanL-Bold'),
+ ('ALIGN', (1, 0), (1, -1), "RIGHT")
+ ]))
+ w, h = table2.wrapOn(self.canvas, 200, 450)
+ table2.drawOn(self.canvas, 0, self.current_height - (h * 1.1))
+ self.current_height -= h
+
+ def inf_authentication(self, oXML=None):
+ el_infNFe = oXML.find(".//{http://www.portalfiscal.inf.br/nfe}infNFe")
+ # n nfce, serie e data de solicitacao
+ el_ide = oXML.find(".//{http://www.portalfiscal.inf.br/nfe}ide")
+
+ el_NFeSupl = oXML.find(
+ ".//{http://www.portalfiscal.inf.br/nfe}infNFeSupl")
+
+ el_dest = el_infNFe.find(".//{http://www.portalfiscal.inf.br/nfe}dest")
+ # chave, n protocolo, data autorizacao
+ el_prot_nfe = oXML.find(
+ ".//{http://www.portalfiscal.inf.br/nfe}protNFe")
+
+ el_infAdic = oXML.find(
+ ".//{http://www.portalfiscal.inf.br/nfe}infAdic")
+
+ url_chave = tagtext(oNode=el_NFeSupl, cTag='urlChave')
+ access_key = tagtext(oNode=el_prot_nfe, cTag="chNFe")
+
+ frase_chave_acesso = 'Consulte pela Chave de Acesso em:
\
+%s
%s' % (url_chave, access_key)
+
+ qrcode = tagtext(oNode=el_NFeSupl, cTag='qrCode')
+
+ cnpj = tagtext(oNode=el_dest, cTag='CNPJ')
+ cpf = tagtext(oNode=el_dest, cTag='CPF')
+ if cnpj:
+ cnpj_cpf = format_cnpj_cpf(cnpj)
+ cnpj_cpf = "CONSUMIDOR CNPJ: %s" % (cnpj)
+ elif cpf:
+ cnpj_cpf = format_cnpj_cpf(cpf)
+ cnpj_cpf = "CONSUMIDOR CPF: %s" % (cpf)
+ else:
+ cnpj_cpf = u"CONSUMIDOR NÃO IDENTIFICADO"
+
+ nNFC = tagtext(oNode=el_ide, cTag="nNF")
+ serie = tagtext(oNode=el_ide, cTag='serie')
+
+ dataSolicitacao = getdateUTC(tagtext(oNode=el_ide, cTag="dhEmi"))
+ dataSolicitacao = dataSolicitacao[0] + " " + dataSolicitacao[1]
+
+ numProtocolo = tagtext(oNode=el_prot_nfe, cTag="nProt")
+
+ dataAutorizacao = getdateUTC(tagtext(oNode=el_prot_nfe,
+ cTag='dhRecbto'))
+ dataAutorizacao = dataAutorizacao[0] + " " + dataAutorizacao[1]
+
+ text = u"%s
%s
NFC-e nº%s Série %s %s
\
+Protocolo de autorização: %s
Data de autorização %s
\
+" % (frase_chave_acesso, cnpj_cpf, nNFC, serie, dataSolicitacao,
+ numProtocolo, dataAutorizacao)
+
+ self._drawCenteredParagraph(text)
+
+ self.draw_qr_code(qrcode)
+
+ infAdFisco = tagtext(oNode=el_infAdic, cTag='infAdFisco')
+ self._drawCenteredParagraph(infAdFisco)
+
+ infCpl = tagtext(oNode=el_infAdic, cTag='infCpl')
+ self._drawCenteredParagraph(infCpl)
+
+ def _drawCenteredParagraph(self, text):
+
+ style = ParagraphStyle(
+ name='Normal',
+ fontName='NimbusSanL-Regu',
+ fontSize=7,
+ alignment=TA_CENTER,
+ leading=7,
+ )
+
+ paragraph = Paragraph(text, style=style)
+ w, h = paragraph.wrapOn(self.canvas, 180, 300)
+ paragraph.drawOn(self.canvas, 10, self.current_height - h)
+ self.current_height -= (h*1.1)
+
+ def drawString(self, string, centered=False):
+ if centered:
+ self.canvas.drawCentredString(
+ self.max_width / 2, self.current_height, string)
+ self.current_height -= self.current_font_size
+ else:
+ self.canvas.drawString(self.min_width, self.current_height, string)
+ self.current_height -= self.current_font_size
+
+ def drawTitle(self, string, size, font='NimbusSanL-Regu'):
+ self.canvas.setFont(font, size)
+ self.canvas.drawCentredString(
+ self.max_width / 2, self.current_height, string)
+ self.current_height -= self.current_font_size
+ self.canvas.setFont(self.current_font_name, self.current_font_size)
+
+ def drawLine(self):
+ self.canvas.line(self.min_width, self.current_height,
+ self.max_width, self.current_height)
+ self.current_height -= self.current_font_size
+
+ def draw_qr_code(self, string):
+ qr_code = qr.QrCodeWidget(string)
+ drawing = Drawing(23 * mm, 23 * mm)
+ drawing.add(qr_code)
+ renderPDF.draw(drawing, self.canvas, 20 * mm, self.current_height - 85)
+ self.current_height -= 85
+
+ def newpage(self):
+ self.current_height = self.max_height
+ self.Page += 1
+ self.canvas.showPage()
+
+ def nfce_generate(self):
+ for oXML in self.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
+
+ self.ide_emit(oXML=oXML)
+ # self.destinatario(oXML=oXML)
+ self.danfce_information()
+
+ self.produtos(oXML=oXML, el_det=el_det, oPaginator=oPaginator[0],
+ list_desc=list_desc, list_cod_prod=list_cod_prod)
+
+ self.drawLine()
+
+ self.totais(oXML=oXML)
+
+ self.inf_authentication(oXML=oXML)
+
+ # Gera o restante das páginas do XML
+ for oPag in oPaginator[1:]:
+ if oPag:
+ self.newpage()
+ self.ide_emit(oXML=oXML)
+ # self.destinatario(oXML=oXML)
+ self.produtos(oXML=oXML, el_det=el_det, oPaginator=oPag,
+ list_desc=list_desc,
+ list_cod_prod=list_cod_prod)
+ self.totais(oXML=oXML)
+ self.inf_authentication(oXML=oXML)
+
+ self.newpage()
+
+ self.canvas.save()
+
+ def writeto_pdf(self, fileObj):
+ pdf_out = self.oPDF_IO.getvalue()
+ self.oPDF_IO.close()
+ fileObj.write(pdf_out)