diff --git a/pytrustnfe/nfe/danfe.py b/pytrustnfe/nfe/danfe.py index 2233877..7cb7303 100644 --- a/pytrustnfe/nfe/danfe.py +++ b/pytrustnfe/nfe/danfe.py @@ -1,839 +1,854 @@ -# -*- 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 - -import os -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 -from reportlab.pdfbase import pdfmetrics -from reportlab.pdfbase.ttfonts import TTFont - - -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): - - path = os.path.join(os.path.dirname(__file__), 'fonts') - pdfmetrics.registerFont( - TTFont('NimbusSanL-Regu', - os.path.join(path, 'NimbusSanL Regular.ttf'))) - pdfmetrics.registerFont( - TTFont('NimbusSanL-Bold', - os.path.join(path, 'NimbusSanL Bold.ttf'))) - 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) +# -*- 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 + +import os +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 +from reportlab.pdfbase import pdfmetrics +from reportlab.pdfbase.ttfonts import TTFont + + +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): + + path = os.path.join(os.path.dirname(__file__), 'fonts') + pdfmetrics.registerFont( + TTFont('NimbusSanL-Regu', + os.path.join(path, 'NimbusSanL Regular.ttf'))) + pdfmetrics.registerFont( + TTFont('NimbusSanL-Bold', + os.path.join(path, 'NimbusSanL Bold.ttf'))) + 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 - Destinatário', + '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')) + cnpj_cpf = tagtext(oNode=elem_dest, cTag='CNPJ') + if cnpj_cpf: + cnpj_cpf = format_cnpj_cpf(cnpj_cpf) + else: + cnpj_cpf = format_cnpj_cpf(tagtext(oNode=elem_dest, cTag='CPF')) + self.string(nMr - 69, self.nlin + 7.5, cnpj_cpf) + 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') or + tagtext(oNode=el_imp_ICMS, cTag='CSOSN')) + 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/dsf/__init__.py b/pytrustnfe/nfse/dsf/__init__.py new file mode 100644 index 0000000..df4ce4a --- /dev/null +++ b/pytrustnfe/nfse/dsf/__init__.py @@ -0,0 +1,131 @@ +# -*- encoding: utf-8 -*- +# © 2017 Fábio Luna, Trustcode +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import os +import suds +from lxml import etree +from pytrustnfe.xml import render_xml, sanitize_response +from pytrustnfe.certificado import extract_cert_and_key_from_pfx, save_cert_key +from pytrustnfe.nfse.assinatura import Assinatura +from pytrustnfe.client import get_client + + +def _render(certificado, method, **kwargs): + path = os.path.join(os.path.dirname(__file__), 'templates') + if method == "testeEnviar": + xml_send = render_xml(path, 'enviar.xml', True, **kwargs) + else: + xml_send = render_xml(path, '%s.xml' % method, False, **kwargs) + + if type(xml_send) != str: + xml_send = etree.tostring(xml_send) + + return xml_send + + +def _get_url(**kwargs): + + try: + cod_cidade = kwargs['nfse']['cidade'] + except (KeyError, TypeError): + raise KeyError("Código de cidade inválido!") + + urls = { + # Belém - PA + '2715': 'http://www.issdigitalbel.com.br/WsNFe2/LoteRps.jws', + # Sorocaba - SP + '7145': 'http://issdigital.sorocaba.sp.gov.br/WsNFe2/LoteRps.jws', + # Teresina - PI + '1219': 'http://www.issdigitalthe.com.br/WsNFe2/LoteRps.jws', + # Campinas - SP + '6291': 'http://issdigital.campinas.sp.gov.br/WsNFe2/LoteRps.jws?wsdl', + # Uberlandia - MG + '5403': 'http://udigital.uberlandia.mg.gov.br/WsNFe2/LoteRps.jws', + # São Luis - MA + '0921': 'https://stm.semfaz.saoluis.ma.gov.br/WsNFe2/LoteRps?wsdl', + # Campo Grande - MS + '2729': 'http://issdigital.pmcg.ms.gov.br/WsNFe2/LoteRps.jws?wsdl', + } + + try: + return urls[str(cod_cidade)] + except KeyError: + raise KeyError("DSF não emite notas da cidade {}!".format( + cod_cidade)) + + +def _send(certificado, method, **kwargs): + url = _get_url(**kwargs) + + path = os.path.join(os.path.dirname(__file__), 'templates') + + xml_send = _render(path, method, **kwargs) + client = get_client(url) + response = False + + if certificado: + cert, key = extract_cert_and_key_from_pfx( + certificado.pfx, certificado.password) + cert, key = save_cert_key(cert, key) + signer = Assinatura(cert, key, certificado.password) + xml_send = signer.assina_xml(xml_send, '') + + try: + response = getattr(client.service, method)(xml_send) + response, obj = sanitize_response(response.encode()) + except suds.WebFault as e: + return { + 'sent_xml': xml_send, + 'received_xml': e.fault.faultstring, + 'object': None + } + except Exception as e: + if response: + raise Exception(response) + else: + raise e + + return { + 'sent_xml': xml_send, + 'received_xml': response, + 'object': obj + } + + +def xml_enviar(certificado, **kwargs): + return _render(certificado, 'enviar', **kwargs) + + +def enviar(certificado, **kwargs): + if "xml" not in kwargs: + kwargs['xml'] = xml_enviar(certificado, **kwargs) + return _send(certificado, 'enviar', **kwargs) + + +def xml_teste_enviar(certificado, **kwargs): + return _render(certificado, 'testeEnviar', **kwargs) + + +def teste_enviar(certificado, **kwargs): + if "xml" not in kwargs: + kwargs['xml'] = xml_teste_enviar(certificado, **kwargs) + return _send(certificado, 'testeEnviar', **kwargs) + + +def cancelar(certificado, ** kwargs): + return _send(certificado, 'cancelar', **kwargs) + + +def consulta_lote(**kwargs): + return _send(False, 'consultarLote', **kwargs) + + +def xml_consultar_nfse_rps(certificado, **kwargs): + return _render(certificado, 'consultarNFSeRps', **kwargs) + + +def consultar_nfse_rps(certificado, **kwargs): + if "xml" not in kwargs: + kwargs['xml'] = xml_consultar_nfse_rps(certificado, **kwargs) + return _send(certificado, 'consultarNFSeRps', **kwargs) diff --git a/pytrustnfe/nfse/dsf/templates/cancelar.xml b/pytrustnfe/nfse/dsf/templates/cancelar.xml new file mode 100644 index 0000000..d72086b --- /dev/null +++ b/pytrustnfe/nfse/dsf/templates/cancelar.xml @@ -0,0 +1,18 @@ + + + {{ cancelamento.cidade }} + {{ cancelamento.cpf_cnpj }} + true + 1 + + + + {{ cancelamento.inscricao_municipal }} + {{ cancelamento.nota_id }} + {{ cancelamento.assinatura }} + {{ cancelamento.motivo }} + + + diff --git a/pytrustnfe/nfse/dsf/templates/consulta_notas.xml b/pytrustnfe/nfse/dsf/templates/consulta_notas.xml new file mode 100644 index 0000000..4a666d0 --- /dev/null +++ b/pytrustnfe/nfse/dsf/templates/consulta_notas.xml @@ -0,0 +1,11 @@ + + +{{ consulta.cidade }} +{{ consulta.cpf_cnpj }} +{{ consulta.inscricao_municipal }} +{{ consulta.data_inicio }} +{{ consulta.data_final }} +{{ consulta.nota_inicial }} +1 + + \ No newline at end of file diff --git a/pytrustnfe/nfse/dsf/templates/consultarLote.xml b/pytrustnfe/nfse/dsf/templates/consultarLote.xml new file mode 100644 index 0000000..24afc5d --- /dev/null +++ b/pytrustnfe/nfse/dsf/templates/consultarLote.xml @@ -0,0 +1,10 @@ + + + {{ consulta.cidade }} + {{ consulta.cpf_cnpj }} + 1 + {{ consulta.lote }} + + \ No newline at end of file diff --git a/pytrustnfe/nfse/dsf/templates/consultarNFSeRps.xml b/pytrustnfe/nfse/dsf/templates/consultarNFSeRps.xml new file mode 100644 index 0000000..a6a51bc --- /dev/null +++ b/pytrustnfe/nfse/dsf/templates/consultarNFSeRps.xml @@ -0,0 +1,22 @@ + + + {{ nfse.cidade }} + {{ nfse.cpf_cnpj }} + true + 1 + + + {% for rps in nfse.lista_rps -%} + + + {{ rps.prestador.inscricao_municipal }} + {{ rps.numero }} + {{ rps.serie_prestacao }} + + + {% endfor %} + + \ No newline at end of file diff --git a/pytrustnfe/nfse/dsf/templates/enviar.xml b/pytrustnfe/nfse/dsf/templates/enviar.xml new file mode 100644 index 0000000..7e4b178 --- /dev/null +++ b/pytrustnfe/nfse/dsf/templates/enviar.xml @@ -0,0 +1,108 @@ + + + {{ nfse.cidade }} + {{ nfse.cpf_cnpj }} + {{ nfse.remetente }} + {{ nfse.transacao }} + {{ nfse.data_inicio|format_date }} + {{ nfse.data_fim|format_date }} + {{ nfse.total_rps }} + {{ nfse.total_servicos }} + {{ nfse.total_deducoes }} + 1 + WS + + + {% for rps in nfse.lista_rps -%} + + {{ rps.assinatura }} + {{ rps.prestador.inscricao_municipal }} + + {{ rps.prestador.razao_social }} + RPS + {{ rps.serie }} + {{ rps.numero }} + {{ rps.data_emissao|format_datetime }} + + {{ rps.situacao }} + + 0 + 0 + 1900-01-01 + {{ rps.serie_prestacao }} + {{ rps.tomador.inscricao_municipal }} + {{ rps.tomador.cpf_cnpj }} + {{ rps.tomador.razao_social }} + + {{ rps.tomador.tipo_logradouro }} + + {{ rps.tomador.logradouro }} + {{ rps.tomador.numero }} + + {{ rps.tomador.tipo_bairro }} + {{ rps.tomador.bairro }} + {{ rps.tomador.cidade }} + {{ rps.tomador.cidade_descricao }} + + {{ rps.tomador.cep }} + {{ rps.tomador.email }} + {{ rps.codigo_atividade }} + {{ rps.aliquota_atividade }} + {{ rps.tipo_recolhimento }} + {{ rps.municipio_prestacao }} + + {{ rps.municipio_descricao_prestacao }} + + {{ rps.operacao }} + {{ rps.tributacao }} + {{ rps.valor_pis }} + {{ rps.valor_cofins }} + {{ rps.valor_inss }} + {{ rps.valor_ir }} + {{ rps.valor_csll }} + {{ rps.aliquota_pis }} + {{ rps.aliquota_cofins }} + {{ rps.aliquota_inss }} + {{ rps.aliquota_ir }} + {{ rps.aliquota_csll }} + {{ rps.descricao }} + {{ rps.prestador.ddd }} + {{ rps.prestador.telefone }} + {{ rps.tomador.ddd }} + {{ rps.tomador.telefone }} + {{ rps.motivo_cancelamento }} + {% if rps.deducoes|count > 0 %} + + {% for deducao in rps.deducoes -%} + + {{ deducao.por }} + {{ deducao.tipo }} + {{ deducao.cnpj_referencia }} + {{ deducao.nf_referencia }} + {{ deducao.valor_referencia }} + {{ deducao.percentual_deduzir }} + {{ deducao.valor_deduzir }} + + {% endfor %} + + {% endif %} + {% if rps.deducoes|count == 0 %} + + {% endif %} + + {% for item in rps.itens -%} + + {{ item.descricao }} + {{ item.quantidade }} + {{ item.valor_unitario }} + {{ item.valor_total }} + S + + {% endfor %} + + + {% endfor %} + + diff --git a/pytrustnfe/nfse/dsf/templates/soap_header.xml b/pytrustnfe/nfse/dsf/templates/soap_header.xml new file mode 100644 index 0000000..e9d1dd2 --- /dev/null +++ b/pytrustnfe/nfse/dsf/templates/soap_header.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/pytrustnfe/xml/__init__.py b/pytrustnfe/xml/__init__.py index 99cd580..413df5e 100644 --- a/pytrustnfe/xml/__init__.py +++ b/pytrustnfe/xml/__init__.py @@ -27,7 +27,6 @@ def render_xml(path, template_name, remove_empty, **nfe): env.filters["format_date"] = filters.format_date template = env.get_template(template_name) - xml = template.render(**nfe) parser = etree.XMLParser(remove_blank_text=True, remove_comments=True, strip_cdata=False) @@ -61,6 +60,7 @@ def sanitize_response(response): def recursively_normalize(vals): for item in vals: if type(vals[item]) is str: + vals[item] = vals[item].strip() vals[item] = filters.normalize_str(vals[item]) elif type(vals[item]) is dict: recursively_normalize(vals[item]) diff --git a/setup.py b/setup.py index b39eb64..cb097df 100644 --- a/setup.py +++ b/setup.py @@ -27,6 +27,7 @@ later (LGPLv2+)', 'nfe/templates/*xml', 'nfe/fonts/*ttf', 'nfse/paulistana/templates/*xml', + 'nfse/dsf/templates/*xml', 'nfse/ginfes/templates/*xml', 'nfse/simpliss/templates/*xml', 'nfse/betha/templates/*xml',