# -*- 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)