From 62ad9582c6d34234472fa8a07fa3774733c9dfc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marinho=20Brand=C3=A3o?= Date: Thu, 21 Jan 2010 17:38:55 -0200 Subject: [PATCH 1/2] =?UTF-8?q?Trabalhando=20na=20comunica=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pynfe/processamento/comunicacao.py | 81 +++++++++++++++++++++----- pynfe/processamento/serializacao.py | 4 +- run_fake_soap_server.py | 58 +++++++++++++----- tests/03-processamento-01-serializacao-xml.txt | 2 +- tests/03-processamento-04-comunicacao.txt | 51 +++++++++++++++- tests/certificado.pem | 27 +++++++++ tests/key.pem | 15 +++++ 7 files changed, 203 insertions(+), 35 deletions(-) create mode 100644 tests/certificado.pem create mode 100644 tests/key.pem diff --git a/pynfe/processamento/comunicacao.py b/pynfe/processamento/comunicacao.py index 750730d..6c21e0f 100644 --- a/pynfe/processamento/comunicacao.py +++ b/pynfe/processamento/comunicacao.py @@ -1,22 +1,27 @@ # -*- coding: utf-8 -*- - +import datetime from httplib import HTTPSConnection, HTTPResponse -from pynfe.utils import etree, StringIO +from pynfe.utils import etree, StringIO, so_numeros from pynfe.utils.flags import NAMESPACE_NFE, NAMESPACE_SOAP, VERSAO_PADRAO +from pynfe.utils.flags import CODIGOS_ESTADOS, VERSAO_PADRAO class Comunicacao(object): u"""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 servidor = None + porta = 80 certificado = None certificado_senha = None - def __init__(self, servidor, certificado, certificado_senha): + def __init__(self, servidor, porta, certificado, certificado_senha, homologacao=False): self.servidor = servidor + self.porta = porta self.certificado = certificado self.certificado_senha = certificado_senha + self._ambiente = homologacao and 2 or 1 class ComunicacaoSefaz(Comunicacao): u"""Classe de comunicação que segue o padrão definido para as SEFAZ dos Estados.""" @@ -41,8 +46,8 @@ class ComunicacaoSefaz(Comunicacao): # Monta XML para envio da requisição xml = self._construir_xml_soap( - metodo='CadConsultaCadastro', - tag_metodo='consultaCadastro', + metodo='CadConsultaCadastro', # FIXME + tag_metodo='consultaCadastro', # FIXME cabecalho=self._cabecalho_soap(), dados=dados, ) @@ -51,18 +56,66 @@ class ComunicacaoSefaz(Comunicacao): retorno = self._post(post, xml, self._post_header()) # Transforma o retorno em etree - try: - retorno = etree.parse(StringIO(retorno)) - return retorno - except TypeError: - pass + #retorno = etree.parse(StringIO(retorno)) + + return bool(retorno) def consultar_cadastro(self, instancia): #post = '/nfeweb/services/cadconsultacadastro.asmx' post = '/nfeweb/services/nfeconsulta.asmx' - def inutilizar_faixa_numeracao(self, faixa): - pass + def inutilizar_faixa_numeracao(self, numero_inicial, numero_final, emitente, ano=None, + serie='1', justificativa=''): + post = '/nfeweb/services/nfestatusservico.asmx' + + # Valores default + ano = str(ano or datetime.date.today().year)[-2:] + uf = CODIGOS_ESTADOS[emitente.endereco_uf] + cnpj = so_numeros(emitente.cnpj) + + # 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), + } + + # Monta XML do corpo da requisição # FIXME + raiz = etree.Element('inutNFe', xmlns="http://www.portalfiscal.inf.br/nfe", versao="1.07") + inf_inut = etree.SubElement(raiz, 'infInut', Id=id_unico) # FIXME + etree.SubElement(inf_inut, 'tpAmb').text = str(self._ambiente) + etree.SubElement(inf_inut, 'xServ').text = 'INUTILIZAR' + etree.SubElement(inf_inut, 'cUF').text = uf + etree.SubElement(inf_inut, 'ano').text = ano + etree.SubElement(inf_inut, 'CNPJ').text = emitente.cnpj + etree.SubElement(inf_inut, 'mod').text = '55' + etree.SubElement(inf_inut, 'serie').text = serie + etree.SubElement(inf_inut, 'nNFIni').text = str(numero_inicial) + etree.SubElement(inf_inut, 'nNFFin').text = str(numero_final) + etree.SubElement(inf_inut, 'xJust').text = justificativa + dados = etree.tostring(raiz, encoding='utf-8', xml_declaration=True) + + # Efetua assinatura + # Monta XML para envio da requisição + xml = self._construir_xml_soap( + metodo='CadConsultaCadastro', # FIXME + tag_metodo='consultaCadastro', # FIXME + cabecalho=self._cabecalho_soap(), + dados=dados, + ) + + # Chama método que efetua a requisição POST no servidor SOAP + retorno = self._post(post, xml, self._post_header()) + + # Transforma o retorno em etree # TODO + #retorno = etree.parse(StringIO(retorno)) + + return retorno def _cabecalho_soap(self): u"""Monta o XML do cabeçalho da requisição SOAP""" @@ -99,11 +152,11 @@ class ComunicacaoSefaz(Comunicacao): caminho_chave, caminho_cert = self.certificado.separar_arquivo(senha=self.certificado_senha) # Abre a conexão HTTPS - con = HTTPSConnection(self.servidor, key_file=caminho_chave, cert_file=caminho_cert) + con = HTTPSConnection(self.servidor, self.porta, key_file=caminho_chave, cert_file=caminho_cert) try: #con.set_debuglevel(100) - + con.request(u'POST', post, xml, header) resp = con.getresponse() diff --git a/pynfe/processamento/serializacao.py b/pynfe/processamento/serializacao.py index f36939d..43e2462 100644 --- a/pynfe/processamento/serializacao.py +++ b/pynfe/processamento/serializacao.py @@ -17,7 +17,7 @@ class Serializacao(object): Nao deve ser instanciada diretamente!""" _fonte_dados = None - _ambiente = 1 + _ambiente = 1 # 1 = Produção, 2 = Homologação _nome_aplicacao = 'PyNFe' def __new__(cls, *args, **kwargs): @@ -232,7 +232,7 @@ class SerializacaoXML(Serializacao): etree.SubElement(ide, 'indPag').text = str(nota_fiscal.forma_pagamento) etree.SubElement(ide, 'mod').text = str(nota_fiscal.modelo) etree.SubElement(ide, 'serie').text = nota_fiscal.serie - etree.SubElement(ide, 'nNF').text = nota_fiscal.numero_nf + etree.SubElement(ide, 'nNF').text = str(nota_fiscal.numero_nf) etree.SubElement(ide, 'dEmi').text = nota_fiscal.data_emissao.strftime('%Y-%m-%d') etree.SubElement(ide, 'dSaiEnt').text = nota_fiscal.data_saida_entrada.strftime('%Y-%m-%d') etree.SubElement(ide, 'tpNF').text = str(nota_fiscal.tipo_documento) diff --git a/run_fake_soap_server.py b/run_fake_soap_server.py index c760285..692347f 100644 --- a/run_fake_soap_server.py +++ b/run_fake_soap_server.py @@ -1,24 +1,50 @@ -from soaplib.wsgi_soap import SimpleWSGISoapApp -from soaplib.service import soapmethod -from soaplib.serializers.primitive import String, Integer, Array, Null +# -*- coding: utf-8 -*- -class ServidorNFEFalso(SimpleWSGISoapApp): - @soapmethod(String, Integer, _returns=Array(String)) - def ping(self, nome, vezes): - ret = [nome for i in range(vezes)] - print ret - return ret +"""Este script deve ser executado com Python 2.6+ e OpenSSL""" + +import os + +CUR_DIR = os.path.dirname(os.path.abspath(__file__)) + +#from soaplib.wsgi_soap import SimpleWSGISoapApp +#from soaplib.service import soapmethod +#from soaplib.serializers.primitive import String, Integer, Array, Null + +#import tornado.wsgi +import tornado.httpserver +import tornado.ioloop +import tornado.web +import tornado.options + +#class ServidorNFEFalso(SimpleWSGISoapApp): +# @soapmethod(String, Integer, _returns=Array(String)) +# def ping(self, nome, vezes): +# ret = [nome for i in range(vezes)] +# return ret + +class HandlerStatusServico(tornado.web.RequestHandler): + def post(self): + self.write('') if __name__ == '__main__': porta = 8080 - # Via Tornado - import tornado.wsgi - import tornado.httpserver - import tornado.ioloop - application = ServidorNFEFalso() - container = tornado.wsgi.WSGIContainer(application) - http_server = tornado.httpserver.HTTPServer(container) + # Codigo específico da soaplib + #application = ServidorNFEFalso() + #container = tornado.wsgi.WSGIContainer(application) + #http_server = tornado.httpserver.HTTPServer(container) + + tornado.options.parse_command_line() + application = tornado.web.Application([ + (r'^/nfeweb/services/nfestatusservico.asmx$', HandlerStatusServico), # Consulta de status do serviço + ]) + + ssl_options = { + 'certfile': os.path.join(CUR_DIR, 'tests', 'certificado.pem'), + 'keyfile': os.path.join(CUR_DIR, 'tests', 'key.pem'), + } + + http_server = tornado.httpserver.HTTPServer(application, ssl_options=ssl_options) http_server.listen(porta) tornado.ioloop.IOLoop.instance().start() diff --git a/tests/03-processamento-01-serializacao-xml.txt b/tests/03-processamento-01-serializacao-xml.txt index 1ceb14c..aa629a4 100644 --- a/tests/03-processamento-01-serializacao-xml.txt +++ b/tests/03-processamento-01-serializacao-xml.txt @@ -63,7 +63,7 @@ Instancia a NF ... uf='GO', ... modelo=55, ... serie='1', - ... numero_nf='1', + ... numero_nf=1, ... data_emissao=datetime.date(2010,1,13), ... data_saida_entrada=datetime.date(2010,1,13), ... natureza_operacao='Venda a vista', diff --git a/tests/03-processamento-04-comunicacao.txt b/tests/03-processamento-04-comunicacao.txt index 55c781d..d6d8bc5 100644 --- a/tests/03-processamento-04-comunicacao.txt +++ b/tests/03-processamento-04-comunicacao.txt @@ -11,13 +11,60 @@ Carregando certificado digital tipo A1 Instancia de comunicacao >>> comunicacao = ComunicacaoSefaz( - ... #servidor='localhost:8080', - ... servidor='homologacao.nfe.fazenda.sp.gov.br', + ... servidor='localhost', + ... porta=8080, + ... #servidor='homologacao.nfe.fazenda.sp.gov.br', ... certificado=certificado, ... certificado_senha='associacao', ... ) +Instancia do emitente (auxiliar para este teste) + + >>> from pynfe.entidades import Emitente + >>> emitente = Emitente( + ... cnpj='12.345.678/0001-90', + ... razao_social='Tarsila Calcados Ltda.', + ... nome_fantasia='Tarsila Calcados Ltda.', + ... inscricao_estadual='123456789012', + ... endereco_logradouro='Rua 10', + ... endereco_numero='15', + ... endereco_complemento='qd 17, lt 10', + ... endereco_bairro='Setor Oeste', + ... endereco_municipio='5208806', # Goiania + ... endereco_uf='GO', + ... endereco_cep='75370-000', + ... endereco_telefone='6242421212', + ... ) + Verifica o status do servico >>> comunicacao.status_servico() + True + +Transmissao de NF-e + + >>> #comunicacao.transmitir(nota_fiscal) + +Cancelamento de NF-e + + >>> #comunicacao.cancelar(nota_fiscal) + +Consulta situacao de NF-e + + >>> #comunicacao.situacao_nfe(nota_fiscal) + +Consulta de cadastro (???) + + >>> #comunicacao.consultar_cadastro() + +Inulitilizacao de faixa de numeracao + + >>> comunicacao.inutilizar_faixa_numeracao( + ... numero_inicial=10, + ... numero_final=20, + ... emitente=emitente, + ... ano=2009, + ... serie='1', + ... justificativa='AJUSTE DA SEQUENCIA DE NUMERACAO', + ... ) diff --git a/tests/certificado.pem b/tests/certificado.pem new file mode 100644 index 0000000..a357501 --- /dev/null +++ b/tests/certificado.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEqzCCA5OgAwIBAgIDMTg4MA0GCSqGSIb3DQEBBQUAMIGSMQswCQYDVQQGEwJC +UjELMAkGA1UECBMCUlMxFTATBgNVBAcTDFBvcnRvIEFsZWdyZTEdMBsGA1UEChMU +VGVzdGUgUHJvamV0byBORmUgUlMxHTAbBgNVBAsTFFRlc3RlIFByb2pldG8gTkZl +IFJTMSEwHwYDVQQDExhORmUgLSBBQyBJbnRlcm1lZGlhcmlhIDEwHhcNMDkwNTIy +MTcwNzAzWhcNMTAxMDAyMTcwNzAzWjCBnjELMAkGA1UECBMCUlMxHTAbBgNVBAsT +FFRlc3RlIFByb2pldG8gTkZlIFJTMR0wGwYDVQQKExRUZXN0ZSBQcm9qZXRvIE5G +ZSBSUzEVMBMGA1UEBxMMUE9SVE8gQUxFR1JFMQswCQYDVQQGEwJCUjEtMCsGA1UE +AxMkTkZlIC0gQXNzb2NpYWNhbyBORi1lOjk5OTk5MDkwOTEwMjcwMIGfMA0GCSqG +SIb3DQEBAQUAA4GNADCBiQKBgQCx1O/e1Q+xh+wCoxa4pr/5aEFt2dEX9iBJyYu/ +2a78emtorZKbWeyK435SRTbHxHSjqe1sWtIhXBaFa2dHiukT1WJyoAcXwB1GtxjT +2VVESQGtRiujMa+opus6dufJJl7RslAjqN/ZPxcBXaezt0nHvnUB/uB1K8WT9G7E +S0V17wIDAQABo4IBfjCCAXowIgYDVR0jAQEABBgwFoAUPT5TqhNWAm+ZpcVsvB7m +alDBjEQwDwYDVR0TAQH/BAUwAwEBADAPBgNVHQ8BAf8EBQMDAOAAMAwGA1UdIAEB +AAQCMAAwgawGA1UdEQEBAASBoTCBnqA4BgVgTAEDBKAvBC0yMjA4MTk3Nzk5OTk5 +OTk5OTk5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDCgEgYFYEwBAwKgCQQHREZU +LU5GZaAZBgVgTAEDA6AQBA45OTk5OTA5MDkxMDI3MKAXBgVgTAEDB6AOBAwwMDAw +MDAwMDAwMDCBGmRmdC1uZmVAcHJvY2VyZ3MucnMuZ292LmJyMCAGA1UdJQEB/wQW +MBQGCCsGAQUFBwMCBggrBgEFBQcDBDBTBgNVHR8BAQAESTBHMEWgQ6BBhj9odHRw +Oi8vbmZlY2VydGlmaWNhZG8uc2VmYXoucnMuZ292LmJyL0xDUi9BQ0ludGVybWVk +aWFyaWEzOC5jcmwwDQYJKoZIhvcNAQEFBQADggEBAJFytXuiS02eJO0iMQr/Hi+O +x7/vYiPewiDL7s5EwO8A9jKx9G2Baz0KEjcdaeZk9a2NzDEgX9zboPxhw0RkWahV +CP2xvRFWswDIa2WRUT/LHTEuTeKCJ0iF/um/kYM8PmWxPsDWzvsCCRp146lc0lz9 +LGm5ruPVYPZ/7DAoimUk3bdCMW/rzkVYg7iitxHrhklxH7YWQHUwbcqPt7Jv0RJx +clc1MhQlV2eM2MO1iIlk8Eti86dRrJVoicR1bwc6/YDqDp4PFONTi1ddewRu6elG +S74AzCcNYRSVTINYiZLpBZO0uivrnTEnsFguVnNtWb9MAHGt3tkR0gAVs6S0fm8= +-----END CERTIFICATE----- diff --git a/tests/key.pem b/tests/key.pem new file mode 100644 index 0000000..dd798dd --- /dev/null +++ b/tests/key.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQCx1O/e1Q+xh+wCoxa4pr/5aEFt2dEX9iBJyYu/2a78emtorZKb +WeyK435SRTbHxHSjqe1sWtIhXBaFa2dHiukT1WJyoAcXwB1GtxjT2VVESQGtRiuj +Ma+opus6dufJJl7RslAjqN/ZPxcBXaezt0nHvnUB/uB1K8WT9G7ES0V17wIDAQAB +AoGAAYOcnzsVLR/JJKSa1uukis0WcYb/PsL7t2Ud6X5C/SdVrsh7jRMQ7oXNV4n7 +U2wayiHyQY/sZhhQoMYvVO6b2Wm/anq+F5L2QNT0FUxckRugDtH6tY6wqbs+AoTK ++eTBcSowB6HP6J9yZss02jSzh1xqX1t7q7dJEJjTGmoRczECQQDRrXux9N5Bdnzu +vTQFm7W1cYkxLrxPZ/gHFaHLAjd/h29cG1UTpkmUeKFDDPETE4q6Zmg3sWOAeMZB +tZQA7nf1AkEA2R5hS2Z+vvXLEGzxSTwYH2pRBoXUzuj801YqMhe4T/pJu4H3Bzab +4/SEEZdcFEa51HdOmqtOtQj1NDy/z3Lb0wJBAI1YaGEvU8BHcrKxgucg715QGg64 +laLl0HJeJ8IlTWo/z1cE6dYkK8fVhcggakbUzpkXPbwFbbEGOYfEMvBp0R0CQQCr +G98vriIbWthjJIhv3/Ve5Mngax6QxltiLpjoi3sNRMJRDRbiz23CFBT1TCUcMbUI +NdJz4KgR0nJ0bZ/43JtTAkBLJwkUNQDLdxx2EMif8KMRfsTAqUQ0d4wWnUP0hPHH +4qjUm/un000TF7eB85tszXxGxWATaGx4OPT91dXwzDsh +-----END RSA PRIVATE KEY----- From cd47ed7a31a57776538d6fbdc5335f048a29fff9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marinho=20Brand=C3=A3o?= Date: Thu, 21 Jan 2010 19:40:39 -0200 Subject: [PATCH 2/2] =?UTF-8?q?Trabalhando=20na=20comunica=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pynfe/processamento/assinatura.py | 9 +----- pynfe/processamento/comunicacao.py | 21 ++++++++----- pynfe/utils/__init__.py | 6 +++- run_fake_soap_server.py | 49 +++++++++++++++++++++++++++++-- tests/03-processamento-04-comunicacao.txt | 7 +++++ 5 files changed, 73 insertions(+), 19 deletions(-) diff --git a/pynfe/processamento/assinatura.py b/pynfe/processamento/assinatura.py index dde2c65..17fe5c7 100644 --- a/pynfe/processamento/assinatura.py +++ b/pynfe/processamento/assinatura.py @@ -2,9 +2,7 @@ import xmlsec, libxml2 # FIXME: verificar ambiguidade de dependencias: lxml e libxml2 -from geraldo.utils import memoize - -from pynfe.utils import etree, StringIO +from pynfe.utils import etree, StringIO, extrair_tag from pynfe.utils.flags import NAMESPACE_NFE, NAMESPACE_SIG class Assinatura(object): @@ -52,11 +50,6 @@ class Assinatura(object): def verificar_objetos(self, objetos): pass - -@memoize -def extrair_tag(root): - return root.tag.split('}')[-1] - class AssinaturaA1(Assinatura): """Classe abstrata responsavel por efetuar a assinatura do certificado digital no XML informado.""" diff --git a/pynfe/processamento/comunicacao.py b/pynfe/processamento/comunicacao.py index 6c21e0f..9ddbe55 100644 --- a/pynfe/processamento/comunicacao.py +++ b/pynfe/processamento/comunicacao.py @@ -5,6 +5,7 @@ from httplib import HTTPSConnection, HTTPResponse from pynfe.utils import etree, StringIO, so_numeros from pynfe.utils.flags import NAMESPACE_NFE, NAMESPACE_SOAP, VERSAO_PADRAO from pynfe.utils.flags import CODIGOS_ESTADOS, VERSAO_PADRAO +from assinatura import AssinaturaA1 class Comunicacao(object): u"""Classe abstrata responsavel por definir os metodos e logica das classes @@ -27,6 +28,7 @@ class ComunicacaoSefaz(Comunicacao): u"""Classe de comunicação que segue o padrão definido para as SEFAZ dos Estados.""" _versao = VERSAO_PADRAO + _assinatura = AssinaturaA1 def transmitir(self, nota_fiscal): pass @@ -46,8 +48,8 @@ class ComunicacaoSefaz(Comunicacao): # Monta XML para envio da requisição xml = self._construir_xml_soap( - metodo='CadConsultaCadastro', # FIXME - tag_metodo='consultaCadastro', # FIXME + metodo='nfeRecepcao2', # FIXME + tag_metodo='nfeStatusServicoNF2', # FIXME cabecalho=self._cabecalho_soap(), dados=dados, ) @@ -64,8 +66,8 @@ class ComunicacaoSefaz(Comunicacao): #post = '/nfeweb/services/cadconsultacadastro.asmx' post = '/nfeweb/services/nfeconsulta.asmx' - def inutilizar_faixa_numeracao(self, numero_inicial, numero_final, emitente, ano=None, - serie='1', justificativa=''): + def inutilizar_faixa_numeracao(self, numero_inicial, numero_final, emitente, certificado, + senha, ano=None, serie='1', justificativa=''): post = '/nfeweb/services/nfestatusservico.asmx' # Valores default @@ -87,7 +89,7 @@ class ComunicacaoSefaz(Comunicacao): # Monta XML do corpo da requisição # FIXME raiz = etree.Element('inutNFe', xmlns="http://www.portalfiscal.inf.br/nfe", versao="1.07") - inf_inut = etree.SubElement(raiz, 'infInut', Id=id_unico) # FIXME + inf_inut = etree.SubElement(raiz, 'infInut', Id=id_unico) etree.SubElement(inf_inut, 'tpAmb').text = str(self._ambiente) etree.SubElement(inf_inut, 'xServ').text = 'INUTILIZAR' etree.SubElement(inf_inut, 'cUF').text = uf @@ -98,13 +100,16 @@ class ComunicacaoSefaz(Comunicacao): etree.SubElement(inf_inut, 'nNFIni').text = str(numero_inicial) etree.SubElement(inf_inut, 'nNFFin').text = str(numero_final) etree.SubElement(inf_inut, 'xJust').text = justificativa - dados = etree.tostring(raiz, encoding='utf-8', xml_declaration=True) + #dados = etree.tostring(raiz, encoding='utf-8', xml_declaration=True) # Efetua assinatura + assinatura = self._assinatura(certificado, senha) + dados = assinatura.assinar_etree(etree.ElementTree(raiz), retorna_xml=True) + # Monta XML para envio da requisição xml = self._construir_xml_soap( - metodo='CadConsultaCadastro', # FIXME - tag_metodo='consultaCadastro', # FIXME + metodo='nfeRecepcao2', # XXX + tag_metodo='nfeInutilizacaoNF', # XXX cabecalho=self._cabecalho_soap(), dados=dados, ) diff --git a/pynfe/utils/__init__.py b/pynfe/utils/__init__.py index b11ab9f..f3efd60 100644 --- a/pynfe/utils/__init__.py +++ b/pynfe/utils/__init__.py @@ -64,4 +64,8 @@ def obter_municipio_por_codigo(codigo, uf): municipios = carregar_arquivo_municipios(uf) return municipios[codigo] - + +@memoize +def extrair_tag(root): + return root.tag.split('}')[-1] + diff --git a/run_fake_soap_server.py b/run_fake_soap_server.py index 692347f..8a1b90d 100644 --- a/run_fake_soap_server.py +++ b/run_fake_soap_server.py @@ -2,7 +2,7 @@ """Este script deve ser executado com Python 2.6+ e OpenSSL""" -import os +import os, datetime CUR_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -16,6 +16,9 @@ import tornado.ioloop import tornado.web import tornado.options +from pynfe.utils import etree, StringIO, extrair_tag +from pynfe.utils.flags import CODIGOS_ESTADOS + #class ServidorNFEFalso(SimpleWSGISoapApp): # @soapmethod(String, Integer, _returns=Array(String)) # def ping(self, nome, vezes): @@ -23,8 +26,50 @@ import tornado.options # return ret class HandlerStatusServico(tornado.web.RequestHandler): + sigla_servidor = 'GO' + def post(self): - self.write('') + # Obtem o body da request + xml = self.request.body + + # Transforma em etree + raiz = etree.parse(StringIO(xml)) + + # Extrai a tag do método da request + tag = extrair_tag(raiz.getroot().getchildren()[0].getchildren()[0]) + + # Chama o método respectivo para a tag + print 'Metodo:', tag + getattr(self, tag)(raiz) + + def nfeStatusServicoNF2(self, raiz): + data_hora = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%S') + + ret = etree.Element('retConsStatServ') + etree.SubElement(ret, 'versao').text = '1.00' # FIXME + etree.SubElement(ret, 'tbAmb').text = '2' # Homologação + etree.SubElement(ret, 'verAplic').text = self.sigla_servidor + etree.SubElement(ret, 'cStat').text = '1' # FIXME + etree.SubElement(ret, 'xMotivo').text = 'Servico em funcionamento normal' # FIXME + etree.SubElement(ret, 'cUF').text = CODIGOS_ESTADOS[self.sigla_servidor] + etree.SubElement(ret, 'dhRecbto').text = data_hora + etree.SubElement(ret, 'tMed').text = '10' + etree.SubElement(ret, 'dhRetorno').text = data_hora + etree.SubElement(ret, 'xObs').text = 'Nenhuma informacao adicional' + + xml = etree.tostring(ret, encoding='utf-8', xml_declaration=True) + self.write(xml) + + def nfeInutilizacaoNF(self, raiz): + data_hora = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%S') + + ret = etree.Element('retInutNFe') + etree.SubElement(ret, 'versao').text = '1.00' # FIXME + + xml_dados = raiz.getroot().getchildren()[0].getchildren()[0].getchildren()[1].text + + xml = etree.tostring(ret, encoding='utf-8', xml_declaration=True) + self.write(xml) if __name__ == '__main__': porta = 8080 diff --git a/tests/03-processamento-04-comunicacao.txt b/tests/03-processamento-04-comunicacao.txt index d6d8bc5..bd6bf65 100644 --- a/tests/03-processamento-04-comunicacao.txt +++ b/tests/03-processamento-04-comunicacao.txt @@ -36,6 +36,11 @@ Instancia do emitente (auxiliar para este teste) ... endereco_telefone='6242421212', ... ) +Instancia do certificado + + >>> from pynfe.entidades import CertificadoA1 + >>> certificado = CertificadoA1(caminho_arquivo='tests/certificado.pfx') + Verifica o status do servico >>> comunicacao.status_servico() @@ -60,6 +65,8 @@ Consulta de cadastro (???) Inulitilizacao de faixa de numeracao >>> comunicacao.inutilizar_faixa_numeracao( + ... certificado=certificado, + ... senha='associacao', ... numero_inicial=10, ... numero_final=20, ... emitente=emitente,