diff --git a/pynfe/processamento/comunicacao.py b/pynfe/processamento/comunicacao.py index 6500655..f5784c6 100644 --- a/pynfe/processamento/comunicacao.py +++ b/pynfe/processamento/comunicacao.py @@ -25,8 +25,10 @@ from .assinatura import AssinaturaA1, AssinaturaA1SignXML class Comunicacao(object): - u"""Classe abstrata responsavel por definir os metodos e logica das classes - de comunicação com os webservices da NF-e.""" + """ + Classe abstrata responsavel por definir os metodos e logica das classes + de comunicação com os webservices da NF-e. + """ _ambiente = 1 # 1 = Produção, 2 = Homologação uf = None @@ -40,52 +42,67 @@ class Comunicacao(object): self.certificado_senha = certificado_senha self._ambiente = 2 if homologacao else 1 + class ComunicacaoSefaz(Comunicacao): - u"""Classe de comunicação que segue o padrão definido para as SEFAZ dos Estados.""" + """Classe de comunicação que segue o padrão definido para as SEFAZ dos Estados.""" _versao = VERSAO_PADRAO _assinatura = AssinaturaA1 - def autorizacao(self, modelo, nota_fiscal, idlote=1, indSinc=1): + def autorizacao(self, modelo, nota_fiscal, id_lote=1, ind_sinc=1): + """ + Método para realizar autorização da nota de acordo com o modelo + :param modelo: Modelo + :param nota_fiscal: XML assinado + :param id_lote: Id do lote - numero autoincremental gerado pelo sistema + :param ind_sinc: Indicador de sincrono e assincrono, 0 para assincrono, 1 para sincrono + :return: Uma tupla que em caso de sucesso, retorna xml com nfe e protocolo de autorização. Caso contrário, + envia todo o soap de resposta da Sefaz para decisão do usuário. + """ # url do serviço url = self._get_url(modelo=modelo, consulta='AUTORIZACAO') + # Monta XML do corpo da requisição raiz = etree.Element('enviNFe', xmlns=NAMESPACE_NFE, versao=VERSAO_PADRAO) - etree.SubElement(raiz, 'idLote').text = str(idlote) # numero autoincremental gerado pelo sistema - etree.SubElement(raiz, 'indSinc').text = str(indSinc) # 0 para assincrono, 1 para sincrono + etree.SubElement(raiz, 'idLote').text = str(id_lote) # numero autoincremental gerado pelo sistema + etree.SubElement(raiz, 'indSinc').text = str(ind_sinc) # 0 para assincrono, 1 para sincrono raiz.append(nota_fiscal) + # Monta XML para envio da requisição - xml = self._construir_xml_status_pr(cabecalho=self._cabecalho_soap(metodo='NfeAutorizacao'), metodo='NfeAutorizacao', dados=raiz) + xml = self._construir_xml_status_pr( + cabecalho=self._cabecalho_soap(metodo='NfeAutorizacao'), metodo='NfeAutorizacao', dados=raiz + ) + # Faz request no Servidor da Sefaz retorno = self._post(url, xml) # Em caso de sucesso, retorna xml com nfe e protocolo de autorização. # Caso contrário, envia todo o soap de resposta da Sefaz para decisão do usuário. if retorno.status_code == 200: - if indSinc == 1: + if ind_sinc == 1: # Procuta status no xml - ns = {'ns':'http://www.portalfiscal.inf.br/nfe'} # namespace + ns = {'ns': 'http://www.portalfiscal.inf.br/nfe'} # namespace try: prot = etree.fromstring(retorno.text) except ValueError: - #em SP retorno.text apresenta erro + # em SP retorno.text apresenta erro prot = etree.fromstring(retorno.content) try: # Protocolo com envio OK - infProt = prot[1][0][0][6] # root protNFe - status = infProt.xpath("ns:infProt/ns:cStat", namespaces=ns)[0].text + inf_prot = prot[1][0][0][6] # root protNFe + status = inf_prot.xpath("ns:infProt/ns:cStat", namespaces=ns)[0].text except IndexError: # Protocolo com algum erro no Envio - retEnvi = prot[1][0][0] # root retEnvi - status = retEnvi.xpath("ns:cStat", namespaces=ns)[0].text + ret_envi = prot[1][0][0] # root retEnvi + status = ret_envi.xpath("ns:cStat", namespaces=ns)[0].text if status == '100': raiz = etree.Element('nfeProc', xmlns=NAMESPACE_NFE, versao=VERSAO_PADRAO) raiz.append(nota_fiscal) - raiz.append(infProt) + raiz.append(inf_prot) return 0, raiz else: # Retorna id do protocolo para posterior consulta em caso de sucesso. - ns = {'ns':'http://www.portalfiscal.inf.br/nfe'} # namespace + ns = {'ns': 'http://www.portalfiscal.inf.br/nfe'} # namespace rec = etree.fromstring(retorno.text) rec = rec[1][0][0] status = rec.xpath("ns:cStat", namespaces=ns)[0].text @@ -97,64 +114,95 @@ class ComunicacaoSefaz(Comunicacao): def consulta_recibo(self, modelo, numero): """ - Este método oferece a consulta do resultado do processamento de um lote de NF-e. - O aplicativo do Contribuinte deve ser construído de forma a aguardar um tempo mínimo de - 15 segundos entre o envio do Lote de NF-e para processamento e a consulta do resultado - deste processamento, evitando a obtenção desnecessária do status de erro 105 - "Lote em - Processamento". + Este método oferece a consulta do resultado do processamento de um lote de NF-e. + O aplicativo do Contribuinte deve ser construído de forma a aguardar um tempo mínimo de + 15 segundos entre o envio do Lote de NF-e para processamento e a consulta do resultado + deste processamento, evitando a obtenção desnecessária do status de erro 105 - "Lote em + Processamento". + :param modelo: Modelo da nota + :param numero: Número da nota + :return: """ + # url do serviço url = self._get_url(modelo=modelo, consulta='RECIBO') + # Monta XML do corpo da requisição raiz = etree.Element('consReciNFe', versao=VERSAO_PADRAO, xmlns=NAMESPACE_NFE) etree.SubElement(raiz, 'tpAmb').text = str(self._ambiente) etree.SubElement(raiz, 'nRec').text = numero + # Monta XML para envio da requisição - xml = self._construir_xml_status_pr(cabecalho=self._cabecalho_soap(metodo='NfeRetAutorizacao'), metodo='NfeRetAutorizacao', dados=raiz) + xml = self._construir_xml_status_pr( + cabecalho=self._cabecalho_soap(metodo='NfeRetAutorizacao'), metodo='NfeRetAutorizacao', dados=raiz + ) return self._post(url, xml) def consulta_nota(self, modelo, chave): """ - Este método oferece a consulta da situação da NF-e/NFC-e na Base de Dados do Portal da Secretaria de Fazenda Estadual. + Este método oferece a consulta da situação da NF-e/NFC-e na Base de Dados do Portal + da Secretaria de Fazenda Estadual. + :param modelo: Modelo da nota + :param chave: Chave da nota + :return: """ + # url do serviço url = self._get_url(modelo=modelo, consulta='CHAVE') + # Monta XML do corpo da requisição raiz = etree.Element('consSitNFe', versao=VERSAO_PADRAO, xmlns=NAMESPACE_NFE) etree.SubElement(raiz, 'tpAmb').text = str(self._ambiente) etree.SubElement(raiz, 'xServ').text = 'CONSULTAR' etree.SubElement(raiz, 'chNFe').text = chave + # Monta XML para envio da requisição - xml = self._construir_xml_status_pr(cabecalho=self._cabecalho_soap(metodo='NfeConsulta2'), metodo='NfeConsulta2', dados=raiz) + xml = self._construir_xml_status_pr( + cabecalho=self._cabecalho_soap(metodo='NfeConsulta2'), metodo='NfeConsulta2', dados=raiz + ) return self._post(url, xml) def consulta_notas_cnpj(self, cnpj, nsu=0): """ - “Serviço de Consulta da Relação de Documentos Destinados” para um determinado CNPJ de destinatário informado na NF-e. + “Serviço de Consulta da Relação de Documentos Destinados” para um determinado CNPJ de + destinatário informado na NF-e. + :param cnpj: CNPJ + :param nsu: NSU + :return: """ + # url do serviço - url = self._get_url_AN(consulta='DESTINADAS') + url = self._get_url_an(consulta='DESTINADAS') + # Monta XML do corpo da requisição raiz = etree.Element('consNFeDest', versao='1.01', xmlns=NAMESPACE_NFE) etree.SubElement(raiz, 'tpAmb').text = str(self._ambiente) etree.SubElement(raiz, 'xServ').text = 'CONSULTAR NFE DEST' etree.SubElement(raiz, 'CNPJ').text = cnpj + # Indicador de NF-e consultada: - # 0=Todas as NF-e; - # 1=Somente as NF-e que ainda não tiveram manifestação do destinatário (Desconhecimento da operação, Operação não Realizada ou Confirmação da Operação); - # 2=Idem anterior, incluindo as NF-e que também não tiveram a Ciência da Operação. + # 0 = Todas as NF-e; + # 1 = Somente as NF-e que ainda não tiveram manifestação do destinatário (Desconhecimento da + # operação, Operação não Realizada ou Confirmação da Operação); + # 2 = Idem anterior, incluindo as NF-e que também não tiveram a Ciência da Operação. etree.SubElement(raiz, 'indNFe').text = '0' + # Indicador do Emissor da NF-e: - # 0=Todos os Emitentes / Remetentes; - # 1=Somente as NF-e emitidas por emissores / remetentes que não tenham o mesmo CNPJ-Base do destinatário (para excluir as notas fiscais de transferência entre filiais). + # 0 = Todos os Emitentes / Remetentes; + # 1 = Somente as NF-e emitidas por emissores / remetentes que não tenham o mesmo CNPJ-Base do + # destinatário (para excluir as notas fiscais de transferência entre filiais). etree.SubElement(raiz, 'indEmi').text = '0' - # Último NSU recebido pela Empresa. Caso seja informado com zero, ou com um NSU muito antigo, a consulta retornará unicamente as notas fiscais que tenham sido recepcionadas nos últimos 15 dias. + + # Último NSU recebido pela Empresa. Caso seja informado com zero, ou com um NSU muito antigo, a consulta + # retornará unicamente as notas fiscais que tenham sido recepcionadas nos últimos 15 dias. etree.SubElement(raiz, 'ultNSU').text = str(nsu) # Monta XML para envio da requisição - xml = self._construir_xml_status_pr(cabecalho=self._cabecalho_soap(metodo='NfeConsultaDest'), metodo='NfeConsultaDest', dados=raiz) + xml = self._construir_xml_status_pr( + cabecalho=self._cabecalho_soap(metodo='NfeConsultaDest'), metodo='NfeConsultaDest', dados=raiz + ) return self._post(url, xml) @@ -162,8 +210,15 @@ class ComunicacaoSefaz(Comunicacao): pass def consulta_cadastro(self, modelo, cnpj): + """ + Consulta de cadastro + :param modelo: Modelo da nota + :param cnpj: CNPJ da empresa + :return: + """ # UF que utilizam a SVRS - Sefaz Virtual do RS: Para serviço de Consulta Cadastro: AC, RN, PB, SC - lista_svrs = ['AC','RN','PB','SC'] + lista_svrs = ['AC', 'RN', 'PB', 'SC'] + # RS implementa um método diferente na consulta de cadastro if self.uf.upper() == 'RS': url = NFE['RS']['CADASTRO'] @@ -179,35 +234,51 @@ class ComunicacaoSefaz(Comunicacao): etree.SubElement(info, 'xServ').text = 'CONS-CAD' etree.SubElement(info, 'UF').text = self.uf.upper() etree.SubElement(info, 'CNPJ').text = cnpj - #etree.SubElement(info, 'CPF').text = cpf + # etree.SubElement(info, 'CPF').text = cpf + # Monta XML para envio da requisição - xml = self._construir_xml_status_pr(cabecalho=self._cabecalho_soap(metodo='CadConsultaCadastro2'), metodo='CadConsultaCadastro2', dados=raiz) + xml = self._construir_xml_status_pr( + cabecalho=self._cabecalho_soap(metodo='CadConsultaCadastro2'), metodo='CadConsultaCadastro2', dados=raiz + ) + # Chama método que efetua a requisição POST no servidor SOAP return self._post(url, xml) - def evento(self, modelo, evento, idlote=1): - """ Envia um evento de nota fiscal (cancelamento e carta de correção)""" + def evento(self, modelo, evento, id_lote=1): + """ + Envia um evento de nota fiscal (cancelamento e carta de correção) + :param modelo: Modelo da nota + :param evento: Eventro + :param id_lote: Id do lote + :return: + """ + # url do serviço try: # manifestacao url é do AN if evento[0][5].text.startswith('2'): - url = self._get_url_AN(consulta='EVENTOS') + url = self._get_url_an(consulta='EVENTOS') else: url = self._get_url(modelo=modelo, consulta='EVENTOS') except Exception: url = self._get_url(modelo=modelo, consulta='EVENTOS') + # Monta XML do corpo da requisição raiz = etree.Element('envEvento', versao='1.00', xmlns=NAMESPACE_NFE) - etree.SubElement(raiz, 'idLote').text = str(idlote) # numero autoincremental gerado pelo sistema + etree.SubElement(raiz, 'idLote').text = str(id_lote) # numero autoincremental gerado pelo sistema raiz.append(evento) - xml = self._construir_xml_status_pr(cabecalho=self._cabecalho_soap(metodo='RecepcaoEvento'), metodo='RecepcaoEvento', dados=raiz) + xml = self._construir_xml_status_pr( + cabecalho=self._cabecalho_soap(metodo='RecepcaoEvento'), metodo='RecepcaoEvento', dados=raiz + ) return self._post(url, xml) def status_servico(self, modelo): - """ Verifica status do servidor da receita. """ - """ modelo é a string com tipo de serviço que deseja consultar - Ex: nfe ou nfce """ + Verifica status do servidor da receita. + :param modelo: modelo é a string com tipo de serviço que deseja consultar, Ex: nfe ou nfce + :return: + """ + url = self._get_url(modelo=modelo, consulta='STATUS') # Monta XML do corpo da requisição @@ -215,19 +286,28 @@ class ComunicacaoSefaz(Comunicacao): etree.SubElement(raiz, 'tpAmb').text = str(self._ambiente) etree.SubElement(raiz, 'cUF').text = CODIGOS_ESTADOS[self.uf.upper()] etree.SubElement(raiz, 'xServ').text = 'STATUS' + # Monta XML para envio da requisição - xml = self._construir_xml_status_pr(cabecalho=self._cabecalho_soap(metodo='NfeStatusServico2'), metodo='NfeStatusServico2', dados=raiz) + xml = self._construir_xml_status_pr( + cabecalho=self._cabecalho_soap(metodo='NfeStatusServico2'), metodo='NfeStatusServico2', dados=raiz + ) + # Chama método que efetua a requisição POST no servidor SOAP return self._post(url, xml) def download(self, cnpj, chave): """ - Metodo para download de NFe por parte de destinatário. - O certificado digital deve ser o mesmo do destinatário da Nfe. - NT 2012/002 + Metodo para download de NFe por parte de destinatário. + O certificado digital deve ser o mesmo do destinatário da Nfe. + NT 2012/002 + :param cnpj: CNPJ da empresa + :param chave: Chave + :return: """ + # url do serviço - url = self._get_url_AN(consulta='DOWNLOAD') + url = self._get_url_an(consulta='DOWNLOAD') + # Monta XML do corpo da requisição raiz = etree.Element('downloadNFe', versao='1.00', xmlns=NAMESPACE_NFE) etree.SubElement(raiz, 'tpAmb').text = str(self._ambiente) @@ -235,15 +315,29 @@ class ComunicacaoSefaz(Comunicacao): etree.SubElement(raiz, 'CNPJ').text = str(cnpj) etree.SubElement(raiz, 'chNFe').text = str(chave) - # Monta XML para envio da requisição - xml = self._construir_xml_status_pr(cabecalho=self._cabecalho_soap(metodo='NfeDownloadNF'), metodo='NfeDownloadNF', dados=raiz) + # Monta XML para envio da requisição + xml = self._construir_xml_status_pr( + cabecalho=self._cabecalho_soap(metodo='NfeDownloadNF'), metodo='NfeDownloadNF', dados=raiz + ) return self._post(url, xml) def inutilizacao(self, modelo, cnpj, numero_inicial, numero_final, justificativa='', ano=None, serie='1'): - """ Serviço destinado ao atendimento de solicitações de inutilização de numeração. """ + """ + Serviço destinado ao atendimento de solicitações de inutilização de numeração. + :param modelo: Modelo da nota + :param cnpj: CNPJda empresa + :param numero_inicial: Número inicial + :param numero_final: Número final + :param justificativa: Justificativa + :param ano: Ano + :param serie: Série + :return: + """ + # url do servico url = self._get_url(modelo=modelo, consulta='INUTILIZACAO') + # Valores default ano = str(ano or datetime.date.today().year)[-2:] uf = CODIGOS_ESTADOS[self.uf.upper()] @@ -251,15 +345,15 @@ class ComunicacaoSefaz(Comunicacao): # Identificador da TAG a ser assinada formada com Código da UF + Ano (2 posições) + # CNPJ + modelo + série + nro inicial e nro final precedida do literal “ID” - id_unico = 'ID%(uf)s%(ano)s%(cnpj)s%(modelo)s%(serie)s%(num_ini)s%(num_fin)s'%{ - 'uf': uf, - 'ano': ano, - 'cnpj': cnpj, - 'modelo': '55', - 'serie': serie.zfill(3), - 'num_ini': str(numero_inicial).zfill(9), - 'num_fin': str(numero_final).zfill(9), - } + id_unico = 'ID%(uf)s%(ano)s%(cnpj)s%(modelo)s%(serie)s%(num_ini)s%(num_fin)s' % { + 'uf': uf, + 'ano': ano, + 'cnpj': cnpj, + 'modelo': '55', + 'serie': serie.zfill(3), + 'num_ini': str(numero_inicial).zfill(9), + 'num_fin': str(numero_final).zfill(9), + } # Monta XML do corpo da requisição # FIXME raiz = etree.Element('inutNFe', versao=VERSAO_PADRAO, xmlns=NAMESPACE_NFE) @@ -280,11 +374,14 @@ class ComunicacaoSefaz(Comunicacao): xml = a1.assinar(raiz) # Monta XML para envio da requisição - xml = self._construir_xml_status_pr(cabecalho=self._cabecalho_soap(metodo='NfeInutilizacao2'), metodo='NfeInutilizacao2', dados=xml) + xml = self._construir_xml_status_pr( + cabecalho=self._cabecalho_soap(metodo='NfeInutilizacao2'), metodo='NfeInutilizacao2', dados=xml + ) + # Faz request no Servidor da Sefaz e retorna resposta return self._post(url, xml) - def _get_url_AN(self, consulta): + def _get_url_an(self, consulta): # producao if self._ambiente == 1: if consulta == 'DISTRIBUICAO': @@ -301,7 +398,7 @@ class ComunicacaoSefaz(Comunicacao): def _get_url(self, modelo, consulta): """ Retorna a url para comunicação com o webservice """ # estado que implementam webservices proprios - lista = ['PR','MS','SP','AM','CE','BA','GO','MG','MT','PE','RS'] + lista = ['PR', 'MS', 'SP', 'AM', 'CE', 'BA', 'GO', 'MG', 'MT', 'PE', 'RS'] if self.uf.upper() in lista: if self._ambiente == 1: ambiente = 'HTTPS' @@ -321,7 +418,7 @@ class ComunicacaoSefaz(Comunicacao): raise Exception('Modelo não encontrado! Defina modelo="nfe" ou "nfce"') # Estados que utilizam outros ambientes else: - lista_svrs = ['AC','RN','PB','SC','SE'] + lista_svrs = ['AC', 'RN', 'PB', 'SC', 'SE'] if self.uf.upper() in lista_svrs: if self._ambiente == 1: ambiente = 'HTTPS' @@ -340,8 +437,8 @@ class ComunicacaoSefaz(Comunicacao): def _get_url_uf(self, modelo, consulta): """ Estados que implementam url diferente do padrão nacional""" # estados que implementam webservice SVRS - svrs = ['AC','AL','AP','DF','ES','PB','RJ','RN','RO','RR','SC','SE','TO'] - svan = ['MA','PA','PI'] + svrs = ['AC', 'AL', 'AP', 'DF', 'ES', 'PB', 'RJ', 'RN', 'RO', 'RR', 'SC', 'SE', 'TO'] + svan = ['MA', 'PA', 'PI'] # SVRS if self.uf.upper() in svrs: if self._ambiente == 1: @@ -398,10 +495,10 @@ class ComunicacaoSefaz(Comunicacao): def _construir_xml_soap(self, cabecalho, metodo, dados): """Mota o XML para o envio via SOAP""" - raiz = etree.Element('{%s}Envelope'%NAMESPACE_SOAP, nsmap={'soap12': NAMESPACE_SOAP}) - c= etree.SubElement(raiz, '{%s}Header'%NAMESPACE_SOAP) + raiz = etree.Element('{%s}Envelope' % NAMESPACE_SOAP, nsmap={'soap12': NAMESPACE_SOAP}) + c = etree.SubElement(raiz, '{%s}Header' % NAMESPACE_SOAP) c.append(cabecalho) - body = etree.SubElement(raiz, '{%s}Body'%NAMESPACE_SOAP) + body = etree.SubElement(raiz, '{%s}Body' % NAMESPACE_SOAP) a = etree.SubElement(body, 'nfeDadosMsg', xmlns=NAMESPACE_METODO+metodo) a.append(dados) return raiz @@ -409,11 +506,13 @@ class ComunicacaoSefaz(Comunicacao): def _construir_xml_status_pr(self, cabecalho, metodo, dados): u"""Mota o XML para o envio via SOAP""" - raiz = etree.Element('{%s}Envelope' % NAMESPACE_SOAP, nsmap={'xsi': NAMESPACE_XSI, 'xsd': NAMESPACE_XSD,'soap': NAMESPACE_SOAP}) + raiz = etree.Element('{%s}Envelope' % NAMESPACE_SOAP, nsmap={ + 'xsi': NAMESPACE_XSI, 'xsd': NAMESPACE_XSD,'soap': NAMESPACE_SOAP + }) c = etree.SubElement(raiz, '{%s}Header' % NAMESPACE_SOAP) c.append(cabecalho) body = etree.SubElement(raiz, '{%s}Body' % NAMESPACE_SOAP) - a = etree.SubElement(body, 'nfeDadosMsg', xmlns=NAMESPACE_METODO+metodo) + a = etree.SubElement(body, 'nfeDadosMsg', xmlns=NAMESPACE_METODO + metodo) a.append(dados) return raiz @@ -422,19 +521,22 @@ class ComunicacaoSefaz(Comunicacao): return { u'content-type': u'application/soap+xml; charset=utf-8;', u'Accept': u'application/soap+xml; charset=utf-8;', - } + } def _post(self, url, xml): - certificadoA1 = CertificadoA1(self.certificado) - chave, cert = certificadoA1.separar_arquivo(self.certificado_senha, caminho=True) + certificado_a1 = CertificadoA1(self.certificado) + chave, cert = certificado_a1.separar_arquivo(self.certificado_senha, caminho=True) chave_cert = (cert, chave) # Abre a conexão HTTPS try: - xml_declaration='' + xml_declaration = '' + # limpa xml com caracteres bugados para infNFeSupl em NFC-e - xml = re.sub('(.*?)', - lambda x:x.group(0).replace('<','<').replace('>','>').replace('amp;',''), - etree.tostring(xml, encoding='unicode').replace('\n','')) + xml = re.sub( + '(.*?)', + lambda x: x.group(0).replace('<', '<').replace('>', '>').replace('amp;', ''), + etree.tostring(xml, encoding='unicode').replace('\n', '') + ) xml = xml_declaration + xml # adapter para substituir ssl por tls @@ -454,7 +556,7 @@ class ComunicacaoSefaz(Comunicacao): except requests.exceptions.RequestException as e: raise e finally: - certificadoA1.excluir() + certificado_a1.excluir() class ComunicacaoNfse(Comunicacao): @@ -570,9 +672,10 @@ class ComunicacaoNfse(Comunicacao): """ Monta o XML do cabeçalho da requisição wsdl Namespaces padrão homologação (Ginfes) """ - xml_declaration='' - # cabecalho = '3' - # cabecalho + xml_declaration = '' + # cabecalho = '3' + # cabecalho raiz = etree.Element('{%s}cabecalho'%self._namespace, nsmap={'ns2':self._namespace, 'xsi':NAMESPACE_XSI}, versao=self._versao) etree.SubElement(raiz, 'versaoDados').text = self._versao @@ -587,14 +690,14 @@ class ComunicacaoNfse(Comunicacao): """ Monta o XML do cabeçalho da requisição wsdl Namespaces que funcionaram em produção (Ginfes)""" - xml_declaration='' + xml_declaration = '' # cabecalho raiz = etree.Element('cabecalho', xmlns=self._namespace, versao=self._versao) etree.SubElement(raiz, 'versaoDados').text = self._versao if retorna_string: - cabecalho = etree.tostring(raiz, encoding='unicode', pretty_print=False).replace('\n','') + cabecalho = etree.tostring(raiz, encoding='unicode', pretty_print=False).replace('\n', '') cabecalho = xml_declaration + cabecalho return cabecalho else: @@ -673,7 +776,7 @@ class ComunicacaoNfse(Comunicacao): # versão 2 return cliente.service.CancelarNfse(xml) # versão 3 - #return cliente.service.CancelarNfseV3(cabecalho, xml) + # return cliente.service.CancelarNfseV3(cabecalho, xml) # TODO outros metodos else: raise Exception('Método não implementado no autorizador.')