diff --git a/pynfe/entidades/notafiscal.py b/pynfe/entidades/notafiscal.py index 351165b..517691d 100644 --- a/pynfe/entidades/notafiscal.py +++ b/pynfe/entidades/notafiscal.py @@ -382,6 +382,7 @@ class NotaFiscal(Entidade): remainder = key_sum % 11 if remainder == 0 or remainder == 1: + self.dv_codigo_numerico_aleatorio = '0' return '0' self.dv_codigo_numerico_aleatorio = str(11 - remainder) return str(self.dv_codigo_numerico_aleatorio) diff --git a/pynfe/processamento/assinatura.py b/pynfe/processamento/assinatura.py index 98c6241..f1a85f0 100644 --- a/pynfe/processamento/assinatura.py +++ b/pynfe/processamento/assinatura.py @@ -26,7 +26,7 @@ class AssinaturaA1(Assinatura): """Classe responsavel por efetuar a assinatura do certificado digital no XML informado. Passar XML como string.""" - def assinar(self, xml): + def assinar(self, xml, retona_string=True): arquivo_cert = CertificadoA1(self.certificado) chave, cert = arquivo_cert.separar_arquivo(self.senha) @@ -44,6 +44,7 @@ class AssinaturaA1(Assinatura): #root.findall('.//{http://www.w3.org/2000/09/xmldsig#}Reference')[0] \ # .attrib['URI'] = '#chaveteste' - result = etree.tostring(root, encoding="unicode", pretty_print=True) - - return result + if retona_string: + return etree.tostring(root, encoding="unicode", pretty_print=True) + else: + return root diff --git a/pynfe/processamento/comunicacao.py b/pynfe/processamento/comunicacao.py index ee6f412..d08c1ee 100644 --- a/pynfe/processamento/comunicacao.py +++ b/pynfe/processamento/comunicacao.py @@ -31,19 +31,21 @@ class ComunicacaoSefaz(Comunicacao): def autorizacao(self, modelo, nota_fiscal): # url do serviço url = self._get_url(modelo=modelo, consulta='AUTORIZACAO') - + parser = etree.XMLParser(remove_blank_text=True) # Monta XML do corpo da requisição - raiz = etree.Element('enviNFe') - etree.SubElement(raiz, 'versaoDados').text = self._versao + raiz = etree.Element('enviNFe', versao=VERSAO_PADRAO) + #etree.SubElement(raiz, 'versao').text = self._versao etree.SubElement(raiz, 'idLote').text = str(1) # numero autoincremental gerado pelo sistema etree.SubElement(raiz, 'indSinc').text = str(1) # 0 para assincrono, 1 para sincrono - etree.SubElement(raiz, 'NFe').text = nota_fiscal # conjunto de nfe tramistidas (max 50) - dados = etree.tostring(raiz, encoding="unicode") # BUG < retorna caracteres ASCII + #etree.SubElement(raiz, 'NFe').text = nota_fiscal # conjunto de nfe tramistidas (max 50) + raiz.append(etree.fromstring(nota_fiscal)) + elem = etree.XML(etree.tostring(raiz, encoding='unicode'), parser=parser) + dados = etree.tostring(elem, encoding="unicode") # BUG < retorna caracteres ASCII # Monta XML para envio da requisição - xml = self._construir_xml_status_pr(cabecalho=self._cabecalho_soap(), dados=dados, url=url) - xml = str(xml).replace('&','').replace('lt;','<').replace('gt;','>').replace('&','') - - return self._post(url, xml, self._post_header()) + xml = self._construir_xml_status_pr(cabecalho=self._cabecalho_soap(), dados=dados) + xml = str(xml).replace('lt;','<').replace('gt;','>').replace('&','').replace('ds:','') + #print (xml) + return self._post(url, xml) def cancelar(self, modelo, xml): """ Envia um evento de cancelamento de nota fiscal """ @@ -65,17 +67,17 @@ class ComunicacaoSefaz(Comunicacao): xml = self._construir_xml_status_pr(cabecalho=self._cabecalho_soap(), dados=dados, url=url) xml = str(xml).replace('&','').replace('lt;','<').replace('gt;','>').replace('&','') return xml - #return self._post(url, xml, self._post_header()) + #return self._post(url, xml) def situacao_nfe(self, nota_fiscal): pass - def status_servico(self, tipo): + def status_servico(self, modelo): """ Verifica status do servidor da receita. """ """ tipo é a string com tipo de serviço que deseja consultar Ex: nfe ou nfce """ - url = self._get_url(tipo=tipo, consulta='STATUS') + url = self._get_url(modelo=modelo, consulta='STATUS') # Monta XML do corpo da requisição raiz = etree.Element('consStatServ', versao='3.10', xmlns=NAMESPACE_NFE) @@ -85,12 +87,12 @@ class ComunicacaoSefaz(Comunicacao): dados = etree.tostring(raiz, encoding="utf-8").decode('utf-8') # Monta XML para envio da requisição if self.uf.upper() == 'PR': - xml = self._construir_xml_status_pr(cabecalho=self._cabecalho_soap(), dados=dados, url=url) + xml = self._construir_xml_status_pr(cabecalho=self._cabecalho_soap(), dados=dados) else: xml = self._construir_xml_soap(cabecalho=self._cabecalho_soap(), metodo='nfeRecepcao2', tag_metodo='nfeStatusServicoNF2', dados=dados) - xml = str(xml, 'utf-8').replace('<', '<').replace('>', '>').replace('\'', '"').replace('\n', '') + xml = str(xml).replace('<', '<').replace('>', '>').replace('\'', '"').replace('\n', '') # Chama método que efetua a requisição POST no servidor SOAP - return self._post(url, xml, self._post_header()) + return self._post(url, xml) def consultar_cadastro(self, instancia): #post = '/nfeweb/services/cadconsultacadastro.asmx' @@ -183,7 +185,7 @@ class ComunicacaoSefaz(Comunicacao): body = etree.SubElement(raiz, '{%s}Body'%NAMESPACE_SOAP) met = etree.SubElement( - body, tag_metodo, xmlns="http://www.portalfiscal.inf.br/nfe/wsdl/%s"%metodo, + body, tag_metodo, xmlns=metodo, ) etree.SubElement(met, 'nfeCabecMsg').text = cabecalho @@ -191,15 +193,15 @@ class ComunicacaoSefaz(Comunicacao): return etree.tostring(raiz, encoding="utf-8", xml_declaration=True) - def _construir_xml_status_pr(self, cabecalho, dados, url): + def _construir_xml_status_pr(self, cabecalho, dados): u"""Mota o XML para o envio via SOAP""" - raiz = etree.Element('{%s}Envelope'%NAMESPACE_SOAP, nsmap={'soap': NAMESPACE_SOAP}, xmlns=url) + raiz = etree.Element('{%s}Envelope'%NAMESPACE_SOAP, nsmap={'soap': NAMESPACE_SOAP}, xmlns=NAMESPACE_NFE) etree.SubElement(raiz, '{%s}Header'%NAMESPACE_SOAP).text = cabecalho body = etree.SubElement(raiz, '{%s}Body'%NAMESPACE_SOAP) etree.SubElement(body, 'nfeDadosMsg').text = dados - return etree.tostring(raiz, encoding="UTF-8", xml_declaration=True).decode('utf-8') + return etree.tostring(raiz, encoding='unicode') def _post_header(self): u"""Retorna um dicionário com os atributos para o cabeçalho da requisição HTTP""" @@ -210,22 +212,25 @@ class ComunicacaoSefaz(Comunicacao): u'Accept': u'application/soap+xml; charset=utf-8', } - def _post(self, url, xml, header): + def _post(self, url, xml): # Separa arquivos de certificado para chave e certificado (sozinho) #caminho_chave, caminho_cert = self.certificado.separar_arquivo(senha=self.certificado_senha) - caminho_chave = 'key.pem' - caminho_cert = 'cert.pem' + #caminho_chave = 'key.pem' + #caminho_cert = 'cert.pem' + caminho_chave = '/home/junior/Documentos/Certificados/key.pem' + caminho_cert = '/home/junior/Documentos/Certificados/cert.pem' # Abre a conexão HTTPS cert = (caminho_cert, caminho_chave) #headers = {'content-type': 'text/xml'} try: + print (url) result = requests.post(url, xml, headers=self._post_header(), cert=cert, verify=False) print (result.content) if result == 200: return result.text else: return result - except Exception: - raise + except requests.exceptions.ConnectionError as e: + raise e \ No newline at end of file diff --git a/pynfe/processamento/serializacao.py b/pynfe/processamento/serializacao.py index 85bdc74..3e1a401 100644 --- a/pynfe/processamento/serializacao.py +++ b/pynfe/processamento/serializacao.py @@ -6,7 +6,7 @@ from pynfe.entidades import NotaFiscal from pynfe.utils import etree, so_numeros, obter_municipio_por_codigo, \ obter_pais_por_codigo, obter_municipio_e_codigo, \ formatar_decimal, safe_str, obter_uf_por_codigo, obter_codigo_por_municipio -from pynfe.utils.flags import CODIGOS_ESTADOS, VERSAO_PADRAO +from pynfe.utils.flags import CODIGOS_ESTADOS, VERSAO_PADRAO, NAMESPACE_NFE class Serializacao(object): """Classe abstrata responsavel por fornecer as funcionalidades basicas para @@ -49,7 +49,7 @@ class SerializacaoXML(Serializacao): armazenado(s) em cache local.""" # No raiz do XML de saida - raiz = etree.Element('NFe', xmlns="http://www.portalfiscal.inf.br/nfe") + raiz = etree.Element('NFe', xmlns=NAMESPACE_NFE) # Carrega lista de Notas Fiscais notas_fiscais = self._fonte_dados.obter_lista(_classe=NotaFiscal, **kwargs) @@ -75,8 +75,6 @@ class SerializacaoXML(Serializacao): etree.SubElement(raiz, 'CNPJ').text = so_numeros(emitente.cnpj) etree.SubElement(raiz, 'xNome').text = emitente.razao_social etree.SubElement(raiz, 'xFant').text = emitente.nome_fantasia - etree.SubElement(raiz, 'IE').text = emitente.inscricao_estadual - # Endereço endereco = etree.SubElement(raiz, 'enderEmit') etree.SubElement(endereco, 'xLgr').text = emitente.endereco_logradouro @@ -91,7 +89,11 @@ class SerializacaoXML(Serializacao): etree.SubElement(endereco, 'cPais').text = emitente.endereco_pais etree.SubElement(endereco, 'xPais').text = obter_pais_por_codigo(emitente.endereco_pais) etree.SubElement(endereco, 'fone').text = emitente.endereco_telefone - + etree.SubElement(raiz, 'IE').text = emitente.inscricao_estadual + etree.SubElement(raiz, 'IEST').text = emitente.inscricao_estadual_subst_tributaria + etree.SubElement(raiz, 'IM').text = emitente.inscricao_municipal + etree.SubElement(raiz, 'CNAE').text = emitente.cnae_fiscal + etree.SubElement(raiz, 'CRT').text = emitente.codigo_de_regime_tributario if retorna_string: return etree.tostring(raiz, encoding="unicode", pretty_print=True) else: @@ -253,19 +255,21 @@ class SerializacaoXML(Serializacao): Identificador de local de destino da operação 1=Operação interna;2=Operação interestadual;3=Operação com exterior. """ if nota_fiscal.modelo == 65: - etree.SubElement(ide, 'idDest').text = str(1) - etree.SubElement(ide, 'indPres').text = str(1) - etree.SubElement(ide, 'indFinal').text = str(1) + etree.SubElement(ide, 'idDest').text = str(1) else: etree.SubElement(ide, 'idDest').text = str(nota_fiscal.indicador_destino) - etree.SubElement(ide, 'indPres').text = str(nota_fiscal.indicador_presencial) - etree.SubElement(ide, 'indFinal').text = str(nota_fiscal.cliente_final) etree.SubElement(ide, 'cMunFG').text = nota_fiscal.municipio etree.SubElement(ide, 'tpImp').text = str(nota_fiscal.tipo_impressao_danfe) etree.SubElement(ide, 'tpEmis').text = str(nota_fiscal.forma_emissao) etree.SubElement(ide, 'cDV').text = nota_fiscal.dv_codigo_numerico_aleatorio etree.SubElement(ide, 'tpAmb').text = str(self._ambiente) etree.SubElement(ide, 'finNFe').text = str(nota_fiscal.finalidade_emissao) + if nota_fiscal.modelo == 65: + etree.SubElement(ide, 'indFinal').text = str(1) + etree.SubElement(ide, 'indPres').text = str(1) + else: + etree.SubElement(ide, 'indFinal').text = str(nota_fiscal.cliente_final) + etree.SubElement(ide, 'indPres').text = str(nota_fiscal.indicador_presencial) etree.SubElement(ide, 'procEmi').text = str(nota_fiscal.processo_emissao) etree.SubElement(ide, 'verProc').text = '%s %s'%(self._nome_aplicacao, nota_fiscal.versao_processo_emissao) ### CONTINGENCIA ### diff --git a/pynfe/utils/webservices.py b/pynfe/utils/webservices.py index 1d7fff0..4aed8b1 100644 --- a/pynfe/utils/webservices.py +++ b/pynfe/utils/webservices.py @@ -424,13 +424,16 @@ NFE = { 'CADASTRO': '' }, 'RS': { - 'STATUS': '', - 'AUTORIZACAO': '', - 'RECIBO': '', - 'CHAVE': '', - 'INUTILIZACAO': '', - 'EVENTOS': '', - 'CADASTRO': '' + 'STATUS': 'nfe.sefaz.rs.gov.br/ws/NfeStatusServico/NfeStatusServico2.asmx', + 'AUTORIZACAO': 'nfe.sefaz.rs.gov.br/ws/NfeAutorizacao/NFeAutorizacao.asmx', + 'RECIBO': 'nfe.sefaz.rs.gov.br/ws/NfeRetAutorizacao/NFeRetAutorizacao.asmx', + 'CHAVE': 'nfe.sefaz.rs.gov.br/ws/NfeConsulta/NfeConsulta2.asmx', + 'INUTILIZACAO': 'nfe.sefaz.rs.gov.br/ws/NfeInutilizacao/NfeInutilizacao2.asmx', + 'EVENTOS': 'nfe.sefaz.rs.gov.br/ws/recepcaoevento/recepcaoevento.asmx', + 'CADASTRO': 'nfe.sefaz.rs.gov.br/ws/cadconsultacadastro/cadconsultacadastro2.asmx', + 'EVENTOS': 'nfe.sefaz.rs.gov.br/ws/recepcaoevento/recepcaoevento.asmx', + 'DOWNLOAD': 'nfe.sefaz.rs.gov.br/ws/nfeDownloadNF/nfeDownloadNF.asmx', + 'DESTINADAS': 'nfe.sefaz.rs.gov.br/ws/nfeConsultaDest/nfeConsultaDest.asmx' }, 'MS': { 'STATUS': '',