From 41e219ac6e0157863ffba6713e95dd0836779d29 Mon Sep 17 00:00:00 2001 From: Danimar Ribeiro Date: Sun, 8 May 2016 23:12:35 -0300 Subject: [PATCH] Modificando para a biblioteca signxml a assinatura --- pytrustnfe/servicos/Assinatura.py | 87 ---------------- pytrustnfe/servicos/Comunicacao.py | 96 ------------------ pytrustnfe/servicos/NfeStatusServico.py | 4 +- pytrustnfe/servicos/RecepcaoEvento.py | 5 +- pytrustnfe/servicos/Validacao.py | 4 +- pytrustnfe/servicos/assinatura.py | 129 +++++++++++++++++++++++ pytrustnfe/servicos/comunicacao.py | 90 +++++++++++++++++ pytrustnfe/servicos/nfe_autorizacao.py | 23 +++-- pytrustnfe/utils.py | 1 - pytrustnfe/xml/DynamicXml.py | 76 -------------- pytrustnfe/xml/__init__.py | 22 ++++ pytrustnfe/xml/filters.py | 58 +++++++++++ pytrustnfe/xml/nfeEnv.xml | 174 ++++++++++++++++++++++++++++++++ setup.py | 1 + 14 files changed, 496 insertions(+), 274 deletions(-) delete mode 100644 pytrustnfe/servicos/Assinatura.py delete mode 100644 pytrustnfe/servicos/Comunicacao.py create mode 100644 pytrustnfe/servicos/assinatura.py create mode 100644 pytrustnfe/servicos/comunicacao.py delete mode 100644 pytrustnfe/xml/DynamicXml.py create mode 100644 pytrustnfe/xml/filters.py create mode 100644 pytrustnfe/xml/nfeEnv.xml diff --git a/pytrustnfe/servicos/Assinatura.py b/pytrustnfe/servicos/Assinatura.py deleted file mode 100644 index 92df75b..0000000 --- a/pytrustnfe/servicos/Assinatura.py +++ /dev/null @@ -1,87 +0,0 @@ -#coding=utf-8 -''' -Created on Jun 14, 2015 - -@author: danimar -''' -import xmlsec, libxml2 -import os.path - -NAMESPACE_SIG = 'http://www.w3.org/2000/09/xmldsig#' - -class Assinatura(object): - - def __init__(self, arquivo, senha): - self.arquivo = arquivo - self.senha = senha - - def _checar_certificado(self): - if not os.path.isfile(self.arquivo): - raise Exception('Caminho do certificado não existe.') - - def _inicializar_cripto(self): - libxml2.initParser() - libxml2.substituteEntitiesDefault(1) - - xmlsec.init() - xmlsec.cryptoAppInit(None) - xmlsec.cryptoInit() - - def _finalizar_cripto(self): - xmlsec.cryptoShutdown() - xmlsec.cryptoAppShutdown() - xmlsec.shutdown() - - libxml2.cleanupParser() - - - def assina_xml(self, xml): - self._checar_certificado() - self._inicializar_cripto() - try: - doc_xml = libxml2.parseMemory(xml.encode('utf-8'), - len(xml.encode('utf-8'))) - - signNode = xmlsec.TmplSignature(doc_xml, - xmlsec.transformInclC14NId(), - xmlsec.transformRsaSha1Id(), None) - - doc_xml.getRootElement().addChild(signNode) - refNode = signNode.addReference( - xmlsec.transformSha1Id(), - None, '#NFe43150602261542000143550010000000761792265342', None) - - refNode.addTransform(xmlsec.transformEnvelopedId()) - refNode.addTransform(xmlsec.transformInclC14NId()) - keyInfoNode = signNode.ensureKeyInfo() - keyInfoNode.addX509Data() - - dsig_ctx = xmlsec.DSigCtx() - chave = xmlsec.cryptoAppKeyLoad( - filename=str(self.arquivo), - format=xmlsec.KeyDataFormatPkcs12, - pwd=str(self.senha), pwdCallback=None, pwdCallbackCtx=None) - - dsig_ctx.signKey = chave - dsig_ctx.sign(signNode) - - status = dsig_ctx.status - dsig_ctx.destroy() - - if status != xmlsec.DSigStatusSucceeded: - raise RuntimeError( - 'Erro ao realizar a assinatura do arquivo; status: "' + - str(status) + '"') - - xpath = doc_xml.xpathNewContext() - xpath.xpathRegisterNs('sig', NAMESPACE_SIG) - certs = xpath.xpathEval('//sig:X509Data/sig:X509Certificate') - for i in range(len(certs)-1): - certs[i].unlinkNode() - certs[i].freeNode() - - xml = doc_xml.serialize() - return xml - finally: - doc_xml.freeDoc() - self._finalizar_cripto() diff --git a/pytrustnfe/servicos/Comunicacao.py b/pytrustnfe/servicos/Comunicacao.py deleted file mode 100644 index d2745c9..0000000 --- a/pytrustnfe/servicos/Comunicacao.py +++ /dev/null @@ -1,96 +0,0 @@ -# coding=utf-8 -''' -Created on Jun 14, 2015 - -@author: danimar -''' - -from lxml import objectify -from uuid import uuid4 -from pytrustnfe.xml.DynamicXml import DynamicXml -from pytrustnfe.HttpClient import HttpClient -from pytrustnfe.Certificado import converte_pfx_pem - -from xml.dom.minidom import parseString - -common_namespaces = {'soap': 'http://www.w3.org/2003/05/soap-envelope'} - -soap_body_path = './soap:Envelope/soap:Body' -soap_fault_path = './soap:Envelope/soap:Body/soap:Fault' - - -class Comunicacao(object): - url = '' - web_service = '' - metodo = '' - tag_retorno = '' - - def __init__(self, certificado, senha): - self.certificado = certificado - self.senha = senha - - def _soap_xml(self, body): - xml = ''' - - -422.00 - - - -' + body - xml += '' - - def _preparar_temp_pem(self): - chave_temp = '/tmp/' + uuid4().hex - certificado_temp = '/tmp/' + uuid4().hex - - chave, certificado = converte_pfx_pem(self.certificado, self.senha) - arq_temp = open(chave_temp, 'w') - arq_temp.write(chave) - arq_temp.close() - - arq_temp = open(certificado_temp, 'w') - arq_temp.write(certificado) - arq_temp.close() - - return chave_temp, certificado_temp - - def _validar_dados(self): - assert self.url != '', "Url servidor não configurada" - assert self.web_service != '', "Web service não especificado" - assert self.certificado != '', "Certificado não configurado" - assert self.senha != '', "Senha não configurada" - assert self.metodo != '', "Método não configurado" - assert self.tag_retorno != '', "Tag de retorno não configurado" - - def _validar_xml(self, obj): - xml = None - if isinstance(obj, DynamicXml): - xml = obj.render() - if isinstance(obj, basestring): - xml = obj - assert xml is not None, "Objeto deve ser do tipo DynamicXml ou string" - return xml - - def _executar_consulta(self, xmlEnviar): - self._validar_dados() - chave, certificado = self._preparar_temp_pem() - - client = HttpClient(self.url, chave, certificado) - soap_xml = self._soap_xml(xmlEnviar) - xml_retorno = client.post_xml(self.web_service, soap_xml) - - dom = parseString(xml_retorno) - nodes = dom.getElementsByTagNameNS(common_namespaces['soap'], 'Fault') - if len(nodes) > 0: - return nodes[0].toxml(), None - - nodes = dom.getElementsByTagName(self.tag_retorno) - if len(nodes) > 0: - obj = objectify.fromstring(nodes[0].toxml()) - return nodes[0].toxml(), obj - - return xml_retorno, objectify.fromstring(xml_retorno) diff --git a/pytrustnfe/servicos/NfeStatusServico.py b/pytrustnfe/servicos/NfeStatusServico.py index fd03ec4..a88839a 100644 --- a/pytrustnfe/servicos/NfeStatusServico.py +++ b/pytrustnfe/servicos/NfeStatusServico.py @@ -4,9 +4,7 @@ Created on 21/06/2015 @author: danimar ''' -from pytrustnfe.servicos.Comunicacao import Comunicacao -from pytrustnfe.xml import DynamicXml - +from pytrustnfe.servicos.comunicacao import Comunicacao class NfeStatusServico(Comunicacao): diff --git a/pytrustnfe/servicos/RecepcaoEvento.py b/pytrustnfe/servicos/RecepcaoEvento.py index 487c73c..7ea125e 100644 --- a/pytrustnfe/servicos/RecepcaoEvento.py +++ b/pytrustnfe/servicos/RecepcaoEvento.py @@ -5,11 +5,10 @@ Created on 21/06/2015 @author: danimar ''' from pytrustnfe.servicos.Comunicacao import Comunicacao -from pytrustnfe.xml import DynamicXml class RecepcaoEvento(Comunicacao): - + def registrar_evento(self, evento): xml = self._validar_xml(recibo) @@ -18,4 +17,4 @@ class RecepcaoEvento(Comunicacao): self.web_service = 'ws/recepcaoevento/recepcaoevento.asmx' self.url = 'nfe.sefazrs.rs.gov.br' - return self._executar_consulta(xml) \ No newline at end of file + return self._executar_consulta(xml) diff --git a/pytrustnfe/servicos/Validacao.py b/pytrustnfe/servicos/Validacao.py index 7ba0014..ce5ecdb 100644 --- a/pytrustnfe/servicos/Validacao.py +++ b/pytrustnfe/servicos/Validacao.py @@ -8,8 +8,8 @@ def validar_schema(): arquivo_esquema = '' xml = tira_abertura(self.xml).encode('utf-8') - esquema = etree.XMLSchema(etree.parse(arquivo_esquema)) + esquema = etree.XMLSchema(etree.parse(arquivo_esquema)) esquema.validate(etree.fromstring(xml)) namespace = '{http://www.portalfiscal.inf.br/nfe}' - return "\n".join([x.message.replace(namespace, '') for x in esquema.error_log]) \ No newline at end of file + return "\n".join([x.message.replace(namespace, '') for x in esquema.error_log]) diff --git a/pytrustnfe/servicos/assinatura.py b/pytrustnfe/servicos/assinatura.py new file mode 100644 index 0000000..2d3b9c9 --- /dev/null +++ b/pytrustnfe/servicos/assinatura.py @@ -0,0 +1,129 @@ +# coding=utf-8 +''' +Created on Jun 14, 2015 + +@author: danimar +''' + +import xmlsec +import libxml2 +import os.path +from signxml import xmldsig +from signxml import methods +from lxml import etree + +NAMESPACE_SIG = 'http://www.w3.org/2000/09/xmldsig#' + + +def recursively_empty(e): + if e.text: + return False + return all((recursively_empty(c) for c in e.iterchildren())) + + +def assinar(xml, cert, key, reference, pfx, senha): + context = etree.iterwalk(xml) + for action, elem in context: + parent = elem.getparent() + if recursively_empty(elem): + parent.remove(elem) + + element = xml.find('{' + xml.nsmap[None] + '}NFe') + not_signed = etree.tostring(element) + not_signed = "]>" + \ + not_signed + + signer = xmldsig(element, digest_algorithm=u'sha1') + ns = {} + ns[None] = signer.namespaces['ds'] + signer.namespaces = ns + signed_root = signer.sign( + key=str(key), cert=cert, reference_uri=reference, + algorithm="rsa-sha1", method=methods.enveloped, + c14n_algorithm='http://www.w3.org/TR/2001/REC-xml-c14n-20010315') + + xmldsig(signed_root, digest_algorithm=u'sha1').verify(x509_cert=cert) + # signature = Assinatura(pfx, senha) + # xmlsec = signature.assina_xml(not_signed, reference) + # xmlsec = xmlsec.replace(""" +# ]>\n""", "") + return etree.tostring(signed_root) + # , xmlsec + + +class Assinatura(object): + + def __init__(self, arquivo, senha): + self.arquivo = arquivo + self.senha = senha + + def _checar_certificado(self): + if not os.path.isfile(self.arquivo): + raise Exception('Caminho do certificado não existe.') + + def _inicializar_cripto(self): + libxml2.initParser() + libxml2.substituteEntitiesDefault(1) + + xmlsec.init() + xmlsec.cryptoAppInit(None) + xmlsec.cryptoInit() + + def _finalizar_cripto(self): + xmlsec.cryptoShutdown() + xmlsec.cryptoAppShutdown() + xmlsec.shutdown() + + libxml2.cleanupParser() + + def assina_xml(self, xml, reference): + self._checar_certificado() + self._inicializar_cripto() + try: + doc_xml = libxml2.parseMemory(xml.encode('utf-8'), + len(xml.encode('utf-8'))) + import ipdb; ipdb.set_trace() + signNode = xmlsec.TmplSignature(doc_xml, + xmlsec.transformInclC14NId(), + xmlsec.transformRsaSha1Id(), None) + + doc_xml.getRootElement().addChild(signNode) + refNode = signNode.addReference( + xmlsec.transformSha1Id(), + None, reference, None) + + refNode.addTransform(xmlsec.transformEnvelopedId()) + refNode.addTransform(xmlsec.transformInclC14NId()) + keyInfoNode = signNode.ensureKeyInfo() + keyInfoNode.addX509Data() + + dsig_ctx = xmlsec.DSigCtx() + chave = xmlsec.cryptoAppKeyLoad( + filename=str(self.arquivo), + format=xmlsec.KeyDataFormatPkcs12, + pwd=str(self.senha), pwdCallback=None, pwdCallbackCtx=None) + + dsig_ctx.signKey = chave + dsig_ctx.sign(signNode) + + status = dsig_ctx.status + dsig_ctx.destroy() + + if status != xmlsec.DSigStatusSucceeded: + raise RuntimeError( + 'Erro ao realizar a assinatura do arquivo; status: "' + + str(status) + '"') + + xpath = doc_xml.xpathNewContext() + xpath.xpathRegisterNs('sig', NAMESPACE_SIG) + certs = xpath.xpathEval('//sig:X509Data/sig:X509Certificate') + for i in range(len(certs)-1): + certs[i].unlinkNode() + certs[i].freeNode() + + xml = doc_xml.serialize() + return xml + finally: + doc_xml.freeDoc() + self._finalizar_cripto() diff --git a/pytrustnfe/servicos/comunicacao.py b/pytrustnfe/servicos/comunicacao.py new file mode 100644 index 0000000..99ed9d9 --- /dev/null +++ b/pytrustnfe/servicos/comunicacao.py @@ -0,0 +1,90 @@ +# coding=utf-8 +''' +Created on Jun 14, 2015 + +@author: danimar +''' + +from lxml import objectify +from uuid import uuid4 +from pytrustnfe.HttpClient import HttpClient +from pytrustnfe.Certificado import converte_pfx_pem + +from xml.dom.minidom import parseString + +common_namespaces = {'soap': 'http://www.w3.org/2003/05/soap-envelope'} + +soap_body_path = './soap:Envelope/soap:Body' +soap_fault_path = './soap:Envelope/soap:Body/soap:Fault' + + +class Comunicacao(object): + url = '' + web_service = '' + metodo = '' + tag_retorno = '' + + def __init__(self, cert, key): + self.certificado = cert + self.senha = key + + def _soap_xml(self, body): + xml = ''' + + +422.00 + + + +' + body + xml += '' + + def _preparar_temp_pem(self): + chave_temp = '/tmp/' + uuid4().hex + certificado_temp = '/tmp/' + uuid4().hex + + chave, certificado = converte_pfx_pem(self.certificado, self.senha) + arq_temp = open(chave_temp, 'w') + arq_temp.write(chave) + arq_temp.close() + + arq_temp = open(certificado_temp, 'w') + arq_temp.write(certificado) + arq_temp.close() + + return chave_temp, certificado_temp + + def _validar_dados(self): + assert self.url != '', "Url servidor não configurada" + assert self.web_service != '', "Web service não especificado" + assert self.certificado != '', "Certificado não configurado" + assert self.senha != '', "Senha não configurada" + assert self.metodo != '', "Método não configurado" + assert self.tag_retorno != '', "Tag de retorno não configurado" + + def _validar_nfe(self, obj): + if not isinstance(obj, dict): + raise u"Objeto deve ser um dicionário de valores" + + def _executar_consulta(self, xmlEnviar): + self._validar_dados() + # chave, certificado = self._preparar_temp_pem() + + client = HttpClient(self.url, self.certificado, self.senha) + soap_xml = self._soap_xml(xmlEnviar) + xml_retorno = client.post_xml(self.web_service, soap_xml) + + dom = parseString(xml_retorno) + nodes = dom.getElementsByTagNameNS(common_namespaces['soap'], 'Fault') + if len(nodes) > 0: + return nodes[0].toxml(), None + + nodes = dom.getElementsByTagName(self.tag_retorno) + if len(nodes) > 0: + obj = objectify.fromstring(nodes[0].toxml()) + return nodes[0].toxml(), obj + + return xml_retorno, objectify.fromstring(xml_retorno) diff --git a/pytrustnfe/servicos/nfe_autorizacao.py b/pytrustnfe/servicos/nfe_autorizacao.py index 2e6aed4..d7595d6 100644 --- a/pytrustnfe/servicos/nfe_autorizacao.py +++ b/pytrustnfe/servicos/nfe_autorizacao.py @@ -4,17 +4,23 @@ Created on 21/06/2015 @author: danimar ''' -from pytrustnfe.servicos.Comunicacao import Comunicacao +from lxml import etree +from pytrustnfe.servicos.comunicacao import Comunicacao from pytrustnfe import utils +from pytrustnfe.xml import render_xml +from pytrustnfe.servicos.assinatura import assinar class NfeAutorizacao(Comunicacao): - def __init__(self, certificado, senha): + def __init__(self, cert, key, certificado, senha): Comunicacao.__init__(self, certificado, senha) + self.cert = cert + self.key = key def autorizar_nfe(self, nfe): - xml = self._validar_xml(nfe) + self._validar_nfe(nfe) + xml = render_xml('nfeEnv.xml', **nfe) self.metodo = 'NFeAutorizacao' self.tag_retorno = 'retEnviNFe' @@ -23,8 +29,13 @@ class NfeAutorizacao(Comunicacao): return self._executar_consulta(xml) - def autorizar_nfe_e_recibo(self, nfe): - xml = self._validar_xml(nfe) + def autorizar_nfe_e_recibo(self, nfe, id): + self._validar_nfe(nfe) + xml = render_xml('nfeEnv.xml', **nfe) + + return assinar(xml, self.cert, self.key, + '#%s' % id, + self.certificado, self.senha) self.metodo = 'NFeAutorizacao' self.tag_retorno = 'retEnviNFe' @@ -34,7 +45,7 @@ class NfeAutorizacao(Comunicacao): xml_recibo, recibo = self._executar_consulta(xml) consulta_recibo = utils.gerar_consulta_recibo(recibo) - xml = self._validar_xml(nfe) + self._validar_nfe(nfe) self.metodo = 'NFeRetAutorizacao' self.tag_retorno = 'retConsReciNFe' diff --git a/pytrustnfe/utils.py b/pytrustnfe/utils.py index 8adafdd..933e4e3 100644 --- a/pytrustnfe/utils.py +++ b/pytrustnfe/utils.py @@ -6,7 +6,6 @@ Created on 22/06/2015 ''' from datetime import date, datetime from pytrustnfe.ChaveNFe import ChaveNFe -from pytrustnfe.xml.DynamicXml import DynamicXml def date_tostring(data): diff --git a/pytrustnfe/xml/DynamicXml.py b/pytrustnfe/xml/DynamicXml.py deleted file mode 100644 index 124c939..0000000 --- a/pytrustnfe/xml/DynamicXml.py +++ /dev/null @@ -1,76 +0,0 @@ -# coding=utf-8 -''' -Created on Jun 17, 2015 - -@author: danimar -''' - -import xml.etree.ElementTree as ET -from lxml.etree import Element, tostring -from __builtin__ import str - - -class DynamicXml(object): - - def __getattr__(self, name): - try: - return object.__getattribute__(self, name) - except: - self.__setattr__(name, None) - return object.__getattribute__(self, name) - - def __setattr__(self, obj, val): - if(obj == "value" or obj == "atributos" or obj == "_indice"): - object.__setattr__(self, obj, val) - else: - self._indice = self._indice + 1 - object.__setattr__(self, obj, DynamicXml(val, self._indice)) - - def __init__(self, value, indice=0): - self.value = unicode(value, 'utf-8') if isinstance(value, - str) else value - self.atributos = {} - self._indice = indice - - def __str__(self): - return unicode(self.value) - - def __call__(self, *args, **kw): - if(len(kw) > 0): - self.atributos = kw - if(len(args) > 0): - self.value = args[0] - else: - return self.value - - def __getitem__(self, i): - if not isinstance(self.value, list): - self.value = [] - if(i + 1 > len(self.value)): - self.value.append(DynamicXml(None)) - return self.value[i] - - def render(self, pretty_print=False): - root = Element(self.value) - self._gerar_xml(root, self) - return tostring(root, pretty_print=pretty_print) - - def _gerar_xml(self, xml, objeto): - items = sorted( - objeto.__dict__.items(), - key=lambda x: x[1]._indice if isinstance(x[1], DynamicXml) else 0 - ) - for attr, value in items: - if(attr != "value" and attr != "atributos" and attr != "_indice"): - if isinstance(value(), list): - for item in value(): - sub = ET.SubElement(xml, attr) - self._gerar_xml(sub, item) - else: - sub = ET.SubElement(xml, attr) - if(unicode(value) != u"None"): - sub.text = unicode(value) - self._gerar_xml(sub, value) - elif(attr == "atributos"): - for atr, val in value.items(): - xml.set(atr.replace("__", ":"), str(val)) diff --git a/pytrustnfe/xml/__init__.py b/pytrustnfe/xml/__init__.py index e69de29..759854f 100644 --- a/pytrustnfe/xml/__init__.py +++ b/pytrustnfe/xml/__init__.py @@ -0,0 +1,22 @@ +import os.path +from lxml import etree +from jinja2 import Environment, FileSystemLoader +from . import filters + + +def render_xml(template_name, **nfe): + path = os.path.dirname(__file__) + env = Environment( + loader=FileSystemLoader(path), extensions=['jinja2.ext.with_']) + + env.filters["normalize"] = filters.normalize_str + env.filters["format_percent"] = filters.format_percent + env.filters["format_datetime"] = filters.format_datetime + env.filters["format_date"] = filters.format_date + + template = env.get_template(template_name) + + xml = template.render(**nfe) + xml = xml.replace('&', '&') + parser = etree.XMLParser(remove_blank_text=True, remove_comments=True) + return etree.fromstring(xml, parser=parser) diff --git a/pytrustnfe/xml/filters.py b/pytrustnfe/xml/filters.py new file mode 100644 index 0000000..5f275fd --- /dev/null +++ b/pytrustnfe/xml/filters.py @@ -0,0 +1,58 @@ +# coding=utf-8 + +from decimal import Decimal +from datetime import datetime +import dateutil.parser as parser_dateutil + +from unicodedata import normalize + + +def normalize_str(string): + """ + Remove special characters and return the ascii string + """ + if string: + if not isinstance(string, unicode): + string = unicode(string, 'utf-8', 'replace') + + string = string.encode('utf-8') + return normalize( + 'NFKD', string.decode('utf-8')).encode('ASCII', 'ignore') + return '' + + +def format_percent(value): + if value: + return Decimal(value) / 100 + + +def format_datetime(value): + """ + Format datetime + """ + dt_format = '%Y-%m-%dT%H:%M:%I' + if isinstance(value, datetime): + return value.strftime(dt_format) + + try: + value = parser_dateutil.parse(value).strftime(dt_format) + except AttributeError: + pass + + return value + + +def format_date(value): + """ + Format date + """ + dt_format = '%Y-%m-%d' + if isinstance(value, datetime): + return value.strftime(dt_format) + + try: + value = parser_dateutil.parse(value).strftime(dt_format) + except AttributeError: + pass + + return value diff --git a/pytrustnfe/xml/nfeEnv.xml b/pytrustnfe/xml/nfeEnv.xml new file mode 100644 index 0000000..01e137f --- /dev/null +++ b/pytrustnfe/xml/nfeEnv.xml @@ -0,0 +1,174 @@ + + {{ idLote }} + {{ indSinc }} + {% for NFe in NFes %} + + + + {% with ide = NFe.infNFe.ide %} + {{ ide.cUF }} + {{ ide.cNF }} + {{ ide.natOp }} + {{ ide.indPag }} + {{ ide.mod }} + {{ ide.serie }} + {{ ide.nNF }} + {{ ide.dhEmi }} + {{ ide.dhSaiEnt }} + {{ ide.tpNF }} + {{ ide.idDest }} + {{ ide.cMunFG }} + {{ ide.tpImp }} + {{ ide.tpEmis }} + {{ ide.cDV }} + {{ ide.tpAmb }} + {{ ide.finNFe }} + {{ ide.indFinal }} + {{ ide.indPres }} + {{ ide.procEmi }} + Odoo Brasil 9.0 + {% endwith %} + + + {% with emit = NFe.infNFe.emit %} + {{ emit.CNPJ }} + {{ emit.xNome }} + {{ emit.xFant }} + + {{ emit.enderEmit.xLgr }} + {{ emit.enderEmit.nro }} + {{ emit.enderEmit.xBairro }} + {{ emit.enderEmit.cMun }} + {{ emit.enderEmit.xMun }} + {{ emit.enderEmit.UF }} + {{ emit.enderEmit.CEP }} + {{ emit.enderEmit.cPais }} + {{ emit.enderEmit.xPais }} + {{ emit.enderEmit.fone }} + + {{ emit.IE }} + {{ emit.CRT }} + {% endwith %} + + + {% with dest = NFe.infNFe.dest %} + {{ dest.CNPJ }} + {{ dest.CPF }} + {{ dest.xNome }} + + {{ dest.enderDest.xLgr }} + {{ dest.enderDest.nro }} + {{ dest.enderDest.xBairro }} + {{ dest.enderDest.cMun }} + {{ dest.enderDest.xMun }} + {{ dest.enderDest.UF }} + {{ dest.enderDest.CEP }} + {{ dest.enderDest.cPais }} + {{ dest.enderDest.xPais }} + {{ dest.enderDest.fone }} + + {{ dest.indIEDest }} + {{ dest.IE }} + {% endwith %} + + {% for det in NFe.infNFe.detalhes %} + + + {% with prod = det.prod %} + {{ prod.cProd }} + {{ prod.cEAN }} + {{ prod.xProd }} + {{ prod.NCM }} + {{ prod.CFOP }} + {{ prod.uCom }} + {{ prod.qCom }} + {{ prod.vUnCom }} + {{ prod.vProd }} + {{ prod.cEANTrib }} + {{ prod.uTrib }} + {{ prod.qTrib }} + {{ prod.vUnTrib }} + {{ prod.indTot }} + {% endwith %} + + + {% with imposto = det.imposto %} + {{ imposto.vTotTrib }} + + + {{ imposto.ICMS.ICMS00.orig }} + {{ imposto.ICMS.ICMS00.CST }} + {{ imposto.ICMS.ICMS00.modBC }} + {{ imposto.ICMS.ICMS00.vBC }} + {{ imposto.ICMS.ICMS00.pICMS }} + {{ imposto.ICMS.ICMS00.vICMS }} + + + + {{ imposto.IPI.cEnq }} + + {{ imposto.IPI.IPITrib.CST }} + {{ imposto.IPI.IPITrib.vBC }} + {{ imposto.IPI.IPITrib.pIPI }} + {{ imposto.IPI.IPITrib.vIPI }} + + + + + {{ imposto.PIS.PISAliq.CST }} + {{ imposto.PIS.PISAliq.vBC }} + {{ imposto.PIS.PISAliq.pPIS }} + {{ imposto.PIS.PISAliq.vPIS }} + + + + + {{ imposto.COFINS.COFINSAliq.CST }} + {{ imposto.COFINS.COFINSAliq.vBC }} + {{ imposto.COFINS.COFINSAliq.pCOFINS }} + {{ imposto.COFINS.COFINSAliq.vCOFINS }} + + + {% endwith %} + + + {% endfor %} + + {% with total = NFe.infNFe.total %} + + {{ total.vBC }} + {{ total.vICMS }} + {{ total.vICMSDeson }} + {{ total.vBCST }} + {{ total.vST }} + {{ total.vProd }} + {{ total.vFrete }} + {{ total.vSeg }} + {{ total.vDesc }} + {{ total.vII }} + {{ total.vIPI }} + {{ total.vPIS }} + {{ total.vCOFINS }} + {{ total.vOutro }} + {{ total.vNF }} + {{ total.vTotTrib }} + + {% endwith %} + + + {{ NFe.infNFe.transp.modFrete }} + + + + 339/1 + 2016-06-02 + 8611.76 + + + + {{ NFe.infNFe.infAdic.infCpl }} + + + + {% endfor %} + diff --git a/setup.py b/setup.py index bf50a14..04fa4a2 100644 --- a/setup.py +++ b/setup.py @@ -18,6 +18,7 @@ setup( 'Topic :: Software Development :: Libraries :: Python Modules', ], packages=find_packages(exclude=['*test*']), + package_data={'pytrustnfe': ['xml/*xml']}, url='https://github.com/danimaribeiro/PyNfeTrust', license='LGPL-v2.1+', description='PyNfeTrust é uma biblioteca para envio de NF-e',