Example #1
0
    def __init__(self):
        self.ambiente = 2
        self.estado = 'SP'
        self.versao = '2.00'
        self.certificado = Certificado()
        self.caminho = ''
        self.salvar_arquivos = True
        self.contingencia_SCAN = False
        self.gerar_danfe = True
        self.danfe = DANFE()
        self.caminho_temporario = ''
        self.maximo_tentativas_consulta_recibo = 5

        self._servidor     = ''
        self._url          = ''
        self._soap_envio   = None
        self._soap_retorno = None
 def _obter_dados_do_certificado(self, certificado, senha):
     self._certificado = Certificado()
     self._certificado.arquivo = certificado
     self._certificado.senha = senha
     self._certificado.prepara_certificado_arquivo_pfx()
class ProcessadorBase(object):
    def __init__(self, servidor, endereco, certificado, senha, caminho=''):
        self.servidor = servidor
        self.endereco = endereco
        self.versao = u'1.00'
        self.caminho = caminho
        self._destino = None
        self._obter_dados_do_certificado(certificado, senha)
        self.NS = 'http://www.abrasf.org.br/ABRASF/arquivos/nfse.xsd'
        NS_SCHEMA = 'http://www.w3.org/2001/XMLSchema-instance'
        self.namespace = 'xmlns="{}" xmlns:xsi="{}"'.format(self.NS, NS_SCHEMA)

    def _obter_dados_do_certificado(self, certificado, senha):
        self._certificado = Certificado()
        self._certificado.arquivo = certificado
        self._certificado.senha = senha
        self._certificado.prepara_certificado_arquivo_pfx()

    def _remover_encode(self, xml):
        aberturas = ('<?xml version="1.0" encoding="utf-8"?>',
                     '<?xml version="1.0" encoding="utf-8" ?>',
                     '<?xml version="1.0" encoding="UTF-8"?>',
                     '<?xml version="1.0" encoding="UTF-8" ?>')

        for a in aberturas:
            xml = xml.replace(a, '')

        return xml

    def _validar_xml(self, xml, xsd=None):
        xml = self._remover_encode(xml)
        curdir = os.getcwd()
        try:
            xsd_path = os.path.join(os.path.dirname(__file__), 'nfse.xsd')
            esquema = etree.XMLSchema(etree.parse(xsd_path))
        finally:
            os.chdir(curdir)
        esquema.assertValid(etree.fromstring(xml))
        return xml

    def _conectar_servidor(self, xml, servico, xsd_retorno):
        caminho_temporario = u'/tmp/'
        nome_arq_chave = caminho_temporario + uuid4().hex
        arq_tmp = open(nome_arq_chave, 'w')
        arq_tmp.write(self._certificado.chave)
        arq_tmp.close()

        nome_arq_certificado = caminho_temporario + uuid4().hex
        arq_tmp = open(nome_arq_certificado, 'w')
        arq_tmp.write(self._certificado.certificado)
        arq_tmp.close()

        xml = self._soap(xml, servico)
        con = HTTPSConnection(self.servidor,
                              key_file=nome_arq_chave,
                              cert_file=nome_arq_certificado)
        con.request(
            u'POST', self.endereco, xml, {
                u'Content-Type': u'application/soap+xml; charset=utf-8',
                u'Content-Length': len(xml),
            })

        if self._destino:
            arq = open(self._destino + '-env.xml', 'w')
            arq.write(xml.encode(u'utf-8'))
            arq.close()

        resposta = con.getresponse()
        if resposta.status != 200:
            raise CommunicationError(resposta.status, resposta.reason)

        resp_xml_str = resposta.read()
        resp_xml = ET.fromstring(resp_xml_str)
        result_str = resp_xml.find(".//{%s}RetornoXML" % self.NS).text
        xsd_retorno.ExternalEncoding = 'utf-8'
        result = xsd_retorno.parseString(result_str.encode('utf-8'))

        nos_erro = result.Erro
        nos_alerta = result.Alerta

        sucesso = result.Cabecalho.Sucesso

        alertas = {}
        for n in nos_alerta:
            codigo = n.Codigo
            descricao = n.Descricao
            chave_rps = n.ChaveRPS
            if chave_rps:
                chave = chave_rps
            else:
                chave = n.ChaveNFe

            try:
                alertas[chave].append((codigo, descricao))
            except KeyError:
                alertas[chave] = [(codigo, descricao)]

        erros = {}
        for n in nos_erro:
            codigo = n.Codigo
            descricao = n.Descricao
            if n.ChaveRPS:
                chave = n.ChaveRPS
            else:
                chave = n.ChaveNFe

            try:
                erros[chave].append((codigo, descricao))
            except KeyError:
                erros[chave] = [(codigo, descricao)]

        if self._destino:
            arq = open(self._destino + '-rec.xml', 'w')
            arq.write(resp_xml_str.encode(u'utf-8'))
            arq.close()

        os.remove(nome_arq_chave)
        os.remove(nome_arq_certificado)
        con.close()

        return (sucesso, result, alertas, erros)

    def _soap(self, xml, servico):
        return '''<?xml version="1.0" encoding="utf-8"?>
            <soap12:Envelope
                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
                <soap12:Body>
                    <{servico} xmlns="http://www.e-governeapps2.com.br/">
                        {xml}
                    </{servico}>
                </soap12:Body>
            </soap12:Envelope>
            '''.format(servico=servico, xml=xml).encode(u'utf-8')

    def _obter_xml_da_funcao(self, funcao, assinar=False, xsd=None):
        tmp_dir = u'/tmp/'
        tmp_file_path = tmp_dir + uuid4().hex
        tmp_file = open(tmp_file_path, 'w+')

        funcao.export(tmp_file, 0, namespacedef_=self.namespace)

        tmp_file.seek(0)
        xml = tmp_file.read()
        tmp_file.close()

        if assinar:
            xml = self._certificado.assina_xml(xml)

        return self._validar_xml(xml, xsd)

    def _remove_accents(self, data):
        return ''.join(x for x in unicodedata.normalize('NFKD', unicode(data))\
            if x in string.ascii_letters + ' ').lower()

    # FIXME: Verificar utilidade dos dois métodos abaixo
    def RemoveSoap(self, xml):
        for x in ('RecepcionarLoteRpsResponse', 'ConsultarLoteRpsResponse',
                  'CancelarNfseResponse'):
            xml = xml.replace(
                '<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><%s xmlns="http://www.e-governeapps2.com.br/">'
                % x, '')
            xml = xml.replace('</%s></soap:Body></soap:Envelope>' % x, '')

        return xml

    def Destino(self, emissao=None, serie=None, rps=None, arquivo=None):
        self._destino = None

        if arquivo is not None:
            destino = ('%s/%s/%03d-%09d' % (os.path.join(
                self.caminho, 'producao' if self.ambiente == 1 else
                'homologacao'), emissao.strftime('%Y-%m'), serie, rps))

            if not os.path.exists(destino):
                os.makedirs(destino)

            self._destino = os.path.join(destino, arquivo)

        return self._destino
Example #4
0
 def _obter_dados_do_certificado(self, certificado, senha):
     self._certificado = Certificado()
     self._certificado.arquivo = certificado
     self._certificado.senha = senha
     self._certificado.prepara_certificado_arquivo_pfx()
Example #5
0
class ProcessadorBase(object):
    def __init__(self, servidor, endereco, certificado, senha, caminho='', servidor_homologacao=''):
        self.servidor = servidor
        self.servidor_homologacao = servidor_homologacao
        self.endereco = endereco
        self.versao = u'1.00'
        self.caminho = caminho
        self._destino = None
        self._obter_dados_do_certificado(certificado, senha)
        self.NS = 'http://www.abrasf.org.br/ABRASF/arquivos/nfse.xsd'
        self.NS_SCHEMA = 'http://www.w3.org/2001/XMLSchema-instance'
        self.namespace = 'xmlns="{}" xmlns:xsi="{}"'.format(self.NS, self.NS_SCHEMA)

    def _obter_dados_do_certificado(self, certificado, senha):
        self._certificado = Certificado()
        self._certificado.arquivo = certificado
        self._certificado.senha = senha
        self._certificado.prepara_certificado_arquivo_pfx()

    def _remover_encode(self, xml):
        aberturas = ('<?xml version="1.0" encoding="utf-8"?>',
            '<?xml version="1.0" encoding="utf-8" ?>',
            '<?xml version="1.0" encoding="UTF-8"?>',
            '<?xml version="1.0" encoding="UTF-8" ?>')

        for a in aberturas:
            xml = xml.replace(a, '')

        return xml

    def _validar_xml(self, xml, xsd=None):
        xml = self._remover_encode(xml)
        curdir = os.getcwd()
        try:
            xsd_path = os.path.join(os.path.dirname(__file__), 'nfse.xsd')
            esquema = etree.XMLSchema(etree.parse(xsd_path))
        finally:
            os.chdir(curdir)
        esquema.assertValid(etree.fromstring(xml))
        return xml

    def _soap_post(self, connection, xml, xsd_retorno, servico=None):
        connection.request(u'POST', self.endereco, xml, {
            u'Content-Type': u'application/soap+xml; charset=utf-8',
            u'Content-Length': len(xml),
            })

        if self._destino:
            arq = open(self._destino + '-env.xml', 'w')
            arq.write(xml.encode(u'utf-8'))
            arq.close()

        resposta = connection.getresponse()

        if resposta.status != 200:
            raise CommunicationError(resposta.status, resposta.reason)

        resp_xml_str = resposta.read()
        resp_xml = ET.fromstring(resp_xml_str)
        result_str = resp_xml.find(".//{%s}RetornoXML" % self.NS).text
        xsd_retorno.ExternalEncoding = 'utf-8'
        result = xsd_retorno.parseString(result_str.encode('utf-8'))
        
        return result

    def _parse_result(self, result):
        nos_erro = result.Erro
        nos_alerta = result.Alerta

        sucesso = result.Cabecalho.Sucesso

        alertas = {}
        for n in nos_alerta:
            codigo = n.Codigo
            descricao = n.Descricao
            chave_rps = n.ChaveRPS
            if chave_rps:
                chave = chave_rps
            else:
                chave = n.ChaveNFe

            #TODO a chave esta vindo como None, estou setando para o codigo indice, desta forma nao sera possivel enviar em lote.
            try:
                alertas[codigo].append((descricao))
            except KeyError:
                alertas[codigo] = [(descricao)]

        erros = {}
        for n in nos_erro:
            codigo = n.Codigo
            descricao = n.Descricao
            if n.ChaveRPS:
                chave = n.ChaveRPS
            else:
                chave = n.ChaveNFe

            #TODO a chave esta vindo como None, estou setando para o codigo indice, desta forma nao sera possivel enviar em lote.
            try:
                erros[codigo].append((descricao))
            except KeyError:
                erros[codigo] = [(descricao)]

        return (sucesso, erros, alertas)

    def _conectar_servidor(self, xml, service, xsd_retorno, test=False):
        server = test and self.servidor_homologacao or self.servidor

        caminho_temporario = u'/tmp/'
        key_file = caminho_temporario + uuid4().hex
        arq_tmp = open(key_file, 'w')
        arq_tmp.write(self._certificado.chave)
        arq_tmp.close()

        cert_file = caminho_temporario + uuid4().hex
        arq_tmp = open(cert_file, 'w')
        arq_tmp.write(self._certificado.certificado)
        arq_tmp.close()

        xml = self._soap(xml, service)

        connection = HTTPSConnection(server, key_file=key_file, cert_file=cert_file)
        result = self._soap_post(connection, xml, xsd_retorno, service)

        sucesso, erros, alertas = self._parse_result(result)

        if self._destino:
            arq = open(self._destino + '-rec.xml', 'w')
            arq.write(resp_xml_str.encode(u'utf-8'))
            arq.close()

        os.remove(key_file)
        os.remove(cert_file)
        connection.close()

        return (sucesso, result, alertas, erros)

    def _soap(self, xml, servico):
        return '''<?xml version="1.0" encoding="utf-8"?>
            <soap12:Envelope
                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
                <soap12:Body>
                    <{servico} xmlns="http://www.e-governeapps2.com.br/">
                        {xml}
                    </{servico}>
                </soap12:Body>
            </soap12:Envelope>
            '''.format(servico=servico, xml=xml).encode(u'utf-8')

    def _obter_xml_da_funcao(self, funcao, assinar=False, xsd=None):
        tmp_dir = u'/tmp/'
        tmp_file_path = tmp_dir + uuid4().hex
        tmp_file = open(tmp_file_path, 'w+')

        funcao.export(tmp_file, 0, namespacedef_=self.namespace)

        tmp_file.seek(0)
        xml = tmp_file.read()
        tmp_file.close()

        if assinar:
            xml = self._certificado.assina_xml(xml)
        return self._validar_xml(xml, xsd)

    def _remove_accents(self, data):
        return ''.join(x for x in unicodedata.normalize('NFKD', unicode(data))\
            if x in string.ascii_letters + ' ').lower()

    # FIXME: Verificar utilidade dos dois métodos abaixo
    def RemoveSoap(self, xml):
        for x in ('RecepcionarLoteRpsResponse', 'ConsultarLoteRpsResponse', 'CancelarNfseResponse'):
            xml = xml.replace('<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><%s xmlns="http://www.e-governeapps2.com.br/">' % x, '')
            xml = xml.replace('</%s></soap:Body></soap:Envelope>' % x, '')

        return xml

    def Destino(self, emissao=None, serie=None, rps=None, arquivo=None):
        self._destino = None

        if arquivo is not None:
            destino = ('%s/%s/%03d-%09d' % (os.path.join(self.caminho, 'producao' if self.ambiente == 1 else 'homologacao'), emissao.strftime('%Y-%m'), serie, rps))

            if not os.path.exists(destino):
                os.makedirs(destino)

            self._destino = os.path.join(destino, arquivo)

        return self._destino
Example #6
0
class ProcessadorNFe(object):
    def __init__(self):
        self.ambiente = 2
        self.estado = 'SP'
        self.versao = '2.00'
        self.certificado = Certificado()
        self.caminho = ''
        self.salvar_arquivos = True
        self.contingencia_SCAN = False
        self.gerar_danfe = True
        self.danfe = DANFE()
        self.caminho_temporario = ''
        self.maximo_tentativas_consulta_recibo = 5

        self._servidor     = ''
        self._url          = ''
        self._soap_envio   = None
        self._soap_retorno = None

    def _conectar_servico(self, servico, envio, resposta, ambiente=None, somente_ambiente_nacional=False):
        if ambiente is None:
            ambiente = self.ambiente

        if self.versao == u'1.10':
            metodo_ws = webservices_1.METODO_WS
            self._soap_envio   = SOAPEnvio_110()
            self._soap_retorno = SOAPRetorno_110()

            if self.contingencia_SCAN:
                self._servidor = webservices_1.SCAN[ambiente]['servidor']
                self._url      = webservices_1.SCAN[ambiente][servico]
            else:
                self._servidor = webservices_1.ESTADO_WS[self.estado][ambiente]['servidor']
                self._url      = webservices_1.ESTADO_WS[self.estado][ambiente][servico]

        elif self.versao == u'2.00':
            metodo_ws = webservices_2.METODO_WS
            self._soap_envio   = SOAPEnvio_200()
            self._soap_retorno = SOAPRetorno_200()
            self._soap_envio.cUF = UF_CODIGO[self.estado]

            if somente_ambiente_nacional:
                self._servidor = webservices_2.AN[ambiente]['servidor']
                self._url      = webservices_2.AN[ambiente][servico]

            elif servico == WS_NFE_DOWNLOAD:
                self._servidor = webservices_2.SVAN[ambiente]['servidor']
                self._url      = webservices_2.SVAN[ambiente][servico]

            elif self.contingencia_SCAN:
                self._servidor = webservices_2.SCAN[ambiente]['servidor']
                self._url      = webservices_2.SCAN[ambiente][servico]

            else:
                #
                # Testa a opção de um estado, para determinado serviço, usar o WS
                # de outro estado
                #
                if type(webservices_2.ESTADO_WS[self.estado][ambiente][servico]) == dict:
                    ws_a_usar = webservices_2.ESTADO_WS[self.estado][ambiente][servico]
                else:
                    ws_a_usar = webservices_2.ESTADO_WS[self.estado]

                self._servidor = ws_a_usar[ambiente]['servidor']
                self._url      = ws_a_usar[ambiente][servico]

        self._soap_envio.webservice = metodo_ws[servico]['webservice']
        self._soap_envio.metodo     = metodo_ws[servico]['metodo']
        self._soap_envio.envio      = envio

        self._soap_retorno.webservice = self._soap_envio.webservice
        self._soap_retorno.metodo     = self._soap_envio.metodo
        self._soap_retorno.resposta   = resposta

        #try:
        self.certificado.prepara_certificado_arquivo_pfx()

        #
        # Salva o certificado e a chave privada para uso na conexão HTTPS
        # Salvamos como um arquivo de nome aleatório para evitar o conflito
        # de uso de vários certificados e chaves diferentes na mesma máquina
        # ao mesmo tempo
        #
        self.caminho_temporario = self.caminho_temporario or '/tmp/'

        nome_arq_chave = self.caminho_temporario + uuid4().hex
        arq_tmp = open(nome_arq_chave, 'w')
        arq_tmp.write(self.certificado.chave)
        arq_tmp.close()

        nome_arq_certificado = self.caminho_temporario + uuid4().hex
        arq_tmp = open(nome_arq_certificado, 'w')
        arq_tmp.write(self.certificado.certificado)
        arq_tmp.close()

        #con = HTTPSConnection(self._servidor, key_file=nome_arq_chave, cert_file=nome_arq_certificado)
        con = ConexaoHTTPS(self._servidor, key_file=nome_arq_chave, cert_file=nome_arq_certificado)
        #con.request('POST', '/' + self._url, self._soap_envio.xml.decode('utf-8'), self._soap_envio.header)
        #
        # É preciso definir o POST abaixo como bytestring, já que importamos
        # os unicode_literals... Dá um pau com xml com acentos sem isso...
        #
        con.request(b'POST', b'/' + self._url.encode('utf-8'), self._soap_envio.xml.encode('utf-8'), self._soap_envio.header)
        resp = con.getresponse()

        #
        # Apagamos os arquivos do certificado e o da chave privada, para evitar
        # um potencial risco de segurança; muito embora o uso da chave privada
        # para assinatura exija o uso da senha, pode haver serviços que exijam
        # apenas o uso do certificado para validar a identidade, independente
        # da existência de assinatura digital
        #
        os.remove(nome_arq_chave)
        os.remove(nome_arq_certificado)

        # Dados do envelope de envio salvos para possível debug
        envio.original = self._soap_envio.xml

        # Dados da resposta salvos para possível debug
        self._soap_retorno.resposta.version  = resp.version
        self._soap_retorno.resposta.status   = resp.status
        self._soap_retorno.resposta.reason   = unicode(resp.reason.decode('utf-8'))
        self._soap_retorno.resposta.msg      = resp.msg
        self._soap_retorno.resposta.original = unicode(resp.read().decode('utf-8'))

        # Tudo certo!
        if self._soap_retorno.resposta.status == 200:
            self._soap_retorno.xml = self._soap_retorno.resposta.original
        #except Exception, e:
            #raise e
        #else:
        con.close()

    def enviar_lote(self, numero_lote=None, lista_nfes=[]):
        if self.versao == u'1.10':
            envio = EnviNFe_110()
            resposta = RetEnviNFe_110()

        elif self.versao == u'2.00':
            envio = EnviNFe_200()
            resposta = RetEnviNFe_200()

            if self.ambiente == 2: # Homologação tem detalhes especificos desde a NT2011_002
                for nfe in lista_nfes:
                    nfe.infNFe.dest.CNPJ.valor = '99999999000191'
                    nfe.infNFe.dest.xNome.valor = 'NF-E EMITIDA EM AMBIENTE DE HOMOLOGACAO - SEM VALOR FISCAL'
                    nfe.infNFe.dest.IE.valor = ''

        processo = ProcessoNFe(webservice=WS_NFE_ENVIO_LOTE, envio=envio, resposta=resposta)

        #
        # Vamos assinar e validar todas as NF-e antes da transmissão, evitando
        # rejeição na SEFAZ por incorreção no schema dos arquivos
        #
        for nfe in lista_nfes:
            self.certificado.assina_xmlnfe(nfe)
            nfe.validar()

        envio.NFe = lista_nfes

        if numero_lote is None:
            numero_lote = datetime.now().strftime('%Y%m%d%H%M%S')

        envio.idLote.valor = numero_lote

        envio.validar()
        if self.salvar_arquivos:
            for n in lista_nfes:
                n.monta_chave()
                arq = open(self.caminho + n.chave + '-nfe.xml', 'w')
                arq.write(n.xml.encode('utf-8'))
                arq.close

            arq = open(self.caminho + unicode(envio.idLote.valor).strip().rjust(15, '0') + '-env-lot.xml', 'w')
            arq.write(envio.xml.encode('utf-8'))
            arq.close()

        self._conectar_servico(WS_NFE_ENVIO_LOTE, envio, resposta)

        #resposta.validar()
        if self.salvar_arquivos:
            nome_arq = self.caminho + unicode(envio.idLote.valor).strip().rjust(15, '0') + '-rec'

            if resposta.cStat.valor != '103':
                nome_arq += '-rej.xml'
            else:
                nome_arq += '.xml'

            arq = open(nome_arq, 'w')
            arq.write(resposta.xml.encode('utf-8'))
            arq.close()

        return processo

    def consultar_recibo(self, ambiente=None, numero_recibo=None):
        if self.versao == u'1.10':
            envio = ConsReciNFe_110()
            resposta = RetConsReciNFe_110()

        elif self.versao == u'2.00':
            envio = ConsReciNFe_200()
            resposta = RetConsReciNFe_200()

        processo = ProcessoNFe(webservice=WS_NFE_CONSULTA_RECIBO, envio=envio, resposta=resposta)

        if ambiente is None:
            ambiente = self.ambiente

        envio.tpAmb.valor = ambiente
        envio.nRec.valor  = numero_recibo

        envio.validar()
        if self.salvar_arquivos:
            arq = open(self.caminho + unicode(envio.nRec.valor).strip().rjust(15, '0') + '-ped-rec.xml', 'w')
            arq.write(envio.xml.encode('utf-8'))
            arq.close()

        self._conectar_servico(WS_NFE_CONSULTA_RECIBO, envio, resposta, ambiente)

        #resposta.validar()
        if self.salvar_arquivos:
            nome_arq = self.caminho + unicode(envio.nRec.valor).strip().rjust(15, '0') + '-pro-rec'

            if resposta.cStat.valor != '104':
                nome_arq += '-rej.xml'
            else:
                nome_arq += '.xml'

            arq = open(nome_arq, 'w')
            arq.write(resposta.xml.encode('utf-8'))
            arq.close()

            #
            # Salvar os resultados dos processamentos
            #
            for pn in resposta.protNFe:
                nome_arq = self.caminho + unicode(pn.infProt.chNFe.valor).strip().rjust(44, '0') + '-pro-nfe-'

                # NF-e autorizada
                if pn.infProt.cStat.valor == '100':
                    nome_arq += 'aut.xml'

                # NF-e denegada
                elif pn.infProt.cStat.valor in ('110', '301', '302'):
                    nome_arq += 'den.xml'

                # NF-e rejeitada
                else:
                    nome_arq += 'rej.xml'

                arq = open(nome_arq, 'w')
                arq.write(pn.xml.encode('utf-8'))
                arq.close()

        return processo

    def cancelar_nota(self, ambiente=None, chave_nfe=None, numero_protocolo=None, justificativa=None):
        if self.versao == u'1.10':
            envio = CancNFe_107()
            resposta = RetCancNFe_107()

        elif self.versao == u'2.00':
            envio = CancNFe_200()
            resposta = RetCancNFe_200()

        processo = ProcessoNFe(webservice=WS_NFE_CANCELAMENTO, envio=envio, resposta=resposta)

        if ambiente is None:
            ambiente = self.ambiente

        self.caminho = self.monta_caminho_nfe(ambiente=ambiente, chave_nfe=chave_nfe)

        envio.infCanc.tpAmb.valor = ambiente
        envio.infCanc.chNFe.valor = chave_nfe
        envio.infCanc.nProt.valor = numero_protocolo
        envio.infCanc.xJust.valor = justificativa

        self.certificado.assina_xmlnfe(envio)

        envio.validar()
        if self.salvar_arquivos:
            arq = open(self.caminho + unicode(envio.infCanc.chNFe.valor).strip().rjust(44, '0') + '-ped-can.xml', 'w')
            arq.write(envio.xml.encode('utf-8'))
            arq.close()

        self._conectar_servico(WS_NFE_CANCELAMENTO, envio, resposta, ambiente)

        #resposta.validar()

        #
        # Se for autorizado, monta o processo de cancelamento
        # 101 - cancelado dentro do prazo
        # 151 - cancelado fora do prazo
        #
        if resposta.infCanc.cStat.valor in ('101', '151'):
            if self.versao == u'1.10':
                processo_cancelamento_nfe = ProcCancNFe_107()

            elif self.versao == u'2.00':
                processo_cancelamento_nfe = ProcCancNFe_200()

            nome_arq = self.caminho + unicode(envio.infCanc.chNFe.valor).strip().rjust(44, '0') + '-proc-canc-nfe.xml'
            processo_cancelamento_nfe.cancNFe = envio
            processo_cancelamento_nfe.retCancNFe = resposta

            processo_cancelamento_nfe.validar()

            processo.processo_cancelamento_nfe = processo_cancelamento_nfe

        if self.salvar_arquivos:
            nome_arq = self.caminho + unicode(envio.infCanc.chNFe.valor).strip().rjust(44, '0') + '-pro-can-'

            # Cancelamento autorizado
            if resposta.infCanc.cStat.valor == '101':
                nome_arq += 'aut.xml'
            else:
                nome_arq += 'rej.xml'

            arq = open(nome_arq, 'w')
            arq.write(resposta.xml.encode('utf-8'))
            arq.close()

            # Se for autorizado, monta o processo de cancelamento
            if resposta.infCanc.cStat.valor == '101':
                nome_arq = self.caminho + unicode(envio.infCanc.chNFe.valor).strip().rjust(44, '0') + '-proc-canc-nfe.xml'
                arq = open(nome_arq, 'w')
                arq.write(processo_cancelamento_nfe.xml.encode('utf-8'))
                arq.close()

                # Estranhamente, o nome desse arquivo, pelo manual, deve ser chave-can.xml
                nome_arq = self.caminho + unicode(envio.infCanc.chNFe.valor).strip().rjust(44, '0') + '-can.xml'
                arq = open(nome_arq, 'w')
                arq.write(processo_cancelamento_nfe.xml.encode('utf-8'))
                arq.close()

        return processo

    def inutilizar_nota(self, ambiente=None, codigo_estado=None, ano=None, cnpj=None, serie=None, numero_inicial=None, numero_final=None, justificativa=None):
        if self.versao == u'1.10':
            envio = InutNFe_107()
            resposta = RetInutNFe_107()

        elif self.versao == u'2.00':
            envio = InutNFe_200()
            resposta = RetInutNFe_200()

        processo = ProcessoNFe(webservice=WS_NFE_INUTILIZACAO, envio=envio, resposta=resposta)

        if ambiente is None:
            ambiente = self.ambiente

        if codigo_estado is None:
            codigo_estado = UF_CODIGO[self.estado]

        if ano is None:
            ano = datetime.now().strftime('%y')

        if numero_final is None:
            numero_final = numero_inicial

        self.caminho = self.monta_caminho_inutilizacao(ambiente=ambiente, serie=serie, numero_inicial=numero_inicial, numero_final=numero_final)

        envio.infInut.tpAmb.valor  = ambiente
        envio.infInut.cUF.valor    = codigo_estado
        envio.infInut.ano.valor    = ano
        envio.infInut.CNPJ.valor   = cnpj
        #envio.infInut.mod.valor    = 55
        envio.infInut.serie.valor  = serie
        envio.infInut.nNFIni.valor = numero_inicial
        envio.infInut.nNFFin.valor = numero_final
        envio.infInut.xJust.valor  = justificativa

        envio.gera_nova_chave()
        self.certificado.assina_xmlnfe(envio)

        envio.validar()
        if self.salvar_arquivos:
            arq = open(self.caminho + unicode(envio.chave).strip().rjust(41, '0') + '-ped-inu.xml', 'w')
            arq.write(envio.xml.encode('utf-8'))
            arq.close()

        self._conectar_servico(WS_NFE_INUTILIZACAO, envio, resposta, ambiente)

        #resposta.validar()

        # Se for autorizada, monta o processo de inutilização
        if resposta.infInut.cStat.valor == '102':
            if self.versao == u'1.10':
                processo_inutilizacao_nfe = ProcInutNFe_107()

            elif self.versao == u'2.00':
                processo_inutilizacao_nfe = ProcInutNFe_200()

            processo_inutilizacao_nfe.inutNFe = envio
            processo_inutilizacao_nfe.retInutNFe = resposta

            processo_inutilizacao_nfe.validar()

            processo.processo_inutilizacao_nfe = processo_inutilizacao_nfe

        if self.salvar_arquivos:
            nome_arq = self.caminho + unicode(envio.chave).strip().rjust(41, '0') + '-pro-inu-'

            # Inutilização autorizada
            if resposta.infInut.cStat.valor == '102':
                nome_arq += 'aut.xml'
            else:
                nome_arq += 'rej.xml'

            arq = open(nome_arq, 'w')
            arq.write(resposta.xml.encode('utf-8'))
            arq.close()

            # Se for autorizada, monta o processo de inutilização
            if resposta.infInut.cStat.valor == '102':
                nome_arq = self.caminho + unicode(envio.chave).strip().rjust(41, '0') + '-proc-inut-nfe.xml'
                arq = open(nome_arq, 'w')
                arq.write(processo_inutilizacao_nfe.xml.encode('utf-8'))
                arq.close()

                # Estranhamente, o nome desse arquivo, pelo manual, deve ser chave-inu.xml
                nome_arq = self.caminho + unicode(envio.chave).strip().rjust(41, '0') + '-inu.xml'
                arq = open(nome_arq, 'w')
                arq.write(processo_inutilizacao_nfe.xml.encode('utf-8'))
                arq.close()

        return processo

    def consultar_nota(self, ambiente=None, chave_nfe=None, nfe=None):
        if self.versao == u'1.10':
            envio = ConsSitNFe_107()
            resposta = RetConsSitNFe_107()

        elif self.versao == u'2.00':
            envio = ConsSitNFe_201()
            resposta = RetConsSitNFe_201()

        processo = ProcessoNFe(webservice=WS_NFE_CONSULTA, envio=envio, resposta=resposta)

        if ambiente is None:
            ambiente = self.ambiente

        caminho_original = self.caminho
        self.caminho = self.monta_caminho_nfe(ambiente, chave_nfe)

        envio.tpAmb.valor = ambiente
        envio.chNFe.valor = chave_nfe

        envio.validar()
        if self.salvar_arquivos:
            arq = open(self.caminho + unicode(chave_nfe).strip().rjust(44, '0') + '-ped-sit.xml', 'w')
            arq.write(envio.xml.encode('utf-8'))
            arq.close()

        self._conectar_servico(WS_NFE_CONSULTA, envio, resposta, ambiente)

        #resposta.validar()
        if self.salvar_arquivos:
            nome_arq = self.caminho + unicode(chave_nfe).strip().rjust(44, '0') + '-sit.xml'
            arq = open(nome_arq, 'w')
            arq.write(resposta.xml.encode('utf-8'))
            arq.close()

        self.caminho = caminho_original
        #
        # Se a NF-e tiver sido informada, montar o processo da NF-e
        #
        if nfe:
            nfe.procNFe = self.montar_processo_uma_nota(nfe, protnfe_recibo=resposta.protNFe)

        return processo

    def consultar_servico(self, ambiente=None, codigo_estado=None):
        if self.versao == u'1.10':
            envio = ConsStatServ_107()
            resposta = RetConsStatServ_107()

        elif self.versao == u'2.00':
            envio = ConsStatServ_200()
            resposta = RetConsStatServ_200()

        processo = ProcessoNFe(webservice=WS_NFE_SITUACAO, envio=envio, resposta=resposta)

        if ambiente is None:
            ambiente = self.ambiente

        if codigo_estado is None:
            codigo_estado = UF_CODIGO[self.estado]

        envio.tpAmb.valor = ambiente
        envio.cUF.valor   = codigo_estado
        envio.data        = datetime.now()

        envio.validar()
        if self.salvar_arquivos:
            arq = open(self.caminho + envio.data.strftime('%Y%m%dT%H%M%S') + '-ped-sta.xml', 'w')
            arq.write(envio.xml.encode('utf-8'))
            arq.close()

        self._conectar_servico(WS_NFE_SITUACAO, envio, resposta, ambiente)

        #resposta.validar()
        if self.salvar_arquivos:
            arq = open(self.caminho + envio.data.strftime('%Y%m%dT%H%M%S') + '-sta.xml', 'w')
            arq.write(resposta.xml.encode('utf-8'))
            arq.close()

        return processo

    def processar_notas(self, lista_nfes):
        #
        # Definir o caminho geral baseado na 1ª NF-e
        #
        caminho_original = self.caminho
        nfe = lista_nfes[0]
        nfe.monta_chave()
        self.caminho = caminho_original
        ambiente = nfe.infNFe.ide.tpAmb.valor
        self.caminho = self.monta_caminho_nfe(ambiente=nfe.infNFe.ide.tpAmb.valor, chave_nfe=nfe.chave)

        proc_servico = self.consultar_servico(ambiente=ambiente)
        yield proc_servico

        #
        # Serviço em operação?
        #
        if proc_servico.resposta.cStat.valor == '107':
            #
            # Verificar se as notas já não foram emitadas antes
            #
            for nfe in lista_nfes:
                nfe.monta_chave()
                self.caminho = caminho_original
                proc_consulta = self.consultar_nota(ambiente=nfe.infNFe.ide.tpAmb.valor, chave_nfe=nfe.chave)
                yield proc_consulta

            #
            # Nenhuma das notas estava já enviada, enviá-las então
            #
            nfe = lista_nfes[0]
            nfe.monta_chave()
            self.caminho = caminho_original
            self.caminho = self.monta_caminho_nfe(ambiente=nfe.infNFe.ide.tpAmb.valor, chave_nfe=nfe.chave)
            proc_envio = self.enviar_lote(lista_nfes=lista_nfes)
            yield proc_envio

            ret_envi_nfe = proc_envio.resposta

            #
            # Deu certo?
            #
            if ret_envi_nfe.cStat.valor == '103':
                #
                # Aguarda o tempo do processamento antes da consulta
                #
                time.sleep(ret_envi_nfe.infRec.tMed.valor * 1.3)

                proc_recibo = self.consultar_recibo(ambiente=ret_envi_nfe.tpAmb.valor, numero_recibo=ret_envi_nfe.infRec.nRec.valor)

                #
                # Tenta receber o resultado do processamento do lote
                #
                tentativa = 0
                while proc_recibo.resposta.cStat.valor == '105' and tentativa < self.maximo_tentativas_consulta_recibo:
                    time.sleep(ret_envi_nfe.infRec.tMed.valor * 1.5)
                    tentativa += 1
                    proc_recibo = self.consultar_recibo(ambiente=ret_envi_nfe.tpAmb.valor, numero_recibo=ret_envi_nfe.infRec.nRec.valor)

                # Montar os processos das NF-es
                dic_protNFe = proc_recibo.resposta.dic_protNFe
                dic_procNFe = proc_recibo.resposta.dic_procNFe

                self.caminho = caminho_original
                self.montar_processo_lista_notas(lista_nfes, dic_protNFe, dic_procNFe)

                yield proc_recibo

    def montar_processo_lista_notas(self, lista_nfes, dic_protNFe, dic_procNFe):
        for nfe in lista_nfes:
            if nfe.chave in dic_protNFe:
                protocolo = dic_protNFe[nfe.chave]
                processo = self.montar_processo_uma_nota(nfe, protnfe_recibo=protocolo)

                if processo is not None:
                    dic_procNFe[nfe.chave] = processo

    def montar_processo_uma_nota(self, nfe, protnfe_recibo=None, protnfe_consulta_110=None, retcancnfe=None):
        #
        # Somente para a versão 1.10
        # Caso processarmos o protocolo vindo de uma consulta,
        # temos que converter esse protocolo no formato
        # do protocolo que retorna quando o recibo do lote é consultado.
        #
        # Sim, as informações são as mesmas, mas o leiaute não...
        # Vai entender...
        #
        if protnfe_consulta_110 is not None:
            protnfe_recibo = ProtNFe_110()
            protnfe_recibo.infProt.tpAmb.valor = protnfe_consulta_110.infProt.tpAmb.valor
            protnfe_recibo.infProt.verAplic.valor = protnfe_consulta_110.infProt.verAplic.valor
            protnfe_recibo.infProt.chNFe.valor = protnfe_consulta_110.infProt.chNFe.valor
            protnfe_recibo.infProt.dhRecbto.valor = protnfe_consulta_110.infProt.dhRecbto.valor
            protnfe_recibo.infProt.cStat.valor = protnfe_consulta_110.infProt.cStat.valor
            protnfe_recibo.infProt.xMotivo.valor = protnfe_consulta_110.infProt.xMotivo.valor
            protnfe_recibo.infProt.nProt.valor = protnfe_consulta_110.infProt.nProt.valor
            protnfe_recibo.infProt.digVal.valor = protnfe_consulta_110.infProt.digVal.valor

        caminho_original = self.caminho
        self.caminho = self.monta_caminho_nfe(ambiente=nfe.infNFe.ide.tpAmb.valor, chave_nfe=nfe.chave)

        processo = None
        #
        # Se nota foi autorizada ou denegada
        # 100 - autorizada
        # 150 - autorizada fora do prazo
        # 110 - denegada
        # 301 - denegada por irregularidade do emitente
        # 302 - denegada por irregularidade do destinatário
        #
        if protnfe_recibo.infProt.cStat.valor in ('100', '104', '150', '110',
                                                  '301', '302', '204'):
            if self.versao == u'1.10':
                processo = ProcNFe_110()

            elif self.versao == u'2.00':
                processo = ProcNFe_200()

            processo.NFe     = nfe
            processo.protNFe = protnfe_recibo

            if self.gerar_danfe:
                self.danfe.NFe     = nfe
                self.danfe.protNFe = protnfe_recibo
                self.danfe.salvar_arquivo = self.salvar_arquivos
                self.danfe.gerar_danfe()
                processo.danfe_pdf = self.danfe.conteudo_pdf

            if self.salvar_arquivos:
                nome_arq = self.caminho + unicode(nfe.chave).strip().rjust(44, '0') + '-proc-nfe.xml'
                arq = open(nome_arq, 'w')
                arq.write(processo.xml.encode('utf-8'))
                arq.close()

                # Estranhamente, o nome desse arquivo, pelo manual, deve ser chave-nfe.xml ou chave-den.xml
                # para notas denegadas
                if protnfe_recibo.infProt.cStat.valor in ('100', '150'):
                    nome_arq = self.caminho + unicode(nfe.chave).strip().rjust(44, '0') + '-nfe.xml'
                else:
                    nome_arq = self.caminho + unicode(nfe.chave).strip().rjust(44, '0') + '-den.xml'

                arq = open(nome_arq, 'w')
                arq.write(processo.xml.encode('utf-8'))
                arq.close()

                if self.gerar_danfe:
                    nome_arq = self.caminho + unicode(nfe.chave).strip().rjust(44, '0') + '.pdf'
                    arq = open(nome_arq, 'w')
                    arq.write(processo.danfe_pdf)
                    arq.close()

        self.caminho = caminho_original
        return processo

    def monta_caminho_nfe(self, ambiente, chave_nfe):
        caminho = self.caminho

        if ambiente == 1:
            caminho = os.path.join(caminho, 'producao/')
        else:
            caminho = os.path.join(caminho, 'homologacao/')

        data = '20' + chave_nfe[2:4] + '-' + chave_nfe[4:6]
        serie = chave_nfe[22:25]
        numero = chave_nfe[25:34]

        caminho = os.path.join(caminho, data + '/')
        caminho = os.path.join(caminho, serie + '-' + numero + '/')

        try:
            os.makedirs(caminho)
        except:
            pass

        return caminho

    def monta_caminho_inutilizacao(self, ambiente=None, data=None, serie=None, numero_inicial=None, numero_final=None):
        caminho = self.caminho

        if ambiente == 1:
            caminho = os.path.join(caminho, 'producao/')
        else:
            caminho = os.path.join(caminho, 'homologacao/')

        if data is None:
            data = datetime.now()

        caminho = os.path.join(caminho, data.strftime('%Y-%m') + '/')

        serie          = unicode(serie).strip().rjust(3, '0')
        numero_inicial = unicode(numero_inicial).strip().rjust(9, '0')
        numero_final   = unicode(numero_final).strip().rjust(9, '0')

        caminho = os.path.join(caminho, serie + '-' + numero_inicial + '-' + numero_final + '/')

        try:
            os.makedirs(caminho)
        except:
            pass

        return caminho

    def montar_processo_lista_eventos(self, lista_eventos, dic_retEvento, dic_procEvento, classe_procEvento):
        for evento in lista_eventos:
            chave = evento.infEvento.chNFe.valor
            if chave in dic_retEvento:
                retorno = dic_retEvento[chave]
                processo = classe_procEvento()
                processo.evento = evento
                processo.retEvento = retorno
                dic_procEvento[chave] = processo

    def _enviar_lote_evento(self, tipo_evento, numero_lote=None, lista_eventos=[]):
        #
        # Determina o tipo do evento
        #
        if tipo_evento == 'cce':
            classe_evento = ProcEventoCCe_100
            envio = EnvEventoCCe_100()
            resposta = RetEnvEventoCCe_100()

        elif tipo_evento == 'can':
            classe_evento = ProcEventoCancNFe_100
            envio = EnvEventoCancNFe_100()
            resposta = RetEnvEventoCancNFe_100()

        elif tipo_evento == 'confrec':
            classe_evento = ProcEventoConfRecebimento_100
            envio = EnvEventoConfRecebimento_100()
            resposta = RetEnvEventoConfRecebimento_100()

        processo = ProcessoNFe(webservice=WS_NFE_RECEPCAO_EVENTO, envio=envio, resposta=resposta)

        #
        # Vamos assinar e validar todas os Eventos antes da transmissão, evitando
        # rejeição na SEFAZ por incorreção no schema dos arquivos
        #
        for evento in lista_eventos:
            #
            # No caso de eventos de confirmação de recebimento, só é possível o
            # envio para o ambiente nacional, então é preciso forçar o cOrgao
            # nos eventos
            #
            if tipo_evento == 'confrec':
                evento.infEvento.cOrgao.valor = UF_CODIGO['RFB']

            self.certificado.assina_xmlnfe(evento)
            #evento.validar()

        envio.evento = lista_eventos

        if numero_lote is None:
            numero_lote = datetime.now().strftime('%Y%m%d%H%M%S')

        envio.idLote.valor = numero_lote

        envio.validar()
        if self.salvar_arquivos:
            for evento in lista_eventos:
                chave = evento.infEvento.chNFe.valor
                ambiente = evento.infEvento.tpAmb.valor
                caminho = self.monta_caminho_nfe(ambiente=ambiente, chave_nfe=chave)
                numero_sequencia = evento.infEvento.nSeqEvento.valor
                nome_arq = caminho + chave + '-' + unicode(numero_sequencia).zfill(2)
                arq = open(nome_arq + '-' + tipo_evento + '.xml', 'w')
                arq.write(evento.xml.encode('utf-8'))
                arq.close

            arq = open(caminho + unicode(envio.idLote.valor).strip().rjust(15, '0') + '-env-' + tipo_evento + '.xml', 'w')
            arq.write(envio.xml.encode('utf-8'))
            arq.close()

        self._conectar_servico(WS_NFE_RECEPCAO_EVENTO, envio, resposta, somente_ambiente_nacional=tipo_evento=='confrec')

        #resposta.validar()
        if self.salvar_arquivos:
            nome_arq = caminho + unicode(envio.idLote.valor).strip().rjust(15, '0') + '-rec-' + tipo_evento

            if resposta.cStat.valor != '129':
                nome_arq += '-rej.xml'
            else:
                nome_arq += '.xml'

            arq = open(nome_arq, 'w')
            arq.write(resposta.xml.encode('utf-8'))
            arq.close()

            self.montar_processo_lista_eventos(lista_eventos, processo.resposta.dic_retEvento, processo.resposta.dic_procEvento, classe_evento)

            #
            # Salva o processamento de cada arquivo
            #
            for ret in resposta.retEvento:
                chave = ret.infEvento.chNFe.valor
                ambiente = ret.infEvento.tpAmb.valor
                caminho = self.monta_caminho_nfe(ambiente=ambiente, chave_nfe=chave)
                nome_arq = caminho + ret.infEvento.chNFe.valor + '-' + unicode(ret.infEvento.nSeqEvento.valor).zfill(2)

                #
                # O evento foi aceito e vinculado à NF-e
                #
                if ret.infEvento.cStat.valor == '135':
                    arq = open(nome_arq + '-ret-' + tipo_evento + '.xml', 'w')
                    arq.write(ret.xml.encode('utf-8'))
                    arq.close

                    #
                    # Salva o processo do evento
                    #
                    arq = open(nome_arq + '-proc-' + tipo_evento + '.xml', 'w')
                    arq.write(processo.resposta.dic_procEvento[chave].xml.encode('utf-8'))
                    arq.close

                #
                # O evento foi aceito, mas não foi vinculado à NF-e
                #
                elif ret.infEvento.cStat.valor == '136':
                    arq = open(nome_arq + '-ret-' + tipo_evento + '-sv.xml', 'w') # -sv = sem vínculo
                    arq.write(ret.xml.encode('utf-8'))
                    arq.close

                    #
                    # Salva o processo do evento
                    #
                    arq = open(nome_arq + '-proc-' + tipo_evento + '.xml', 'w')
                    arq.write(processo.resposta.dic_procEvento[chave].xml.encode('utf-8'))
                    arq.close

                #
                # O evento foi aceito e vinculado à NF-e, é um cancelamento for do prazo
                #
                elif ret.infEvento.cStat.valor == '155':
                    arq = open(nome_arq + '-ret-' + tipo_evento + '.xml', 'w')
                    arq.write(ret.xml.encode('utf-8'))
                    arq.close

                    #
                    # Salva o processo do evento
                    #
                    arq = open(nome_arq + '-proc-' + tipo_evento + '.xml', 'w')
                    arq.write(processo.resposta.dic_procEvento[chave].xml.encode('utf-8'))
                    arq.close

                #
                # O evento foi rejeitado
                #
                else:
                    arq = open(nome_arq + '-ret-' + tipo_evento + '-rej.xml', 'w')
                    arq.write(ret.xml.encode('utf-8'))
                    arq.close

        return processo

    def enviar_lote_cce(self, numero_lote=None, lista_eventos=[]):
        return self._enviar_lote_evento('cce', numero_lote, lista_eventos)

    def enviar_lote_cancelamento(self, numero_lote=None, lista_eventos=[]):
        return self._enviar_lote_evento('can', numero_lote, lista_eventos)

    def enviar_lote_confirmacao_recebimento(self, numero_lote=None, lista_eventos=[]):
        return self._enviar_lote_evento('confrec', numero_lote, lista_eventos)

    def consultar_notas_destinadas(self, ambiente=None, cnpj=None, ultimo_nsu='0', tipo_emissao='0', tipo_nfe='0'):
        envio = ConsNFeDest_101()
        resposta = RetConsNFeDest_101()

        envio.tpAmb.valor = ambiente or self.ambiente
        envio.CNPJ.valor = cnpj
        envio.ultNSU.valor = ultimo_nsu
        envio.indNFe.valor = tipo_nfe
        envio.indEmi.valor = tipo_emissao

        processo = ProcessoNFe(webservice=WS_NFE_CONSULTA_DESTINADAS, envio=envio, resposta=resposta)

        numero_lote = datetime.now().strftime('%Y%m%d%H%M%S')

        envio.validar()
        if self.salvar_arquivos:
            arq = open(self.caminho + unicode(numero_lote).strip().rjust(15, '0') + '-consnfedest.xml', 'w')
            arq.write(envio.xml.encode('utf-8'))
            arq.close()

        self._conectar_servico(WS_NFE_CONSULTA_DESTINADAS, envio, resposta, somente_ambiente_nacional=True)

        #resposta.validar()
        if self.salvar_arquivos:
            arq = open(self.caminho + unicode(numero_lote).strip().rjust(15, '0') + '-consnfedest-resp.xml', 'w')
            arq.write(resposta.original.encode('utf-8'))
            arq.close()

        return processo

    def baixar_notas_destinadas(self, ambiente=None, cnpj=None, lista_chaves=[]):
        envio = DownloadNFe_100()
        resposta = RetDownloadNFe_100()

        envio.tpAmb.valor = ambiente or self.ambiente
        envio.CNPJ.valor = cnpj
        envio.chNFe = [TagChNFe_100(valor=ch) for ch in lista_chaves]

        processo = ProcessoNFe(webservice=WS_NFE_DOWNLOAD, envio=envio, resposta=resposta)

        numero_lote = datetime.now().strftime('%Y%m%d%H%M%S')

        envio.validar()
        if self.salvar_arquivos:
            arq = open(self.caminho + unicode(numero_lote).strip().rjust(15, '0') + '-downloadnfe.xml', 'w')
            arq.write(envio.xml.encode('utf-8'))
            arq.close()

        self._conectar_servico(WS_NFE_DOWNLOAD, envio, resposta)

        #resposta.validar()
        if self.salvar_arquivos:
            arq = open(self.caminho + unicode(numero_lote).strip().rjust(15, '0') + '-downloadnfe-resp.xml', 'w')
            arq.write(resposta.original.encode('utf-8'))
            arq.close()

        return processo

    def cancelar_nota_evento(self, ambiente=None, chave_nfe=None, numero_protocolo=None, justificativa=None):
        evento = EventoCancNFe_100()
        evento.infEvento.tpAmb.valor = ambiente or self.ambiente
        evento.infEvento.cOrgao.valor = UF_CODIGO[self.estado]
        evento.infEvento.CNPJ.valor = chave_nfe[6:20] # Extrai o CNPJ da própria chave da NF-e
        evento.infEvento.chNFe.valor = chave_nfe
        evento.infEvento.dhEvento.valor = datetime.now()
        evento.infEvento.detEvento.nProt.valor = numero_protocolo
        evento.infEvento.detEvento.xJust.valor = justificativa

        processo = self.enviar_lote_cancelamento(lista_eventos=[evento])
        return processo

    def corrigir_nota_evento(self, ambiente=None, chave_nfe=None, numero_sequencia=None, correcao=None):
        evento = EventoCCe_100()
        evento.infEvento.tpAmb.valor = ambiente or self.ambiente
        evento.infEvento.cOrgao.valor = UF_CODIGO[self.estado]
        evento.infEvento.CNPJ.valor = chave_nfe[6:20] # Extrai o CNPJ da própria chave da NF-e
        evento.infEvento.chNFe.valor = chave_nfe
        evento.infEvento.dhEvento.valor = datetime.now()
        evento.infEvento.detEvento.xCorrecao.valor = correcao
        evento.infEvento.nSeqEvento.valor = numero_sequencia or 1

        processo = self.enviar_lote_cce(lista_eventos=[evento])
        return processo

    def confirmar_operacao_evento(self, ambiente=None, chave_nfe=None, cnpj=None):
        evento = EventoConfRecebimento_100()
        evento.infEvento.tpAmb.valor = ambiente or self.ambiente
        evento.infEvento.cOrgao.valor = UF_CODIGO[self.estado]
        evento.infEvento.CNPJ.valor = cnpj
        evento.infEvento.chNFe.valor = chave_nfe
        evento.infEvento.dhEvento.valor = datetime.now()
        evento.infEvento.tpEvento.valor = CONF_RECEBIMENTO_CONFIRMAR_OPERACAO
        evento.infEvento.detEvento.descEvento.valor = DESCEVENTO_CONF_RECEBIMENTO[evento.infEvento.tpEvento.valor]

        processo = self.enviar_lote_confirmacao_recebimento(lista_eventos=[evento])
        return processo

    def conhecer_operacao_evento(self, ambiente=None, chave_nfe=None, cnpj=None):
        evento = EventoConfRecebimento_100()
        evento.infEvento.tpAmb.valor = ambiente or self.ambiente
        evento.infEvento.cOrgao.valor = UF_CODIGO[self.estado]
        evento.infEvento.CNPJ.valor = cnpj
        evento.infEvento.chNFe.valor = chave_nfe
        evento.infEvento.dhEvento.valor = datetime.now()
        evento.infEvento.tpEvento.valor = CONF_RECEBIMENTO_CIENCIA_OPERACAO
        evento.infEvento.detEvento.descEvento.valor = DESCEVENTO_CONF_RECEBIMENTO[evento.infEvento.tpEvento.valor]

        processo = self.enviar_lote_confirmacao_recebimento(lista_eventos=[evento])
        return processo

    def desconhecer_operacao_evento(self, ambiente=None, chave_nfe=None, cnpj=None):
        evento = EventoConfRecebimento_100()
        evento.infEvento.tpAmb.valor = ambiente or self.ambiente
        evento.infEvento.cOrgao.valor = UF_CODIGO[self.estado]
        evento.infEvento.CNPJ.valor = cnpj
        evento.infEvento.chNFe.valor = chave_nfe
        evento.infEvento.dhEvento.valor = datetime.now()
        evento.infEvento.tpEvento.valor = CONF_RECEBIMENTO_DESCONHECIMENTO_OPERACAO
        evento.infEvento.detEvento.descEvento.valor = DESCEVENTO_CONF_RECEBIMENTO[evento.infEvento.tpEvento.valor]

        processo = self.enviar_lote_confirmacao_recebimento(lista_eventos=[evento])
        return processo

    def nao_realizar_operacao_evento(self, ambiente=None, chave_nfe=None, cnpj=None, justificativa=None):
        evento = EventoConfRecebimento_100()
        evento.infEvento.tpAmb.valor = ambiente or self.ambiente
        evento.infEvento.cOrgao.valor = UF_CODIGO[self.estado]
        evento.infEvento.CNPJ.valor = cnpj
        evento.infEvento.chNFe.valor = chave_nfe
        evento.infEvento.dhEvento.valor = datetime.now()
        evento.infEvento.tpEvento.valor = CONF_RECEBIMENTO_OPERACAO_NAO_REALIZADA
        evento.infEvento.detEvento.descEvento.valor = DESCEVENTO_CONF_RECEBIMENTO[evento.infEvento.tpEvento.valor]
        evento.infEvento.detEvento.xJust.valor = justificativa

        processo = self.enviar_lote_confirmacao_recebimento(lista_eventos=[evento])
        return processo

    def consultar_cadastro(self, estado=None, ie=None, cnpj_cpf=None):
        if self.versao == '1.10':
            envio = ConsCad_101()
            resposta = RetConsCad_101()

        elif self.versao == '2.00':
            envio = ConsCad_200()
            resposta = RetConsCad_200()

        processo = ProcessoNFe(webservice=WS_NFE_CONSULTA_CADASTRO, envio=envio, resposta=resposta)

        if estado is None:
            estado = self.estado

        envio.infCons.UF.valor = estado
        
        if ie is not None:
            envio.infCons.IE.valor = ie
            nome = 'IE_' + ie
        elif cnpj_cpf is not None:
            if len(cnpj_cpf) == 11:
                envio.infCons.CPF.valor = cnpj_cpf
                nome = 'CPF_' + cnpj_cpf
            else:
                envio.infCons.CNPJ.valor = cnpj_cpf
                nome = 'CNPJ_' + cnpj_cpf

        envio.validar()
        if self.salvar_arquivos:
            arq = open(self.caminho + nome + '-cons-cad.xml', 'w')
            arq.write(envio.xml.encode('utf-8'))
            arq.close()

        # Consulta de cadastro é sempre feita em ambiente de produção
        self._conectar_servico(WS_NFE_CONSULTA_CADASTRO, envio, resposta, 1)

        #resposta.validar()
        if self.salvar_arquivos:
            arq = open(self.caminho + nome + '-cad.xml', 'w')
            arq.write(resposta.xml.encode('utf-8'))
            arq.close()

        return processo